Commit Graph

128 Commits

Author SHA1 Message Date
fb59ef72a8 Fix FastHTML empty value attribute omission in select options
FastHTML omits the value attribute when value="" (empty string), causing
browsers to use the option's text content as the submitted value. This
made forms send "Keep current" or "No change" text instead of empty
string, failing Pydantic enum validation.

Fixed by using "-" as a sentinel value instead of "" for "no change"
options, and updating route handlers to treat "-" as None.

Affected forms:
- Promote form (sex, repro_status)
- Update attributes form (sex, life_stage, repro_status)
- Outcome form (yield_product_code)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-08 09:31:11 +00:00
29fbe68c73 Add backdating support to egg harvest and sell forms
Both forms now have datetime pickers like the feed forms, allowing
users to record events at past timestamps. Each form has a unique
field_id (harvest_datetime, sell_datetime) to avoid conflicts.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-08 09:24:26 +00:00
4b951d428f Remove bootstrapping data. 2026-01-08 09:21:09 +00:00
1d322de67b Fix datetime picker on multi-form pages
The event_datetime_field JavaScript used querySelector to find the
ts_utc hidden input by name, which breaks when multiple forms have
ts_utc fields (like feed give and purchase forms). Now each hidden
field gets a unique ID based on the field_id parameter, and the
JavaScript uses getElementById for correct scoping.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-08 09:17:56 +00:00
d4a29130f6 Fix feed error response rendering with to_xml
The 422 error handlers were using str() to convert FT objects to HTML,
which produces Python repr output instead of HTML. Changed to use
to_xml() like other routes (eggs.py, products.py) do.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-08 09:11:08 +00:00
3f510d8d76 Add animal_id filter support for registry selection
The registry selection feature builds filters like animal_id:X|Y|Z
but the parser didn't recognize animal_id as a valid field.

- Add animal_id to VALID_FIELDS in parser.py
- Add animal_id handler in resolver.py _build_filter_clause

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-08 08:44:28 +00:00
abb1c87e6c Add registry selection + expandable affected animals
Registry improvements:
- Add checkbox column for selecting animals in the table
- Add selection toolbar with count display
- Add Actions dropdown (Move, Add Tag, Update Attributes, Record Outcome)
- Selection persists across infinite scroll via JavaScript
- Navigate to action page with filter=animal_id:X|Y|Z for selected animals

Event detail improvements:
- Show more animal details: sex (M/F/?), life stage, location name
- Add "Show all X animals" button when >20 animals affected
- HTMX endpoint to load full list on demand
- Separate affected_animals_list component for HTMX swaps

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-05 16:03:25 +00:00
ad1f91098b Fix MonsterUI Select bug, UI improvements, enable animal rename
- Replace broken MonsterUI LabelSelect with raw HTML Select elements
  (was sending label text instead of value attribute)
- Fix wrong ReproStatus options in promote form (use enum values)
- Add spacing between name and details in animal selection list
- Fix registry filter layout, add Clear button
- Use phonetic ID in animal details panel title
- Change feed price input from cents to euros
- Allow renaming already-identified animals (remove identified check)
- Fix FeedGiven 422 error (same MonsterUI Select bug)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-05 15:57:09 +00:00
14bf2fa4ae Fix CSRF 403, improve registry UI, add phonetic IDs
- Fix CSRF token handling for production: generate tokens with HMAC,
  set cookie via afterware, inject into HTMX requests via JS
- Improve registry page: filter at top with better proportions,
  compact horizontal pill layout for facets
- Add phonetic ID encoding (e.g., "tobi-kafu-meli") for animal display
  instead of truncated ULIDs
- Remove "subadult" life stage (migration converts to juvenile)
- Change "Death (natural)" outcome label to just "Death"
- Show sex/life stage in animal picker alongside species/location

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-05 15:20:26 +00:00
a4b4fe6ab8 Migrate to alo organization
All checks were successful
Deploy / deploy (push) Successful in 2m51s
Update docker image path and workflow reference to use alo org.
Fix var directory permissions in docker build.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-05 10:50:16 +00:00
0b51ad3dac Use shared CI/CD workflow from alo-cluster
All checks were successful
Deploy / deploy (push) Successful in 2m40s
Simplify workflow to use reusable workflow:
  uses: ppetru/alo-cluster/.gitea/workflows/deploy-nomad.yaml@master

All build/push/deploy logic is now centralized in alo-cluster.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-05 07:48:44 +00:00
1b6147817b Fix CI: resubmit job with new UUID to trigger deployment
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 2m37s
Evaluate alone doesn't create deployments. We need to resubmit
the job with a changed meta.uuid to force Nomad to deploy.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-04 22:33:51 +00:00
75e7323d7d Fix CI workflow: use bundled tools, add debugging
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 1m40s
- Use skopeo and jq directly (already in nix-runner image)
- Redirect evaluate response to /dev/null
- Echo responses for debugging
- Handle case where no deployment exists

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-04 22:24:36 +00:00
f07102d199 Use extraCommands instead of runAsRoot in Docker build
Some checks failed
Build and Deploy / build-and-deploy (push) Failing after 2m12s
runAsRoot requires KVM which isn't available in CI containers.
extraCommands achieves the same result without virtualization.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-03 21:47:34 +00:00
a2893162e6 Fix branch name.
Some checks failed
Build and Deploy / build-and-deploy (push) Failing after 41s
2026-01-03 12:34:49 +00:00
ee572a37f1 Allow manual workflow runs. 2026-01-03 12:33:17 +00:00
3ac1e1140a Log more info when rejecting connections. 2026-01-03 11:58:35 +00:00
743fe9d68d Deploy workflow. 2026-01-03 11:47:02 +00:00
06421f38bb Make docker image work. 2026-01-03 11:46:50 +00:00
f2145e4827 feat: add CIDR/netmask support for trusted proxy IPs
TRUSTED_PROXY_IPS now accepts CIDR notation (e.g., 192.168.1.0/24)
in addition to exact IP addresses. Supports both IPv4 and IPv6.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-03 11:46:04 +00:00
240cf440cb feat: event detail page styling and deleted events indicator
- Fix event detail page for direct navigation by using FastHTML's
  idiomatic htmx parameter instead of manual header check
- Add custom toaster.py with HTML support using NotStr to render
  clickable links in toast messages
- Add hx_preserve to toast container to survive HTMX body swaps
- Add is_deleted column to event_log_by_location table
- Update event_log projection revert() to set is_deleted flag
  instead of deleting rows
- Add strikethrough styling for deleted events in event log

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-03 11:03:47 +00:00
e86af247da fix: use sentinel value for optional brood location dropdown
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>
2026-01-03 09:10:32 +00:00
9fbda655f5 fix: return FT components directly for proper toast injection
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>
2026-01-02 20:11:05 +00:00
628d5cc6e6 fix: subset selection validation and remove unnecessary hash computation
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>
2026-01-02 13:01:07 +00:00
cccd76a44c fix: 409 responses now swap and event log filtering works
- 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>
2026-01-02 12:16:56 +00:00
0125bc4aaa feat: add location tooltips, links, and detail page
- 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>
2026-01-02 10:53:43 +00:00
d19e5b7120 fix: rebuild-projections doesn't require CSRF_SECRET
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>
2026-01-02 10:38:24 +00:00
85a4c6bc7b feat: add rebuild-projections CLI and fix event delete projection revert
- 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>
2026-01-02 10:35:39 +00:00
c214454d67 fix: create ProjectionRegistry locally in event delete route
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>
2026-01-02 09:53:30 +00:00
9709a78dc6 fix: UserRole.admin typo and defensive JSON parsing in JS
- 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>
2026-01-02 09:49:45 +00:00
a1fed4ebcd fix: event log UI issues and add global error toast handler
- 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>
2026-01-02 09:42:26 +00:00
9cd890b936 fix: checkbox selection bug and add event log improvements
- 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>
2026-01-01 20:03:34 +00:00
1c836c6f7d feat: add HTMX trigger to filter inputs for dynamic checkbox selection
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>
2026-01-01 19:27:17 +00:00
a35d4a3c0d fix: use correct column name (animal_id) in get_event_animals query
The animal_registry table uses animal_id as its primary key, not id.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-01 19:15:31 +00:00
3937d675ba feat: add event detail slide-over, fix toasts, and checkbox selection
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>
2026-01-01 19:10:57 +00:00
25a91c3322 fix: remove trailing newlines from migrations to prevent SQLITE_MISUSE errors
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>
2026-01-01 16:09:06 +00:00
91d2884ec0 feat: add notes field to 7 forms missing it
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>
2026-01-01 13:52:45 +00:00
fdbf259182 feat: add render_page() helper and fix username display
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>
2026-01-01 13:50:28 +00:00
b74ca53f20 fix: preserve datetime picker value when form validation fails
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>
2026-01-01 13:36:07 +00:00
680227f53f fix: improve cohort form validation for empty/placeholder values
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>
2026-01-01 13:32:14 +00:00
37300c00c6 chore: use ppetru as dev mode username
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-01 12:43:36 +00:00
40769f5ce1 fix: add HTMX 422 response handling and missing form name attrs
- 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>
2026-01-01 10:40:49 +00:00
abf78ec98a feat: add event backdating with collapsible datetime picker
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>
2026-01-01 10:40:01 +00:00
82def73188 fix: use APIRouter for proper route function resolution in forms
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>
2026-01-01 09:39:40 +00:00
14c68187f5 fix: resolve Starlette cookie deprecation warnings
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>
2026-01-01 07:59:40 +00:00
c8f026fb2a fix: event-log route handles missing location_id
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>
2026-01-01 07:56:37 +00:00
64bb99aa64 chore: migrate to lefthook for git hooks
Replace custom bash pre-commit hook with lefthook for:
- Parallel execution of ruff + pytest
- Quieter output (pytest -q instead of -v)
- Version-controlled config (lefthook.yml)
- Smart skipping when no .py files staged

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-01 07:50:57 +00:00
c8f348621f Speed up tests. 2025-12-31 20:08:20 +00:00
768a3e4352 feat: redesign navigation with responsive sidebar
- 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>
2025-12-31 19:45:39 +00:00
c6a87e35d4 feat: complete CLI, Docker & deployment docs (Step 10.3)
- 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>
2025-12-31 18:21:14 +00:00