Cover text fields, numeric fields, +/- adjuster buttons, searchable
selects, image upload/replace/remove, deferred image save, and the
cancel button across roaster, gear, cafe, brew, and cup entities.
Add wait_for_url_not_contains helper to correctly detect post-submit
redirects when the edit page URL shares a prefix with the detail page.
- 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
Add {contentType: 'form'} to all edit template @put() calls so form
inputs are submitted by name rather than as Datastar signals. Replace
the brew edit quick notes text input with toggle pill buttons. Recompute
bag remaining when amount is updated to account for brew deductions.
Add CLI tests for brew and cup update commands.
Add {contentType: 'form'} to all edit template @put() calls so form
inputs are submitted by name rather than as Datastar signals. Replace
the quick notes text input on the brew edit page with toggle pill
buttons matching the add form. Add CLI tests for brew and cup update
commands.
Add form-based PUT update tests via define_form_update_tests macro for all
7 entities. Add hand-written datastar update tests for roasters, roasts,
cafes, brews, and cups. Add brew/cup update API tests (CRUD + auth).
Add put_form, put_form_datastar, create_default_brew, create_default_cup
test helpers.
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.
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.
Add UpdateBrewSubmission and UpdateCupSubmission types with image
support. Register PUT handlers on /brews/{id} and /cups/{id} with
three-way response pattern (Datastar/form/JSON).
All five existing update handlers (roaster, roast, cafe, gear, bag) now
accept FlexiblePayload with UpdateSubmission types that separate image
data from domain structs. Each returns Datastar redirect scripts, form
redirects, or JSON depending on request type. Image save support added
to all update paths.
Add domain update structs for brew and cup entities, add update()
methods to their repository traits and SQL implementations, and
expand UpdateBag with roast_id, roast_date, and amount fields.
Log at info level when a deferred image is successfully saved, matching
the project convention that every create/update operation logs with
entity context.
Limit concurrent image processing tasks to 4 via a tokio::Semaphore on
AppState. Acquired before spawn_blocking in both upload_image() and
save_deferred_image() to prevent CPU/memory exhaustion from concurrent
image uploads.
Set image decoder limits (10000x10000 max dimensions, 256MB max alloc)
to prevent crafted images from causing OOM via decompression bombs.
Validate data URL MIME types against an allowlist (jpeg/png/webp)
before decoding, rejecting non-image content types early.
CPU-intensive image operations (decode, Lanczos3 resize, JPEG encode)
were running directly on the async worker thread, blocking all other
requests for 100-500ms per upload. Wrap in spawn_blocking in both
upload_image() and save_deferred_image().
Images were not included in the backup/restore system, so a backup
would lose all uploaded images and a reset would orphan them.
Add BackupImage with base64-encoded binary data to BackupData,
export/restore/reset the entity_images table, and verify it in
the empty-database check. Backward-compatible with existing v2
backups via #[serde(default)].
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
Add 3 homepage scan e2e tests covering all roaster/roast match
permutations (new+new, existing+new, existing+existing). Fix
StaleElementReference in checkin test by using JS clicks for
Datastar-managed DOM elements.
Cover the check-in flow where neither the cafe nor the roast exist
beforehand. The test searches for a cafe via location search (mocked
Foursquare), scans a coffee bag via text prompt (mocked OpenRouter),
and submits the check-in. Adds spawn_app_with_all_mocks helper.
Cover tab switching, search, pagination, and sorting on the data list
page. Verify detail page rendering for roasters, roasts, bags, gear,
and cafes. Test the close-bag flow from the detail page. Check that
home, stats, and timeline pages load correctly with data.
Roaster creation (basic + all fields), full brew chain through browser,
entity deletion with confirm dialog, AI extraction via text prompt with
mocked OpenRouter, and check-in wizard with saved cafe + existing roast.
BrowserSession wraps headless Chrome via thirtyfour. Auth helper injects
session cookies to bypass WebAuthn. Wait helpers handle Datastar's async
DOM updates (visibility, text, URL). Form helpers find visible elements
to avoid hidden duplicates on tabbed pages. Chromedriver is auto-spawned
on first use via a dedicated parked thread with PR_SET_PDEATHSIG so the
kernel kills it when the test binary exits.
Add thirtyfour WebDriver client as dev-dependency, feature-gated behind
--features e2e so normal cargo test is unaffected. Add chromedriver and
chromium to the nix devShell.
- 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 PayloadSource::Form branch (redirect response) and form-specific
Submission type parsing were previously untested. Add 19 tests covering
form-encoded POST for all 8 entities, including Datastar+form combo and
edge cases for custom deserializers (TastingNotesInput, optional gear ID,
comma-separated quick notes, date string parsing).
PRAGMAs executed on the pool only applied to one random connection out
of five. Move all settings to SqliteConnectOptions so every connection
gets them. Reduce max_connections to 1 to eliminate cross-connection WAL
snapshot staleness that caused 404s on newly created entities.
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.
The Save Roaster & Roast button text was wrapping awkwardly on narrow
screens. Buttons now stack full-width on mobile and revert to inline
layout on sm: breakpoint.
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.
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.
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.
Move locateUser and nearbyKeydown functions from an Askama macro into
static/js/location.js served as a deferred script. Add arrow key, Enter,
and Escape navigation for nearby cafe search results.
Add ARIA role=img with descriptive labels to world-map SVG and
donut-chart SVG. Include a screen-reader-only data table in world-map
for country/count pairs.
Add role=combobox, aria-expanded, aria-controls, and role=listbox/option
attributes. Support arrow key navigation, Enter to select, and Escape
to dismiss. Add aria-label to clear button.
Add error and success color tokens with dark mode support, increase
btn-adjust touch target to 44px on mobile, and add .ss-active highlight
class for keyboard navigation in searchable components.
Restructure the quick start to lead with Docker, add install-from-git
as a secondary option, update the config table to reflect the new
localhost defaults and auto-enabled insecure cookies, and consolidate
the Docker section into the quick start.
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.