Commit graph

111 commits

Author SHA1 Message Date
Jon Seager
937ee4c5f2
feat: store bag finished_at as full datetime for correct timeline ordering
The finished_at field previously stored only a date, causing bag "finished"
timeline events to sort before same-day brews. Now stores a full datetime
and uses the actual close time for timeline ordering.
2026-02-24 21:38:39 +00:00
Jon Seager
6a2bd5e563
fix: allow multiple origins for mixed roasts 2026-02-24 11:03:20 +00:00
Jon Seager
0e3847ade9
feat: sync timeline events with entity edits
Add background timeline rebuild task (mirroring stats cache pattern)
that refreshes denormalized timeline event snapshots when entities are
updated. Includes cascade logic so editing a roaster refreshes timeline
events for its roasts, bags, brews, and cups.

- Add update_by_entity/delete_by_entity/delete_all to TimelineEventRepository
- Add TimelineInvalidator with debounced background rebuild task
- Add invalidate() calls to all 7 entity update handlers
- Add delete_by_entity cleanup to define_delete_handler! macro
- Add gear_id filter to BrewFilter for cascade traversal
- Add `brewlog timeline rebuild` CLI command for full rebuild
- Add 5 integration tests for timeline sync behavior
2026-02-15 11:11:07 +00:00
Jon Seager
920931ba17
refactor: fix template review findings (security, macros, tokens)
Address findings from the templates code review:

- Fix XSS in admin.html onclick handlers via data attributes
- Fix XSS in 5 edit page signal initializations via JSON serialization
- Fix register.html token exposure by moving to data attribute
- Add entity_icon, quick_notes_toggles, add_form_submit macros
- Replace hardcoded colors with design tokens (warning, error, success)
- Add warning design tokens to CSS theme
- Scope MutationObserver to main element
- Add defer to webauthn.js script tags
- Refactor login/register JS to arrow functions
- Guard lightbox script behind image_url check
- Fix else-if to elif in 5 templates
2026-02-13 16:20:30 +00:00
Jon Seager
ab4bcbaa0a
test: add unit tests for all reviewed presentation modules
Cover parse_created_at in CLI, StatsView::is_empty, format_datetime,
Paginated helpers, encode_uri_component, page_size_from_text,
build_map_data, build_coffee_info, used_percent edge cases,
brew_again_url construction, and parse_and_categorize splitting.
2026-02-13 16:20:29 +00:00
Jon Seager
472e851b80
refactor: standardize view model conversions on From trait
Convert single-argument from_domain() methods to impl From<T> for
consistency with the existing CafeView, RoasterView, and option view
patterns. Affected: BagView, BrewView, CupView, GearView,
GearDetailView, CafeDetailView, RoasterDetailView, TimelineEventView.

RoastView retains from_domain() since it takes extra parameters.
2026-02-13 16:20:27 +00:00
Jon Seager
31cb7c6c71
refactor: simplify NearbyCafeView iterator chain and remove unused _addr parameter
- Replace .iter().copied().cloned() with .into_iter() on str slices in
  NearbyCafeView::from
- Remove unused _addr: SocketAddr parameter from run_callback_server
  and its call site in tokens.rs
2026-02-13 16:20:23 +00:00
Jon Seager
766ac1432d
refactor: extract duplicated helpers for tasting notes, used_percent, format_datetime, and map builder
- Add parse_and_categorize() in tasting_notes.rs, replacing identical
  split-trim-categorize pipelines in mod.rs, roasts.rs, and timeline.rs
- Add used_percent() in bags.rs, deduplicating the calculation in
  BagView::from_domain and BagDetailView::from_parts
- Add format_datetime() in views/mod.rs, replacing 16 paired occurrences
  of .format("%Y-%m-%d") / .format("%H:%M") across 7 view files
- Add build_origin_roaster_map() in views/mod.rs, replacing identical
  map-entry building blocks in bags.rs, brews.rs, and roasts.rs detail views
2026-02-13 16:20:21 +00:00
Jon Seager
3d49236c13
refactor: move NearbyCafe to domain layer to fix dependency violation
The presentation layer was importing NearbyCafe directly from
infrastructure::foursquare, violating the dependency flow
(presentation -> application -> domain <- infrastructure). Introduce
NearbyCafeResult in domain::nearby_cafes and update all
references.
2026-02-13 16:20:20 +00:00
Jon Seager
de49f7c3b1
refactor: add EntityType enum and typed IDs in TimelineBrewData
Replace stringly-typed entity references with a compile-time-safe
EntityType enum throughout timeline events, images, and repository
operations. Also replace raw i64 fields in TimelineBrewData with
typed BagId/GearId wrappers.
2026-02-13 16:20:14 +00:00
Jon Seager
b8a26bbeca
feat: add entity counts to stats cache for home page
Add EntityCounts struct and StatsRepository::entity_counts() to query
per-entity row counts. Include them in CachedStats so the home page can
read counts from cache instead of issuing six list queries with LIMIT 1.
Derive Default on StatsView for the fallback case.
2026-02-13 13:06:04 +00:00
Jon Seager
3a32d9725a
feat: add remaining amount field to bag edit form 2026-02-11 08:53:56 +00:00
Jon Seager
a38e9aae5b
fix: use shortened dates in expanded timeline cards 2026-02-10 20:33:24 +00:00
Jon Seager
7ec422f7ce
style: short month names on expanded timeline cards 2026-02-10 20:32:13 +00:00
Jon Seager
4ee8cf7964
fix: polish edit forms and detail pages
- Skip payload in tracing::instrument to avoid logging base64 image data
- Add blob: to CSP img-src for image preview support
- Add deferred_upload_with_preview macro for edit form image previews
  with Replace/Remove buttons and proper DOM cleanup on replacement
- Fix datastar-fetch finished handler (evt.detail.response is undefined
  for redirect scripts)
- Display brew time in M:SS format on edit form
- Add full-width Save Changes button with check icon and Cancel button
  to all edit forms
- Fix country flag emoji spacing on cafe and cup detail pages
- Add "View on Map" Google Maps link to cafe and cup detail pages
2026-02-10 19:43:03 +00:00
Jon Seager
5f99ca0953
feat: add CLI update commands for brews and cups
Add UpdateBrewCommand and UpdateCupCommand to the CLI with all
updatable fields. Add corresponding update() methods to BrewsClient
and CupsClient.
2026-02-10 19:42:55 +00:00
Jon Seager
1a1b28559c
feat: add edit page route handlers, template structs, and edit templates
Add edit page handlers for all 7 entities (roaster, roast, bag, brew,
cafe, cup, gear) with authentication, data pre-loading, and image URL
resolution. Register edit routes in app router. Add corresponding
template structs and HTML templates with pre-populated forms.
2026-02-10 19:42:49 +00:00
Jon Seager
94088f1f4b
feat: add edit button to all detail pages
Add edit_button and edit_delete_buttons macros to detail_cards.html.
All 7 entity detail pages now show an Edit button next to Delete when
authenticated. Each detail template struct receives a pre-computed
edit_url from the route handler.
2026-02-10 19:42:47 +00:00
Jon Seager
4b3f03f5d3
feat: add entity image support with display on detail pages
Add image infrastructure, API, and detail page integration:

- EntityImage domain model with ImageRepository trait
- SQLite storage for images and thumbnails as BLOBs
- Image processing with data URL decoding and thumbnail generation
- REST API: upload, get, delete, thumbnail at /{entity_type}/{id}/image
- resolve_image_url helper for image fallback chains
- image-upload web component for direct upload on detail pages
- Image display with vignette overlay and lightbox viewer
- Template macros: image_thumbnail, readonly_image, lightbox_script
- All 7 detail pages updated with image thumbnails
- Brew images fall back to roast; cup images fall back to cafe then roast
- Delete handler macro extended with optional image cleanup
2026-02-10 17:50:13 +00:00
Jon Seager
f6b51ccb38
refactor: reduce duplication in handlers, templates, and view structs
- Replace inline redirect scripts with existing render_redirect_script()
  in 6 create handlers
- Add delete_button macro to detail_cards.html, used by 6 detail pages
- Unify 3 map_with_legend macros into 1 with LegendEntry iteration
2026-02-10 09:37:49 +00:00
Jon Seager
08c1ef05cf
fix: use gear model column for brew card grinder display
The grinder_model field was computed by splitting the concatenated
"make model" string on the first space, which broke for multi-word
brand names like "Weber Workshops". Fetch g_grinder.model directly
from the database instead.
2026-02-09 19:57:09 +00:00
Jon Seager
da206c1938
feat(ui): add entity links to detail page cards
Add roaster and roast slug parameters to coffee_card and roaster_card
macros, rendering entity names as links to their detail pages. Add cafe
link in the cup detail page. Pass slug fields through template structs
and route handlers.
2026-02-09 19:56:47 +00:00
Jon Seager
000f20eab7
feat(server): auto-enable insecure cookies for localhost defaults
Default BREWLOG_RP_ID to "localhost" and BREWLOG_RP_ORIGIN to
"http://localhost:3000". When these localhost defaults are active,
insecure cookies are enabled automatically so local dev works without
setting BREWLOG_INSECURE_COOKIES. A warning is logged at startup when
insecure cookies are active.

The insecure_cookies flag is now threaded through AppState instead of
reading the env var at cookie-set time.
2026-02-09 17:23:09 +00:00
Jon Seager
a9fd069a3c
feat(timeline): add country flags to detail rows, allow multiple cards open
- Add flag emojis to Origin and Country detail rows in timeline events
- Change expanded-card signal from single-value to comma-separated list
  so multiple timeline cards can be open simultaneously
- Build subtitle before adding flags to keep condensed text clean
2026-02-08 18:04:31 +00:00
Jon Seager
639d748384
feat(lists): add flag emojis to country/origin columns
- Add country_flag to RoasterView and CafeView
- Add origin_flag to RoastView
- Display flag prefixes in roaster, cafe, and roast list partials
- Swap country/city column order in cafe list
2026-02-08 17:59:37 +00:00
Jon Seager
f0eb346086
feat(detail): add roast detail page, simplify lists and actions
- Add roast detail page at /roasters/{slug}/roasts/{slug}
- Remove expand/collapse detail rows from all 7 list views
- List rows now navigate directly to entity detail pages
- Replace three-dots action button with chevron-right link
- Add delete buttons to brew and cup detail pages
- Restyle all delete buttons: outlined with red text
- Remove share buttons from all detail pages
- Update timeline card links to point at detail pages
- Make homepage activity cards clickable with hover effect
- Replace all vanilla JS delete/close with Datastar actions
- Extract render_redirect_script helper for Datastar redirects
- Update delete macro with referer-based routing for detail pages
2026-02-08 17:48:49 +00:00
Jon Seager
958c8de6cc
feat(detail): add roaster, cafe, and gear detail pages
- Add detail page routes, templates, and view models for roasters, cafes,
  and gear, following the existing pattern from bags/brews/cups
- Add map_with_legend_1 macro for single-country detail maps
- Redirect to detail page after entity creation instead of list page
- Check referer in create handlers to return list fragment only from data page
- Update bag delete button to use red background style
- Update datastar tests to include referer header for create assertions
2026-02-08 17:20:46 +00:00
Jon Seager
4fd72d654a
feat(bags): add bag detail page, extract shared detail page macros
- Create /bags/:id detail page with coffee, roaster, map, and bag info cards
- Extract shared template macros into detail_cards.html (coffee_card, roaster_card, map_with_legend, share_button)
- Extract build_coffee_info() and build_roaster_info() view model helpers
- Refactor brew.html and cup.html to use shared macros
- Make bag cards on homepage clickable, linking to detail page
- Add Close Bag and Delete actions on bag detail page
- Unify homepage card styling (bg-surface, hover:border-accent/40)
- Update CLAUDE.md with detail page patterns
2026-02-08 16:43:04 +00:00
Jon Seager
9e5dff7f5f
feat(web): add Open Graph and Twitter Card meta tags
- Add og:title, og:description, og:image, og:type, og:site_name and
  twitter:card meta tags to base template with Askama block overrides
- Create static 1200x630 OG image served at /og-image.png
- Override OG blocks with entity-specific content on brew, cup, and
  stats detail pages
- Reuse BREWLOG_RP_ORIGIN as base URL for absolute og:image URLs via
  OnceLock initialized at server startup
2026-02-08 16:19:34 +00:00
Jon Seager
b17aaab5f4
feat(detail): add shareable brew and cup detail pages
Add /brews/:id and /cups/:id routes with full coffee, roaster, gear,
and recipe information. Each page includes a world map highlighting
relevant countries, tasting note pills, and a share button that copies
the URL to the clipboard.
2026-02-08 16:10:01 +00:00
Jon Seager
dbdb15a492
feat(home): add stats cards and horizontal scroll to data section
- Add build_stat_cards() with shuffled order from stats cache
- Remove gear from homepage data cards and StatsView
- Add StatCard view struct for icon/value/label display
- Convert data cards from grid to horizontal scroll with chip-scroll
- Add stats section with 6 randomized stat cards
2026-02-08 15:21:19 +00:00
Jon Seager
66c5cd0d6c
feat(stats): add full stats page with consumption, brewing, and charts
- Refactor stats handler to load_or_compute with cache fallback
- Add donut charts for brewer and grinder usage (with XSS-safe labels)
- Add bar charts for origins, flavours, grind weight, and brew time
- Add consumption cards (total brewed, avg per brew, bags consumed)
- Add roast summary section (top origins, processes, producers)
- Register donut-chart.js static asset route
- Add Recompute Stats button to admin page
2026-02-08 15:20:29 +00:00
Jon Seager
0b321068aa
refactor(domain): extract shared formatting helpers
- Add format_weight() and format_relative_time() in domain/formatting.rs
- Replace ad-hoc weight format strings across views and timeline events
- Move relative_date() body to domain layer with explicit now param
- Remove hardcoded "g" suffix from bag templates (now in format_weight)
- Document formatting helpers in CLAUDE.md
2026-02-08 15:14:39 +00:00
Jon Seager
7f99ffc993
feat(stats): add geographic stats page with choropleth world map
- New /stats page with Roasters, Roasts, Cups, and Cafes tabs
- Interactive choropleth world map (SVG) colored by country counts
- Clickable country chips that highlight individual countries on the map
- Horizontal chip scroller with chevron navigation on desktop
- Datastar-powered tab switching without page reload
- Domain layer: country name → ISO code mapping, flag emoji generation
- StatsRepository trait with four aggregate SQL queries
- <world-map> and <chip-scroll> custom elements for Datastar compatibility
2026-02-08 11:03:15 +00:00
Jon Seager
ba319e3ab7
copy: remove second-person pronouns, use imperative tone
Replace "you/your" with impersonal alternatives across all
user-facing strings in templates, CLI output, error messages,
and documentation. Add copy style rule to CLAUDE.md.
2026-02-08 09:50:49 +00:00
Jon Seager
3f672d7c06
chore: fix lints 2026-02-08 09:40:37 +00:00
Jon Seager
ce4f32c63f
feat(icons): add icon macros, remap entity types, add icons to tabs and timeline
- Add new icon macros: coffee_bean, grinder, cup (redesigned), pencil,
  clipboard, check, x_mark, arrow_down_tray, arrow_up_tray
- Remove orphaned building and wrench macros
- Remap entity icons: roast→coffee_bean, roaster→fire, gear→grinder
- Replace pill labels with entity-type icons in homepage activity rows
- Add entity-type icons to timeline cards and tab buttons
- Fix brew/bag card action icons (cup→beaker)
- Add entity_type field to TimelineEventView, add roaster detail picks
2026-02-07 22:36:48 +00:00
Jon Seager
df4469ff69
feat(brews): make Brew Again navigate to pre-filled add form
Instead of immediately creating a duplicate brew via inline POST,
the Brew Again button now links to the add-brew page with query
params that pre-fill all fields (bag, grinder, grind setting,
brewer, filter paper, water volume/temp, brew time, quick notes).
This lets users review and adjust parameters before brewing.
2026-02-07 19:57:23 +00:00
Jon Seager
51f8122201
feat(ui): improve form UX with collapsible equipment and clickable controls
- Split equipment into separate Grinder and Brewer collapsible sections
  with summary bars showing current selection
- Make all summary bars, matched-entity cards, and searchable-select
  selected values clickable anywhere to change (not just the X/Change button)
- Fix stepper buttons broken after manual input by switching from
  data-attr:value to data-bind with Number() coercion
- Merge Water + Brew Time into single Recipe section
- Make all add/confirm buttons full-width
- Add sticky submit button on mobile
- Fix missing closing > on cafe indicator div
- Fix broken Askama syntax on checkin page from formatter
2026-02-07 12:10:56 +00:00
Jon Seager
902d52b171
feat(brews): add brew time field
- Add brew_time INTEGER column to brews table (nullable, stored as seconds)
- Display as M:SS format with stepper control (+/-5s) in add form
- Replace ratio with brew time in brew list Recipe column
- Show brew time in brew cards and timeline events
- Add --brew-time CLI flag and update bootstrap script (1:45-2:45 range)
2026-02-07 11:24:07 +00:00
Jon Seager
577c0e39d3
feat(ui): add horizontal scrolling recent brews on homepage
- Load last 10 brews instead of 3, display in horizontal scroll
- Extract brew card into reusable partial with portrait layout
- Add grinder_model field to BrewView for compact card display
- Move brew-again button to top-right icon, truncate long names
2026-02-07 10:40:20 +00:00
Jon Seager
55e549ceac
feat(tasting-notes): add Levenshtein fuzzy matching for typo tolerance
Add third-tier fuzzy matching using edit distance to catch common typos
and spelling variants in tasting notes (e.g. "cinamon", "smokey").
2026-02-07 10:33:05 +00:00
Jon Seager
e01b6d0a1c
feat: add optional created_at to entity creation and updates
Add `created_at: Option<DateTime<Utc>>` through all layers so CLI users
can backdate entities at creation/update time. When omitted, falls back
to `Utc::now()`.

- Domain: add field to all New*/Update* structs with serde(default)
- Domain: timeline events use entity created_at instead of Utc::now()
- Repos: unify INSERT to explicit Rust-side created_at with unwrap_or_else
- Repos: add created_at to UPDATE dynamic query builders
- Routes: add field to submission structs and has_changes guards
- Clients: pass created_at through manual JSON client methods
- CLI: add --created-at flag with parse_created_at helper (RFC 3339 or YYYY-MM-DD)
2026-02-07 09:48:20 +00:00
Jon Seager
531b58e8e6
feat(tasting-notes): add "creamy" to vegetal flavour category 2026-02-06 18:51:28 +00:00
Jon Seager
44174b4361
feat(ui): colour-code tasting note pills by SCA wheel category
Add view-layer mapping that categorises tasting notes into 9 colour
families (floral, fruity, citrus, sweet, nutty, spice, roasted, sour,
vegetal) based on the SCA Coffee Tasting Wheel. Matching uses
case-insensitive exact lookup against ~90 known terms, then substring
fallback, with pill-muted as the default for unrecognised notes.

- Add tasting_notes module with NoteCategory enum and categorize()
- Add 9 pill CSS variants with light and dark mode support
- Update roast list and timeline templates to render coloured pills
2026-02-06 16:26:29 +00:00
Jon Seager
dcaac1ee9c
feat(ui): add usage progress bars to bag cards and data table
- Add used_percent field to BagView computed from amount/remaining
- Display accent-colored progress bar on homepage bag cards
- Replace Open pill with progress bar in bag data table (desktop and mobile)
- Merge Weight column into Status column showing remaining/total
- Add full-width progress bar to mobile card view for open bags
- Sort Status column by remaining grams (closed bags treated as 10000g)
- Hide Finished field on mobile for open bags
- Use small-caps typography for remaining text labels
2026-02-06 16:08:41 +00:00
Jon Seager
3a960b9d14
feat(ui): enhance desktop tables and mobile card views
- Add spacious flat table CSS for desktop (transparent card wrapper,
  lighter dividers, visible hover, larger cell padding)
- Split roaster/cafe columns into separate Country and City columns
  with sortable headers
- Split cup Roast/Roaster into separate desktop columns with city
  sort support (full-stack: domain, repo, view, template)
- Add sortable Status column to bags with colored pills (green Open,
  amber Closed) and remaining weight subtext
- Style bag Finished column as date-only (NaiveDate) and Weight
  column with smaller text
- Show status pill next to mobile bag card actions menu
- Add city to cups mobile card view
- Use middle dot separator for brew recipe weight/volume
- Add tasting notes pills to roast list, brew notes to brew list
- Fix build.rs to use rerun-if-changed for CSS and templates
2026-02-06 15:40:40 +00:00
Jon Seager
4a7b054de1
feat(brews): pre-select bag when clicking Brew on homepage bag card
- Add initial-value attribute to searchable-select web component
- Update bag card Brew link to pass bag_id query parameter to add page
- Thread bag_id through route handler and template to pre-select the bag
2026-02-06 10:53:42 +00:00
Jon Seager
8777ceb25b
fix(brews): carry over quick notes when using "Brew Again"
- Add quick_notes_raw field to BrewView with comma-separated form values
- Include quick_notes hidden input in all three brew-again forms
  (homepage, brew list desktop, brew list mobile)
2026-02-05 21:18:11 +00:00
Jon Seager
010b700595
feat(brews): add quick notes
Predefined toggleable tags for brews: Good, Too Fast, Too Slow,
Too Hot, Under Extracted, Over Extracted.

- Add quick_notes TEXT column storing JSON array
- Add QuickNote enum with label/form-value/positive helpers
- Update repository to encode/decode JSON, backup to export notes
- Add toggle pill UI on brew form with Datastar signals
- Show pills on home page cards (muted for Good, amber for others)
- Show comma-joined text in data table Notes column
- Add --quick-notes flag to CLI, update HTTP client
- Update bootstrap script with sample quick notes
2026-02-05 20:42:03 +00:00