FastHTML omits empty string attributes (value=""), causing browsers to
submit the option's text content "Same as hatch location" instead of an
empty value. This resulted in a ULID validation error.
Use "__none__" as a sentinel value that the server converts to None.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
POST routes were returning HTMLResponse(content=to_xml(...)) which
bypassed FastHTML's toast middleware. The middleware only injects
toasts for tuple, FT, or FtResponse responses.
Changed 12 routes to return render_page() directly:
- actions.py: 7 routes (cohort, hatch, tag-add, tag-end, attrs, outcome, status-correct)
- eggs.py: 2 routes (product-collected, product-sold)
- feed.py: 2 routes (feed-given, feed-purchased)
- move.py: 1 route (animal-move)
Updated tests to check for toast content in response body instead of
session cookie, since middleware now renders toasts inline.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Two bugs fixed in animal selection with checkboxes:
1. Confirmation dialog showed wrong count (e.g., "35 animals" instead of
"2 animals" when only 2 were selected). Fixed by using valid_selected
count in diff.server_count instead of full filter resolution count.
2. Spurious "Selection Changed" dialogs due to race condition in async
hash computation. Fixed by removing client-side hash computation
entirely - it was unnecessary since the server validates selected_ids
directly against the filter resolution.
Changes:
- validation.py: Remove hash comparison in _validate_subset(), validate
IDs directly, fix server_count in diff
- animal_select.py: Remove computeSelectionHash(), hidden roster_hash
field, and related async fetch code
- test_selection_validation.py: Add tests for subset mode validation
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add 409 to HTMX responseHandling config so selection conflict
dialogs are displayed instead of silently ignored
- Fix event type dropdown using value="" which caused browsers to
send display text "All types" instead of empty string
- Use value="all" for "All types" option (matching location selector)
- Handle event_type="all" in route as no filter
- Remove dead code (location_name lookup duplicated in template)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add tooltips with location ID on hover for location names in event detail
- Make location names clickable links to /locations/{id} detail page
- Create location detail page showing location info, live animal count,
and recent events at that location
- Add public GET /locations/{id} route (existing admin routes unchanged)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The CLI command only needs DB_PATH for database operations, not web
settings like csrf_secret. Read DB_PATH directly from environment.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add 'rebuild-projections' CLI command that truncates all projection
tables and replays non-tombstoned events to rebuild state
- Fix event delete route to register all projections before calling
delete_event, ensuring projections are properly reverted
- Add comprehensive tests for both rebuild CLI and delete with projections
The rebuild-projections command is useful for recovering from corrupted
projection state, while the delete fix ensures future deletes properly
revert animal status (e.g., sold -> alive).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
app.state.registry was never set - create ProjectionRegistry()
locally like all other routes do.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Fix UserRole.admin to UserRole.ADMIN in events.py delete route
- Add content-type check before parsing JSON in event delete handler
- Add error handling and content-type check in animal selection hash computation
- Audited codebase: no other enum case issues found
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Fix event log white background to use dark theme (bg-[#141413])
- Fix UserRole.admin typo to UserRole.ADMIN in event_detail.py
- Add global exception handler that logs errors and shows toast
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Fix checkbox selection not working: remove duplicate subset_mode hidden
fields from 5 form templates and add resolved_ids to checkbox component
- Add all-events view to event log (shows events without location like
AnimalOutcome, FeedPurchased, ProductSold)
- Add event type filter dropdown alongside location filter
- Make event log items clickable to open event detail slide-over
- Add event delete UI with confirmation dialog (admin only)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
When users type a filter, HTMX now fetches the matching animals and
displays a checkbox list for subset selection. Changes:
- Add hx_get="/api/selection-preview" to filter inputs in all forms
- Wrap selection component in #selection-container for HTMX targeting
- Add subset_mode hidden field to checkbox list component
- Handle single-animal case with simple count display (no checkboxes)
Forms updated: outcome, tag-add, tag-end, attrs, move
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Three major features implemented:
1. Event Detail Slide-Over Panel
- Click timeline events to view details in slide-over
- New /events/{event_id} route and event_detail.py template
- Type-specific payload rendering for all event types
2. Toast System Refactor
- Switch from custom addEventListener to FastHTML's add_toast()
- Replace HX-Trigger headers with session-based toasts
- Add event links in toast messages
- Replace addEventListener with hx_on_* in templates
3. Checkbox Selection for Animal Subsets
- New animal_select.py component with checkbox list
- New /api/compute-hash and /api/selection-preview endpoints
- Add subset_mode support to SelectionContext validation
- Update 5 forms: outcome, move, tag-add, tag-end, attrs
- Users can select specific animals from filtered results
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Python's sqlite3.executescript() has a bug where trailing newlines after
the final semicolon create empty statements. When APSW's log_sqlite() is
enabled (via apswutils, imported by fastmigrate), these cause visible
"API called with NULL prepared statement" errors during interpreter shutdown.
- Strip trailing newlines from all 9 existing migration files
- Update migration template to end with semicolon, no trailing newline
- Document the requirement in CLAUDE.md
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Added optional notes field to forms that support it in their payloads
but were missing the UI input:
- cohort_form (Create Animal Cohort)
- hatch_form (Record Hatch)
- promote_form (Promote Animal)
- tag_add_form (Add Tag)
- tag_end_form (End Tag)
- attrs_form (Update Attributes)
- move_form (Move Animals)
Uses consistent LabelTextArea pattern matching existing forms like
outcome_form and status_correct_form.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Create render_page() wrapper that auto-extracts auth from request
for page() calls. This eliminates manual username/user_role passing
and ensures consistent auth handling across all routes.
Updated all route files to use render_page():
- actions.py, eggs.py, feed.py, move.py, products.py
- animals.py, events.py, locations.py, registry.py
This fixes "Guest" username display on forms by ensuring auth
is always extracted from request.scope.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The event_datetime_field component now accepts initial_value and
initial_ts parameters. When re-rendering a form after validation
error, the datetime picker value is preserved and shown expanded.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The validation now checks that select values are in the allowed set,
not just non-empty. This catches cases where browsers send placeholder
text (e.g., "Select location...") instead of empty string for disabled
options.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Configure HTMX to swap 422 responses so validation errors display
- Add missing name="notes" to egg harvest and product sold forms
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add ability to specify custom date/time when recording events,
enabling historical data entry. Forms show "Now - Set custom date"
with a collapsible datetime picker that converts to milliseconds.
- Add event_datetime_field() component in templates/actions.py
- Add datetime picker to all event forms (cohort, hatch, outcome,
tag, attrs, move, feed)
- Add _parse_ts_utc() helper to parse form timestamp or use current
- Add tests for backdating functionality
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Form actions were rendering as function repr strings (e.g.,
"<function product_collected at 0x...>") instead of route paths
because the register_*_routes() pattern didn't attach .to() method
to handler functions.
Migrated all route modules to use FastHTML's APIRouter pattern:
- Routes decorated with @ar("/path") get .to() method attached
- Form(action=handler) now correctly resolves to route path
- Removed register_*_routes() functions in favor of router.to_app()
This is the idiomatic FastHTML pattern for multi-file route organization
per the official documentation.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Set cookies on client instance instead of passing per-request to avoid
the deprecation warning about ambiguous cookie persistence behavior.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The /event-log route now shows a location selector dropdown instead of
returning a 422 error when no location_id is provided. This follows the
same pattern used by the Eggs page.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Simplify bottom nav from 6 to 4 items (Eggs, Feed, Move, Menu)
- Add persistent sidebar on desktop (hidden on mobile)
- Add slide-out menu drawer on mobile
- Rename Egg to Eggs with Harvest/Sell tabs (matching Feed pattern)
- Redirect /sell to /?tab=sell for consistency
- Role-gate Admin section (Locations, Status Correct) in sidebar
- Add user badge to sidebar showing username and role
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add comprehensive CLI tests for seed and serve commands
- Create README.md with development setup, deployment guide,
and environment variable reference
- Mark Step 10.3 as complete in PLAN.md
This completes the final implementation step.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add comprehensive E2E acceptance tests for stats progression:
- Test #1: Baseline eggs/feed/costs with 13 ducks
- Test #2: Mixed group proration with juveniles
- Test #3: Split flock per-location stats
- Test #4: Backdated eggs use historical roster
- Test #5: Event edit with revision tracking
Tests use cumulative fixtures building on each other.
Note: Some cost_per_egg_layers values differ from spec due to
integer bird-day truncation in implementation vs fractional
in spec calculations.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add user_defaults table and repository for persisting form defaults
across sessions. Feed and egg forms now load/save user preferences.
Changes:
- Add migration 0009-user-defaults.sql with table schema
- Add UserDefault model and UserDefaultsRepository
- Integrate defaults into feed route (location, feed_type, amount)
- Integrate defaults into egg route (location)
- Add repository unit tests and route integration tests
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add POST /actions/product-sold route for recording product sales.
Changes:
- Create web/templates/products.py with product_sold_form
- Create web/routes/products.py with GET /sell and POST /actions/product-sold
- Wire up routes in __init__.py and app.py
- Add "Record Sale" link to Egg Quick Capture page
- Add comprehensive tests for form rendering and sale recording
The form allows selling any sellable product with quantity and price,
and calculates unit_price_cents using floor division. Defaults to
egg.duck product as per spec.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add animal-outcome route with yield items section for harvest products
- Add animal-status-correct route with @require_role(ADMIN) decorator
- Add exception handlers for AuthenticationError (401) and AuthorizationError (403)
- Enable quick action buttons in animal detail page (Add Tag, Promote, Record Outcome)
- Add comprehensive tests for outcome and status-correct routes (81 total action tests)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add selection-based tag actions with optimistic locking:
- GET /actions/tag-add and POST /actions/animal-tag-add
- GET /actions/tag-end and POST /actions/animal-tag-end
- Form templates with selection preview and tag input/dropdown
- Diff panel for handling selection mismatches (409 response)
- Add TagProjection to the action service registry
- 16 tests covering form rendering, success, validation
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add /actions/promote/{animal_id} GET and /actions/animal-promote POST routes
- Add promote_form() template with nickname, sex, repro_status fields
- Add AnimalRepository.get() method for single-animal lookup
- 10 new tests for promote form rendering and submission
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add /actions/cohort GET and /actions/animal-cohort POST routes
- Add /actions/hatch GET and /actions/hatch-recorded POST routes
- Add cohort_form() and hatch_form() templates
- Add Cohort and Hatch icons and navigation items
- Add list_active() method to SpeciesRepository
- Register action routes in app.py
- 26 new tests for cohort and hatch actions
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add GET /animals/{animal_id} route to display individual animal details:
- Header summary with species, location, status, tags
- Event timeline showing all events affecting the animal (newest first)
- Quick actions card (Move functional, others disabled for now)
- Merge info alert for animals that have been merged
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add migration 0008 for event_log_by_location table with cap trigger
- Create EventLogProjection for location-scoped event summaries
- Add GET /event-log route with location_id filtering
- Create event log templates with timeline styling
- Register EventLogProjection in eggs, feed, and move routes
- Cap events at 500 per location (trigger removes oldest)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add status field to filter DSL parser and resolver
- Create AnimalRepository with list_animals and get_facet_counts
- Implement registry templates with table, facet sidebar, infinite scroll
- Create registry route handler with HTMX partial support
- Default shows only alive animals; status filter overrides
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add Move Animals form with selection context validation and concurrent
change handling via optimistic locking. When selection changes between
client resolution and submit, the user is shown a diff panel and can
confirm to proceed with the current server resolution.
Key changes:
- Add move template with form and diff panel components
- Add move routes (GET /move, POST /actions/animal-move)
- Register move routes in app
- Fix to_xml() usage for HTMLResponse (was using str())
- Use max(current_time, form_ts) for confirmed re-resolution
Tests:
- 15 route tests covering form rendering, success, validation, mismatch
- 7 E2E tests for optimistic lock flow (spec §21.8)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Routes are now at module level, accessible for import by templates
- Templates accept action parameter (route function or URL string)
- Routes pass themselves to templates for type-safe form actions
- Changes DB access pattern from app.state.db to request.app.state.db
- Registration uses rt(...)(func) pattern instead of @rt decorator
This enables the idiomatic FastHTML pattern where forms can use
action=route_function instead of action="/path/string", providing
type safety and refactoring support.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add /feed page with tabbed forms for Give Feed and Purchase Feed:
- GET /feed renders page with tabs (Give Feed default, Purchase Feed)
- POST /actions/feed-given records feed given to a location
- POST /actions/feed-purchased records feed purchases to inventory
Also adopts idiomatic FastHTML/HTMX pattern:
- Add hx-boost to base template for automatic AJAX on forms
- Refactor egg form to use action/method instead of hx_post
Spec §22 compliance:
- Integer kg only, min=1
- Warn if inventory negative (but allow)
- Toast + stay on page after submit
- Location/type stick, amount resets to default bag size
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>