- 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
- 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)
- 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
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)
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
- 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
- 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
- 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
- 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)
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
- 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"
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.
- 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
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
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.
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)
- 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
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
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
- 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
- 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
Show contextual details inline with event titles on home page timeline
cards. Each event type picks relevant fields: brew shows roaster and
brewer, roast shows roaster and origin, cafe shows city and country,
cup shows roaster and cafe, others show first three detail values.
- Replace single "Last Brew" with grid of 3 recent brew cards
- Show brew details with emphasis on weight/volume, grind, and brewer
- Add "Brew Again" action matching bag card button style
- Replace quick-action buttons with always-visible "Scan Bag" section
- Fix activity chip wrapping by widening to w-28 with whitespace-nowrap
- Increase section gap from gap-6 to gap-8 for better visual spacing
- Remove has_ai_extract/has_foursquare conditionals from all templates
- Remove boolean fields from template structs and route handlers
- Remove has_ai_extract()/has_foursquare() methods from AppState
- Change API key fields from Option<String> to String in ServerConfig
and AppState
- Validate keys in run_server() with clear error messages
- Remove runtime key checks from extraction/nearby route handlers
- Update README and CLAUDE.md to reflect required configuration
- Add roast name and roaster name signals to check-in page
- Show selected cafe city as subtext in cafe summary bar
- Add roast summary bar showing roast name and roaster after selection
- Use data attributes on select options for structured cafe/roast data
- Add name/city fields to CafeOptionView and name/roaster_name to RoastOptionView
- Return roaster name signal from scan endpoint for roast summary display
- Add POST /api/v1/check-in endpoint with CheckInSubmission
- nearby_cafes returns HTML fragment for Datastar, JSON for API
- Add NearbyCafeView and NearbyCafesFragment for server-rendered results
- Rewrite checkin.html: replace checkin.js with Datastar signals,
@post for submission, @get for nearby search, inline geolocation JS
- Cafe filtering uses data-show with signal-based search
- Add home page at / with scan, last brew, open bags, activity, stats
- Add check-in page at /check-in with cafe search + roast scan + rating
- Use Datastar signals for check-in UI state (steps, rating, selection)
- Bridge async JS (geolocation, fetch) to Datastar via custom events
- Replace nav camera icon with house icon (always visible)
- Redirect /scan to / for backward compatibility
- Delete scan.html, add home.html, checkin.html, checkin.js
- Add migration 0016 to drop notes column from roasters table
- Remove notes from domain structs, repositories, views, CLI, routes
- Remove notes from AI extraction prompts and ExtractedRoaster struct
- Remove notes from scan page form and submission handler
- Update templates, bootstrap script, backup module, and tests
- Add migration 0014 to drop the notes column from cafes table
- Remove notes from domain structs, repository, view model, CLI, routes
- Remove notes textarea/column from web templates
- Update bootstrap script and all test files
- Replace osm.rs with foursquare.rs using Foursquare Places Search API
- Add isocountry crate for ISO 3166-1 country code to name conversion
- Override verbose country names (e.g. "United Kingdom" instead of
"United Kingdom of Great Britain and Northern Ireland")
- Add BREWLOG_FOURSQUARE_API_KEY env var for API authentication
- Update route handler, tests, and template to use Foursquare
- Modernise cafes template JS to ES6+ (const/let, arrow fns, template
literals)
Users can upload a photo of a coffee bag or type a text prompt to
auto-fill roaster/roast form fields via OpenRouter's chat completions
API. The feature is opt-in: hidden when BREWLOG_OPENROUTER_API_KEY is
not set.
- Add src/infrastructure/ai.rs with OpenRouter client, prompt
templates, JSON extraction from model responses, and unit tests
- Add extract-roaster and extract-roast API endpoints behind auth
- Add extraction UI (photo button + text input) to both form templates
- Add openrouter_api_key and openrouter_model to CLI args and AppState
- Enable reqwest gzip feature for compressed API responses
- Default model set to openrouter/free (auto-selects available models)
- Prompts instruct model to use web search for missing details
- Move entity view models into views/{entity}.rs submodules
- Keep Paginated and ListNavigator in views/mod.rs as shared infrastructure
- Re-export all types from mod.rs to preserve existing import paths
Extract the detail-mapping loop from TimelineEventView::from_domain
into a private map_details helper method, bringing the function under
clippy's 100-line limit.
- Add CupView, CafeOptionView, RoastOptionView view models
- Add CupsTemplate (full page) and CupListTemplate (fragment)
- Create cups.html with form and cup_list.html with responsive table
- Add Cups link to desktop and mobile navigation
- Add cup timeline event link and label support
Position is already shown as a clickable detail row in the card body,
making the title icon redundant. Also removes the unused map_link field
from TimelineEventView.
Add CLI subcommands (add, list, get, update, delete) with negative
number support for coordinates. Add CafeView with map URL generation,
Askama templates for list/detail pages, and nav link between Brews
and Gear.
- Add BrewDefaultsView to carry last brew's values to the template
- Extract form data loading into load_brew_form_data() helper
- Use defaults for signal initialization and dropdown pre-selection
- Retain submitted values after form submission instead of resetting
Add q parameter to ListQuery with into_request_and_search(). Extend
ListNavigator with search state, search_value(), has_search(), path(),
and search_query_base() for template URL generation. Thread search
through all route handlers and update backup tests.
- Add BackupData struct and BackupService with raw SQL export/import
- Restore uses raw inserts to bypass brew deductions and timeline creation
- Restore requires an empty database, inserts in FK dependency order
- Add comprehensive e2e test verifying full round-trip fidelity
- Add filter paper dropdown to brew form and gear category form
- Add custom deserializer for optional GearId from HTML forms
- Load filter papers in brews_page handler
- Show filter paper as subtext under brewer in brew list
- Add filter paper to timeline event details and brew-again forms
- Add --filter-paper-id flag to CLI brew add command
- Add TimelineBrewData struct to domain layer to carry raw brew
parameters needed for repeating a brew
- Extend timeline SQL query to LEFT JOIN with brews table and fetch
brew data (bag_id, grinder_id, brewer_id, coffee_weight, etc.)
- Add brew_data field to TimelineEventView for template access
- Add is_authenticated to TimelineChunkTemplate for auth-aware chunks
- Add "brew again" button to timeline month partial, visible only for
authenticated users on brew events
- Server returns reload script when brew is created from timeline page,
using Datastar's fragment patching to append script to body
- Combine weight/water/ratio into single "Recipe" column
- Reorder columns: Coffee, Grind, Recipe, Temp, Brewer, Date, Actions
- Style actions column with icon buttons (consistent with roasts)
- Add "brew again" button to repeat a brew with same parameters
- Use data-model for form inputs (fixes Datastar binding error)
- Add raw values to BrewView for repeat brew functionality
- Add BrewView, BagOptionView, GearOptionView
- Add brews page with form and +/- adjustment buttons
- Add brew list partial with sortable columns
- Add Brews link to navigation