Skip to content

Commit 318caa2

Browse files
edenoclaude
andcommitted
Merge phase-1-day-order-dio: DIO behavioral-events per-day redesign
Reframes behavioral-events (DIO) handling to match the real hardware + lab data and gives it its own day-editor home. Highlights: - Day-owned DIO set (animal-level library retired); carry-forward day-to-day. - The editor is the ECU hardware channel grid (Din1-32 inputs / Dout1-32 outputs), 2 columns x 32 rows; name the channels you use, blank = unused/not exported, names unique. Per-label auto-numbering (Poke -> Poke1), no separator, instance-count not channel index. - Direction-specific suggestions (verified vs 98 real YAMLs): Din offers Poke/Run_Camera_Ticks, Dout offers Light/Pump. - Behavioral Events is its own Day Editor tab (validation routing + status moved with it); copy-from-animal bootstraps a blank first day from another animal. - Surgical naming correction (reverted the backwards index-naming) + Din/Dout type restriction; multiple multi-agent reviews and fixes folded in. Exported YAML shape unchanged; golden baselines byte-identical throughout. Verified at the branch HEAD: vitest 4615 passed, baselines byte-identical, lint 0 errors, functional e2e 88 passed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2 parents d5917d4 + 3339e37 commit 318caa2

74 files changed

Lines changed: 3652 additions & 2092 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.claude/docs/plans/dio-per-day-sets/PLAN.md‎

Lines changed: 300 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
# Phase 2a — Day-level DIO wiring table (Inputs / Outputs)
2+
3+
[← back to PLAN.md](PLAN.md) · design rationale: [§5 UX design](PLAN.md#5-ux-design-the-heart), [§6 consistency](PLAN.md#6-consistency-with-the-rest-of-the-app)
4+
5+
Rebuild the **per-day** behavioral-events editor (`BehavioralEventsDisplay`) into a wiring
6+
table grouped into **Inputs (Din)** and **Outputs (Dout)**, in the user's language, with the
7+
guided Type/index controls **moved down** from the animal editor (not rebuilt). The animal-level
8+
library and its "Use on this day" inherited list stay untouched in this phase — they are removed
9+
in [phase-2b](phase-2b-retire-animal-library.md). This phase is a net-new day UI; it changes no
10+
exported YAML (golden baselines stay byte-identical).
11+
12+
**Inputs to read first:**
13+
14+
- [src/pages/DayEditor/BehavioralEventsDisplay.jsx](../../../../src/pages/DayEditor/BehavioralEventsDisplay.jsx) — the surface being rebuilt. Today: inherited read-only list with 🔒 + "Use on this day" (`handleUseOnThisDay`, lines 54-56; inherited block lines 86-123); day-specific section (lines 126-216) with the day-event add editor (lines 170-215); inline duplicate-description error via the shared helper (line 48); duplicate-vs-inherited name warning (line 39). **Only the day-specific editing portion becomes the wiring table here; leave the inherited "Use on this day" block in place for 2b.**
15+
- [src/pages/DayEditor/TasksEpochsStep.jsx:360-366](../../../../src/pages/DayEditor/TasksEpochsStep.jsx#L360-L366) — mounts `BehavioralEventsDisplay`; passes `inheritedEvents={getAnimalBehavioralEvents(animal)}` (line 124), `dayEvents={getDayBehavioralEvents(day)}` (line 125), and wires `onDayEventsChange` → `onFieldUpdate('behavioral_events', events)` (line 364).
16+
- [src/pages/AnimalEditor/BehavioralEventsSection.jsx:294-344](../../../../src/pages/AnimalEditor/BehavioralEventsSection.jsx#L294-L344) — the guided **Type** `<select aria-label="DIO type">` + **Index** `<input aria-label="DIO line index">` cluster (`.dio-description-fields`) and the `descriptionWillBeRewritten` inline note. **Move this control cluster down into the day table; keep the same `aria-label`s, the `splitDioDescription`/`joinDioDescription` round-trip, and the rewrite-warning.** Supporting parse at lines 250-254.
17+
- [src/utils/dioDescription.js](../../../../src/utils/dioDescription.js) — `splitDioDescription` (line 25; empty→`{Din,1}`, unrecognized type→`''`), `joinDioDescription` (line 56). Reuse verbatim.
18+
- [src/validation/behavioralEvents.js:18](../../../../src/validation/behavioralEvents.js#L18) — `duplicateBehavioralEventDescriptions(events)`, the shared helper that the inline gate and export Rule 17 ([rulesValidation.js:1016](../../../../src/validation/rulesValidation.js#L1016)) both use. Keep sharing it.
19+
- [src/valueList.js:905](../../../../src/valueList.js#L905) — `behavioralEventsNames()` (Name suggestions); [:931](../../../../src/valueList.js#L931) — `behavioralEventsDescription()` returns `['Din','Dout']`.
20+
- **Section conventions to mirror** (cited in [§6](PLAN.md#6-consistency-with-the-rest-of-the-app)): [CamerasSection.jsx](../../../../src/pages/AnimalEditor/CamerasSection.jsx) section shell (lines 152-163) + empty-state block (lines 134-148); [TasksTable.jsx](../../../../src/pages/DayEditor/TasksTable.jsx) status-badge glyph+`sr-only` (lines 216-224) and `ConfirmDialog` delete (lines 244-252, handler 111-116); inline validation `div.inline-error[role=alert]` / `div.inline-warning[role=status]` with `aria-invalid`+`aria-describedby` ([BehavioralEventsSection.jsx:283-292, aria at 278-279](../../../../src/pages/AnimalEditor/BehavioralEventsSection.jsx#L278-L292)). `ConfirmDialog` is [src/components/Modal/ConfirmDialog.jsx](../../../../src/components/Modal/ConfirmDialog.jsx).
21+
22+
## Tasks
23+
24+
- **Add a direction selector + Inputs/Outputs grouping to the day events model.** A DIO event's
25+
direction is derived from its `description` Type via `splitDioDescription(description).type`
26+
(`Din` → Input, `Dout` → Output). Render two labelled groups in `BehavioralEventsDisplay`'s
27+
day-specific section — an **"Inputs (Din) — sensors the animal triggers"** block and an
28+
**"Outputs (Dout) — things you drive"** block — each a `role="table"` following the
29+
CamerasSection shell (`header.section-header` with `<h2>`/`<p>`, then `div.table-actions` with a
30+
`button-primary`). An event with an unrecognized/analog `description` (Type `''`) sorts into a
31+
third "Other" group rather than disappearing. Do **not** change storage: `day.behavioral_events`
32+
stays a flat array of `{ description, name }`; grouping is presentation only.
33+
34+
- **Move the guided Type/Index control cluster into each table row.** Copy the
35+
`.dio-description-fields` JSX (Type `<select aria-label="DIO type">` + Index
36+
`<input aria-label="DIO line index">` + `InfoIcon`) and the `descriptionWillBeRewritten`
37+
inline warning from [BehavioralEventsSection.jsx:294-344](../../../../src/pages/AnimalEditor/BehavioralEventsSection.jsx#L294-L344) into the day row's editor. Keep the
38+
`splitDioDescription`/`joinDioDescription` round-trip and the same `aria-label`s. **Do not edit
39+
the frozen legacy `src/element/SelectInputPairElement.jsx`.** (The animal editor keeps its own
40+
copy until [phase-2b](phase-2b-retire-animal-library.md) removes that surface; extracting a
41+
shared `<DioLineFields>` component is acceptable if it reduces duplication, but is not required.)
42+
43+
- **Relabel the fields in the user's language** ([§5.1](PLAN.md#5-ux-design-the-heart)). The
44+
column/field for `description` is labelled **"DIO channel"**; the field for `name` is labelled
45+
**"Event"**. **YAML keys are unchanged** — this is display text only. Add a one-line hint on the
46+
Event field: *"becomes the DIO event's name in the NWB file"* (closes the NWB
47+
`name`/`description` inversion noted in [§2](PLAN.md#2-verified-ground-truth-the-anchor--independently-re-verified-do-not-re-litigate-without-re-checking-these-sources); no separate preview panel).
48+
49+
- **Add the one-time direction legend + per-field hints.** Render a short, *programmatically
50+
associated* text legend stating **Din = inputs / Dout = outputs** (not an emoji glyph alone —
51+
WCAG 1.4.1). Per-field hints carry real examples (e.g. "Din1", "left well poke"). No literal ECU
52+
hardware diagram (the table is the diagram — [§3.10](PLAN.md#3-decisions)).
53+
54+
- **Keep the shared inline gates.** The day-event **Name** field keeps Rule 14's uniqueness
55+
surfaced inline (duplicate `name` within the day set), and the **duplicate-`description`** gate
56+
keeps using `duplicateBehavioralEventDescriptions` ([behavioralEvents.js:18](../../../../src/validation/behavioralEvents.js#L18)) so the inline gate and export Rule 17 never diverge. Use the
57+
Name combobox (`SuggestionCombobox` with `suggestions={behavioralEventsNames()}`, `warnOffList`)
58+
exactly as the animal editor does today — **plain `onChange`, no `onSelect`** (per-label
59+
auto-numbering via `onSelect` is [phase-3](phase-3-templates-autonumber.md)).
60+
61+
- **Add/remove rows via the section conventions.** "+ add event" buttons live in each group's
62+
`div.table-actions`; delete routes through the shared `ConfirmDialog` (destructive), not
63+
`window.confirm`, naming the event being removed (mirror [TasksTable.jsx:244-252](../../../../src/pages/DayEditor/TasksTable.jsx#L244-L252)).
64+
65+
- **Default view = "review yesterday's set."** The carried-forward set (already deep-copied by
66+
`createDayRecord` at [workspaceTransitions.js:389](../../../../src/state/workspaceTransitions.js#L389)) is what the table shows on a new day. Add the section explainer copy: *"Behavioral
67+
events — how your behavioral hardware maps to the SpikeGadgets ECU (carried forward from
68+
{prior date} · edit only if you rewired the rig)"* when the day was seeded from a prior day.
69+
Sourcing the prior date: the day record's carry source is not stored on the day; derive the
70+
"carried from" date from the immediately-earlier same-`configurationVersion` day in
71+
`getAnimalDays(animal)` (same logic the bad-channels carry-forward note uses), or omit the date
72+
clause if none exists.
73+
**Status (shipped):** the default view IS the carried-forward set (it shows `day.behavioral_events`,
74+
which `createDayRecord` seeds from the carry source) and the explainer says so generically
75+
("A new day carries forward the previous day's set — edit only if you rewired the rig."). The
76+
explicit "carried forward from **{date}**" clause is **deferred** — naming the source date needs
77+
the animal's day RECORDS (date + `configurationVersion`), which `TasksEpochsStep` doesn't have
78+
(only `animal` + the current `day`); wiring it cleanly means threading the days map through
79+
`DayEditor`, out of scope for this phase. Pick it up when that plumbing exists.
80+
81+
- **a11y polish** ([§5.3](PLAN.md#5-ux-design-the-heart)): every Type/Index control keeps an
82+
explicit `aria-label`; the Din/Dout legend is associated (e.g. `aria-describedby` from the group
83+
headers); status/empty states follow the glyph+`sr-only` pattern.
84+
85+
- **Empty state.** When `day.behavioral_events` is empty, show the CamerasSection-style empty
86+
block (icon + `<h3>` + copy + hint + primary CTA) inviting the user to add the first event or
87+
(foreshadowing [phase-3](phase-3-templates-autonumber.md)) a standard set.
88+
89+
- **SCSS.** Extend [src/pages/DayEditor/BehavioralEventsDisplay.scss](../../../../src/pages/DayEditor/BehavioralEventsDisplay.scss); reuse the `.dio-description-fields` rules and the
90+
`.inline-error`/`.inline-warning` tokens. Per [CSS-is-global], scope new structural class names
91+
under a `behavioral-events`/day-table root so they don't leak app-wide.
92+
93+
- **CHANGELOG.** Add an "Added"/"Changed" entry for the day-level DIO wiring table (Inputs/Outputs
94+
grouping, relabeled fields, moved guided controls, legend). State explicitly that the exported
95+
YAML shape is unchanged.
96+
97+
## Deliberately not in this phase
98+
99+
- **Removing the animal-level library or the "Use on this day" inherited block** — that is
100+
[phase-2b](phase-2b-retire-animal-library.md). This phase leaves both in place; the wiring table
101+
edits the day-owned set exactly as the day-specific editor does today.
102+
- **Standard-set templates and per-label auto-numbering** ("add 6 pokes → Poke1…Poke6"; `onSelect`
103+
wiring) — [phase-3](phase-3-templates-autonumber.md).
104+
- **"Mark reconfiguration" history and the `comments` field** — [phase-4](phase-4-reconfiguration-comments.md).
105+
- **Any change to the exported `behavioral_events` shape, `BEHAVIORAL_EVENT_ORDER`, or the
106+
validation rules.** Storage and export are untouched.
107+
108+
## Validation slice
109+
110+
| Test | Asserts |
111+
| --- | --- |
112+
| `BehavioralEventsDisplay` groups day events by direction | `Din*` events render under "Inputs (Din)", `Dout*` under "Outputs (Dout)", an `Accel5`/unrecognized event under "Other" — none dropped |
113+
| guided Type/Index round-trip in the day row | editing Type→`Dout` + Index→`3` stores `description: 'Dout3'`; opening `Din1` shows Type `Din`, Index `1` and saves back `Din1` unchanged |
114+
| relabeled fields | the `description` control is reachable by the accessible name **"DIO channel"** and `name` by **"Event"**; the Event field exposes the "becomes the DIO event's name" hint |
115+
| direction legend present + associated | a text node stating Din=input / Dout=output exists and is referenced from the group(s) via `aria-describedby` (not emoji-only) |
116+
| duplicate-`description` inline gate still fires | two day events with `description: 'Din1'` surface the inline error via `duplicateBehavioralEventDescriptions` (same helper as export Rule 17) |
117+
| duplicate `name` inline gate (Rule 14) | two day events named `Poke` surface the inline name-uniqueness error |
118+
| delete via `ConfirmDialog` | row delete opens the shared dialog naming the event; confirm removes it, cancel keeps it (no `window.confirm`) |
119+
| carry-forward default copy | a day created with `carryForwardFromDayId` shows the prior day's set in the table; editing it does not mutate the source day (`structuredClone` at `workspaceTransitions.js:389`) |
120+
| **golden baselines unchanged** *(integration)* | `npx vitest run baselines` stays byte-identical — this phase exports no new/different YAML |
121+
122+
## Fixtures
123+
124+
Reuse the existing day/animal test factories used by `BehavioralEventsDisplay.test.jsx` and
125+
`workspace-day.test.js`. Add a small in-memory animal with a day holding mixed-direction events
126+
(`{description:'Din1',name:'Poke'}`, `{description:'Dout7',name:'Pump'}`, and one analog
127+
`{description:'Accel5',name:'imu'}` to exercise the "Other" group + rewrite warning). No new YAML
128+
fixtures needed (export shape unchanged).
129+
130+
## Review
131+
132+
Before opening the PR for this phase, dispatch `code-reviewer` against the diff. Confirm:
133+
- Every task is implemented as specified; the guided controls were **moved**, not rebuilt, and the frozen `SelectInputPairElement.jsx` is untouched.
134+
- The "Deliberately not in this phase" list is honored — the animal library + "Use on this day" block are still present; no template/auto-number/comments scope crept in.
135+
- Validation slice passes; golden baselines byte-identical (`npx vitest run baselines`); full suite + `npm run lint` (0 errors) + `npm run test:e2e` (functional specs) green. (The 7 local-only visual-regression screenshots are pre-existing stale baselines — out of scope.)
136+
- Tests exercise asserted behavior, not tautologies; shared setup is in fixtures.
137+
- No docstring / test name / module name references "Phase 2a" or this plan.
138+
- a11y: Type/Index `aria-label`s present; legend programmatically associated; no color/emoji-only meaning.

0 commit comments

Comments
 (0)