Multi-word names ("Rat A") now allow any word separator in the
filename — space, underscore, or dash — so "Rat_A_session.csv"
and "Rat-A-data.bin" both match. Single-word names and numeric IDs
are unchanged. Partial overlaps still rejected ("Rat" won't hit
"Rationale"). Tests: 5 new name-based match cases (24 total).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a FileDropZone at the bottom of the day view where users can
drop all session files at once. Filenames are regex-matched to
subjects using their animal_id_string (word-boundary aware, so
"2852" won't match "28520"). Only metadata is recorded — files
are never uploaded.
Backend: new session_files table (id, daily_status_id, filename,
file_size BIGINT, file_type, last_modified, notes). Routes:
POST /api/daily-statuses/:id/files (batch create)
GET /api/daily-statuses/:id/files
DELETE /api/session-files/:id
Frontend: FileDropZone shows per-subject match groups, unmatched
files, and a warning for subjects with no match. After saving,
registered files are displayed with delete capability.
test-files/: generate_test_files.sh creates 4 dummy files per
subject (2×bin, 2×csv) with correct naming for drag-and-drop
testing. Pre-generated for 2026-04-23 with IDs 2852/3076/3077/3078.
Tests: 19 tests covering drag state, regex matching, save payload,
partial-overlap prevention, multi-subject batching, and delete.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Clicking any table cell opens an inline input (text or textarea per
field type). Enter or blur saves; Escape cancels. Unchanged values
skip the API call. Custom fields merge with existing custom_fields;
builtin fields use their key directly.
Prev/next buttons at the bottom navigate to adjacent days that have
data for the selected field, preserving the ?field= param. Day list
is fetched via getCalendar and sorted chronologically.
Tests: 41 total (added 15 new tests for cell editing and day nav).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Covers loading/error states, breadcrumb, row filtering by selected
field (custom and builtin), table structure, subject-name navigation,
edit modal open/close/submit, session metrics bar chart visibility,
and API call parameters.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Calendar day clicks now navigate to /experiments/:id/day/:date
(with ?field= param). ExperimentDayView shows a sticky-column table
of all subjects with data that day, inline edit via modal, and a
session-metrics bar chart from analysis_summary. Subject name links
to /daily-statuses/:id. Backend adds ?date= filtering to
GET /experiments/:id/daily-statuses.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
5 tests cover the bar chart rules:
- Shows when analysis_summary.total > 0
- Hidden when no summary or total=0
- Multiple animals each with their own summary
- Animals excluded from day view (empty field) also excluded from chart
- animalsWithData now filters by selected field value (not just record presence)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
X-axis: animals with valid field data on that day.
Bars: Success / Failure / Total from status.analysis_summary for that day.
Each day shows its own session metrics. Animals without analysis_summary
or with total=0 are excluded from the chart.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
X-axis: animal names, Y-axis: the selected field's numeric value recorded
that specific day. Animals with empty or non-numeric values are excluded.
Each day's chart is independent — different animals, different values.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Shows Total / Success / Failure bars across ALL sessions for each animal
that has at least one valid (non-empty) value for the chosen field.
Animals with zero valid field entries are excluded from the chart.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Previously showed cumulative success/failure counts across all days.
Now shows the selected field's actual value for each subject on that
specific day — numeric values only, subjects with empty or non-numeric
values are excluded from the chart.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Day modal now only shows animals that have a status record for that day
- Removed '+ Add' button and 'No data' placeholder for absent subjects
- Fixed failure bars disappearing in recharts: removed radius from all bars,
added minPointSize=2 on failure bar so zero-value bars remain visible
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Calendar moved out of List/Group tab toggle — always visible below cross-
subject analytics (ExperimentAnalysisCharts)
- Day modal shows all subjects: existing data as read-only + Edit button,
missing data as + Add button; both open DailyStatusForm inline
- Status changes/creates propagate up to parent via onStatusChange/onStatusCreate
and calendar counts recompute locally (no extra API round-trip)
- Bar chart (recharts) at bottom: Total/Success/Failure bars per subject plus
% complete text, grouped by subject name
- Tests updated: 17 ExperimentCalendar tests, 76 total passing
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Use module-level EMPTY_TEMPLATE constant as default prop to prevent
useEffect infinite loop (new [] each render changed identity every cycle)
- Add htmlFor to date and dynamic field labels for proper a11y association
- Add role=alert to date error paragraph so tests can find it
- Update tests to pass a template with builtin fields (required by template-
driven component design) and mock experimentsApi
- Add --max-old-space-size=8192 to test scripts to prevent OOM on full suite
All 72 frontend tests now pass across 7 suites.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Prevents OOM crashes when running the full Jest suite — the act() warning
stack traces were generating 16k+ lines of output which exhausted Node heap.
All assertions still run; only the noisy console.error spam is silenced.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
New ExperimentCalendar component added as a third view mode (List / Group / Calendar)
on the Experiment Detail page.
Backend:
- GET /api/experiments/:id/calendar?field=<fieldIdOrBuiltinKey>
Returns { field, days: { "YYYY-MM-DD": count } } where count = number of
subjects with a non-null/non-empty value for the chosen field on that date.
Supports both builtin fields (vitals, treatment, notes, experiment_description)
and custom fields addressed by fieldId UUID.
Frontend:
- ExperimentCalendar component: navigable monthly grid, field selector
(defaults to first field with "weight" in label, else first active field),
blue badges showing subject count per day, click to open modal with all
subjects' data for that day.
- Integrated into ExperimentDetail as displayMode "calendar", persisted
in localStorage alongside the existing list/group modes.
- experimentsApi.getCalendar() added to API client.
Tests:
- backend/tests/calendar.test.js: 8 unit tests (404, empty days, builtin
count, custom field count, whitespace ignored, multi-month, field echo,
missing param).
- frontend/tests/ExperimentCalendar.test.jsx: 13 RTL tests covering
rendering, default field selection, count badges, month navigation,
field-change re-fetch, day click modal, and multi-subject display.
All 47 backend + 13 calendar frontend tests passing.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>