Add deferred image upload support to entity creation flows:
- Roaster, roast, gear, cafe forms save images on create via
save_deferred_image helper
- Brew form accepts optional image upload
- Check-in form accepts optional cup photo
- Scan flow preserves captured image for new roasts and skips
overwriting existing roast images
- Homepage updated with image-upload component registration
- Delete handlers for brews and cups clean up associated images
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
- 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
The flag emoji and country name were separate flex items inside mobile
card td cells, causing the flag to float away from the country text.
Wrapping them in a single span keeps them together as one flex item.
Replace verbose subtitles with a consistent pattern across all 7 detail
pages: a linked entity type pointing to the data list page, followed by
the creation date.
Replace viewport-relative w-[45vw] with fixed w-[200px] so brew and
bag cards maintain consistent width across breakpoints instead of
shrinking on narrow screens.
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.
Add aria-required to required inputs, role=alert to error messages,
aria-label to icon-only list nav buttons, and progressbar ARIA to bag
status bars. Replace hardcoded red/green colors with error/success
design tokens across all templates. Add toast notifications for entity
creation and tab-switch links for empty add-form states. Increase scan
upload body limit to 10MB.
The stats recomputation task populates the cache with zero values on
startup, causing build_stat_cards() to return 6 cards even with an
empty database. Gate the stats section on stats.is_empty() in addition
to stat_cards.is_empty() so placeholders display correctly.
- 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
- 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
- Add bg-surface background to roast stats, consumption, and country pill cards
- Update tab CSS: bg-surface background, hover:border-accent/40 instead of bg-surface-alt
- Add hover:border-accent/40 to clickable country pill buttons
- Make Roasts the first and default tab on the stats page
- 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
- 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
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.
- 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
Extract <chip-scroll> custom element into its own file and wrap
Recent Brews and Open Bags horizontal scrollers with desktop-only
chevron navigation that half-overlaps the card edges.
- 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
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.
- Add chevron_right icon macro
- Add icon to checkin "Next" and register "Go to Brewlog" buttons
- Align Back (checkin) and Cancel (scan_result_form) to Outlined
style: inline-flex, font-medium
- Rename "Log Brew" → "Save Brew" for consistency with other forms
- Replace icon-only delete/revoke buttons with outlined text buttons
- Rename: Add Passkey → New Passkey, Create Token → New Token,
Download Backup → New Backup, Restore from Backup → Restore,
Reset Database → Reset
- Add w-full sm:w-auto sm:min-w-44 to all action buttons for
consistent sizing on mobile and desktop
- Align Cancel buttons to font-medium text-text (Outlined style)
- Show blurred skeleton cards with overlay text for empty brews,
bags, and activity sections instead of plain dashed borders
- Use 6 skeleton cards for brew/bag sections to fill desktop width
- Skeleton structure mirrors real card layout (title, details, buttons)
- Wrap tab bar, search, and table in gap-4 container for tighter spacing
- Change search placeholder from "Search..." to "Filter..."
- Remove border/padding wrapper around tab bar and search
- Remove check-in and add links from scan input partial
- Add "or" separator between camera and text input
- Remove redundant text-sm from input fields (now in .input-field CSS)
- Fix label text size and color in check-in form
- Remove top-level scan section and scan_input import
- Add per-form extraction bars with camera + describe input
- Rename form signals from _add-* prefix to match extraction endpoints
- Add per-form extracting/error signals to guard datastar-fetch events
- Make Log Brew button full width
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.
- 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
- Hide native number input spin buttons across browsers
- Fix brew time field width to match sibling fields
- Restyle .btn-adjust with accent-subtle fill instead of border
- Remove (M:SS) hint from brew time label
- Replace token dismiss button with close icon in top-right corner
- Add terminal icon to token rows matching passkey layout
- Remove token row from DOM immediately on revoke
- Focus token name input when opening the create form
- Clear token name input after successful creation
- 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)
- Add "Expand all" / "Collapse all" link under each month heading
- Individual cards remain toggleable even when month is expanded
- Three signals coordinate state: _expandedMonths (month-level),
_expandedCard (single card), _collapsedCards (individual overrides)
Cards start collapsed showing category, relative date, title, and
subtitle. Clicking expands to reveal full timestamp, detail rows,
tasting notes, and external links. Uses a single Datastar signal
for expand state.
- Replace pill badge with small-caps category label
- Remove brew-again button from timeline cards
- Replace @ separators with · in brew detail values
- 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
- Load all open bags instead of limiting to 3
- Replace grid layout with horizontal scroll container using snap
- Redesign bag cards as portrait layout with icon actions
- Add scrollbar-hide CSS utility and x_mark icon