Commit graph

100 commits

Author SHA1 Message Date
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
Jon Seager
3bc6fae656
refactor(ui): improve data table detail rows and mobile cards
- Split created_at into created_date + created_time across all view models
- Add inline card-detail pattern for mobile action buttons
- Add desktop detail-row with ellipsis toggle for table actions
- Add pill-warning and pill-success CSS variants with dark mode
- Center mobile card-detail buttons, fix card-date vertical spacing
- Rename action labels: "View on Map" → "View Map", "Visit website/homepage" → "Homepage"
2026-02-05 20:40:38 +00:00
Jon Seager
56109f3afe
refactor: reorganize templates into pages/, partials/lists/, and forms/
Move 9 page templates to templates/pages/, nav to partials/,
7 list partials + table.html to partials/lists/. Extract duplicated
scan result form (~85 lines) from home.html and add.html into
partials/forms/scan_result_form.html. Update all Askama template
paths, include/import directives.
2026-02-05 17:48:18 +00:00
Jon Seager
4a54834424
refactor(ui): extract reusable tab bar component
- Create shared tab_bar.html partial with desktop + mobile layouts
- Add CSS component classes (.tab, .tab-active, .tab-mobile variants)
- Replace divergent implementations in data.html and add.html
- Rename DataTab to Tab for shared use across templates
- Improve readability: darker inactive text, more spacing, hover states
2026-02-05 17:23:50 +00:00
Jon Seager
fb67f0659c
feat(ui): redesign with theme tokens, dark mode, and reusable components
Overhaul the web UI with CSS custom property-based theming and
dark mode support. Extract reusable template partials (location
search, scan input) and web components (photo-capture,
searchable-select). Add version and commit info to page footer.

- Replace hardcoded amber palette with semantic theme tokens
- Add dark mode with localStorage persistence and system preference detection
- Extract <brew-photo-capture> and <searchable-select> web components
- Extract location_search and scan_input Askama macros
- Add sun/moon, timeline, database, map icons
- Display version and git commit in footer
- Improve timeline coordinate rounding and map link formatting
- Add roast_name, roaster_name, remaining to BagOptionView
2026-02-05 17:03:09 +00:00
Jon Seager
6d12b71482
refactor(cups): remove rating field
Drop the rating column from cups. This removes the field from the
domain model, repository, API routes (including the PUT/update
endpoint), CLI, and all related tests.
2026-02-05 17:02:42 +00:00
Jon Seager
03e03d87d9
feat(auth): replace password auth with WebAuthn passkeys
Replace username/password authentication with FIDO2/WebAuthn passkey-based
auth using webauthn-rs. Sessions and bearer tokens are unchanged — only the
way they are created changes.

- Add webauthn-rs, uuid, open, url deps; remove argon2, rpassword
- Add passkey_credentials and registration_tokens tables (migrations 17-18)
- Add domain entities, typed IDs, and repository traits for passkeys/tokens
- Add SQL repository implementations for passkeys and registration tokens
- Add ChallengeStore for in-memory WebAuthn ceremony state
- Add WebAuthn route handlers (register/auth start+finish ceremonies)
- Add CLI browser handoff for token creation (opens browser, local callback)
- Replace login form with "Sign in with Passkey" button
- Add registration page for first-user bootstrap via one-time token
- Replace BREWLOG_ADMIN_USERNAME/PASSWORD with BREWLOG_RP_ID/RP_ORIGIN
- Change default BREWLOG_URL from 127.0.0.1 to localhost (WebAuthn requires it)
2026-02-05 11:00:07 +00:00
Jon Seager
8c521f0e05
feat(data): redesign mobile data page with card layout and tab selector
- Add collapsible mobile tab selector with chevron icons
- Integrate search bar into tab component (below tabs)
- Replace Datastar search handler with vanilla JS for reliability
- Add mobile card layout: card-title, card-date, card-actions classes
- Add three-dot action menu for mobile with details/summary pattern
- Hide duplicate search inside data-content via CSS
- Pass search_value to DataTemplate for input preservation
2026-02-04 22:22:18 +00:00
Jon Seager
2eca0ae3c6
refactor(bags): replace dual-section bag list with single paginated table
Remove the open-bags card grid and closed-bags history table in favour
of a unified table showing all bags with status badges and actions.

- Simplify load_bag_page to single BagFilter::all() query
- Remove open_bags field from BagPageData and BagListTemplate
- Add Status column with Open/Closed badges and remaining weight
- Add Close and Delete action buttons per row
2026-02-04 20:53:12 +00:00
Jon Seager
4bdfc661f9
refactor: consolidate entity pages into unified /data and /add views
Replace per-entity pages (/roasters, /roasts, /bags, /brews, /gear,
/cafes, /cups) and detail pages with a single tabbed /data view and
a dedicated /add page for entity creation.

- Add /data route with tab-based navigation using Datastar
- Add /add route consolidating all create forms
- Remove per-entity page handlers and standalone templates
- Remove detail page routes, handlers, and templates
- Update ListNavigator to accept String paths for query-param URLs
- Update home page and timeline links to use new /data?type=X paths
- Update nav to reference /data instead of individual entity pages
2026-02-04 20:51:30 +00:00
Jon Seager
50a372015c
feat(backup): expose backup/restore via authenticated API endpoints
- Add GET /api/v1/backup and POST /api/v1/backup/restore endpoints
  behind AuthenticatedUser
- Add BackupService to AppState and BackupClient for HTTP access
- Update CLI backup/restore to use API instead of direct DB access
- Remove --database-url flag from backup and restore commands
- Increase body limit to 50MB for restore endpoint
- Add API and CLI tests for auth, export, restore, and round-trip
- Update README to document auth requirement and API endpoints
2026-02-04 19:38:49 +00:00
Jon Seager
47c89bd725
refactor: simplify overly nested and duplicated code across five modules
- Extract 35-line filter_map closure into parse_cafe() in foursquare.rs
- Unify triple-duplicated search branching in pagination.rs into
  fetch_records() and fetch_count() helpers
- DRY three identical gear-loading blocks in brews.rs via
  load_gear_options() helper
- Flatten nested if/else-if in timeline map_details() using match,
  extract shared is_blank() helper
- Deduplicate JSON decode pattern in backup.rs with decode_json_vec()
  and decode_json_opt() helpers
2026-02-04 19:25:35 +00:00