The protocol-pedagogy implementation and visible-UI proof are complete. The
docs/active_plans/reports/protocol_pedagogy_visual_resolution.md ledger records
the repository-supported outcomes, the full 31-page browser walkthrough, and
the remaining evidence or contract boundaries.
Current follow-ups:
- Replace the rejected SVG visual direction with reference-backed, recognizably realistic laboratory-equipment archetypes before another family-wide redraw. The 2026-08-26 user verdict supersedes D01-D05 as visual candidates while preserving the completed SVG schemas, material semantics, state ownership, and browser consumer. Use the current production-art review workflow in USAGE.md rather than the four-object candidate fixture.
- Obtain faculty-owned inputs for blockers B1-B4 before replacing the fixed, internally coherent teaching scenarios.
- Obtain approval for conditional step graphs before implementing B5.
- Decide whether to ratify a closed choice-role capability for B6. Current
selectsemantics intentionally offer every clickable scene item; a semantic choice role would let calculation and interpretation scenes retain visible experimental context without presenting that context as an answer. - Reduce private-YAML snapshot assertions where the same learner-visible behavior is already protected by stepper and browser tests. Preserve exact quantities only when they are approved scientific invariants.
- Migrate the seven overloaded result SVGs into application-owned UI in a future
workstream. Keep
cell_viability_results_display.svg,electrophoresis_endpoint_display.svg,gel_image_results_display.svg,hemocytometer_observation_display.svg,mtt_reader_results_display.svg,plate_reader_absorbance_result_panel.svg, andplate_reader_normalized_viability_panel.svgbyte-preserved until that work begins. The SVG interface scope audit defines the migration boundary.
Big scene-runtime activation plan paused as of 2026-05-17. Gated on the focused row-based base_scene layout plan. See archived scene-runtime activation record for state at hold and resumption criteria.
Surfaced by the 96-well authoring shape semantics spike. See
96_well_authoring_shape_finding.md (archived) for measured evidence.
validation/stepper/state.py now stores independent subpart records, validates
subpart-scoped fields, and preserves per-well material identity and volume. The
cell-culture runner and per-well browser tests exercise the implemented path.
Deferred from the 96-well spike. Only worth implementing if and when
a real subset use case appears (e.g., a control row, a 2x2 block, a
dose-response group of 4 wells that must carry an experimental name).
For whole-plate cases, well_plate_96.all_wells is sufficient and
ships today on main without any spec change.
If a real subset use case surfaces:
- Draft a spec amendment for protocol-level
regions:+ region-awareObjectStateChange(the shape exercised by the uniform-region spike). - Include a
members: all(or equivalent inferred-all) shorthand so the region shape is not line-count-penalized when the region happens to span the whole plate. The spike found the explicit 96-member list made the named-region inline form longer than the expanded enumeration; this shorthand removes the penalty. - Reference the spike-only branch
spike/region-stepper(unmerged) for a working validator + stepper implementation of the region shape; the branch can be cherry-picked onto a fresh implementation branch rather than rewritten from scratch.
Do NOT introduce protocol-level regions: as a generic feature.
Reserve for meaningful subsets, not aliases for the whole plate.
Surfaced by WP-AUDIT-1 of the 96-well cleanup plan. See the archived enumeration audit for full evidence.
The treatment protocol now groups learner actions around meaningful row and half-plate dosing skills while retaining per-well state writes needed for the four experimental condition classes. Browser coverage verifies the visible condition transitions without requiring per-well endurance clicks.
Single repo-wide file ratifying wet-lab + simulation vocabulary
used across EVERY lab family in content/protocols/ -- cell
culture, drug dilution, colorimetric assay (MTT), SDS-PAGE
electrophoresis, plus the simulation-side authoring vocabulary.
NOT a one-lab glossary; the cross-lab coverage IS the value.
Triggered by the MTT cleanup (2026-05-16) where MTT etymology, aspirate vs draw vs dispense, formazan identity, well-total volume semantics, and trituration all needed clarification. The same drift class is likely in every other lab area in the repo.
Full acceptance criteria in ROADMAP.md "Glossary doc (planned)" section.
Defer until vocabulary drift surfaces in a second lab family (MTT alone is insufficient justification for the repo-wide sweep).
Lab convention: "aspirate" means vacuum-line removal to waste (e.g., "aspirate spent media from the plate"). Pipette loading from a source uses "draw," "load," or "pipette up," not "aspirate." The manual renderer uses "draw" for dispensing-pipette uptake, and the protocol validator protects the same distinction in learner-facing prompts.
This is an active schema-validation gate. The protocol validator rejects a
learner-facing prompt that uses "aspirate" unless the step uses the dedicated
aspirating_pipette for vacuum removal. The current protocol corpus is clean:
remaining occurrences describe removal to waste rather than loading a
dispensing pipette.
Action for new content: use "draw," "load," or "pipette up" for dispensing-pipette uptake. Reserve "aspirate" for vacuum removal to waste.
mtt_plate_reaction.add_mtt_to_wells dispenses 25 microL per
channel. That sits in the lower-precision zone of a standard P200
multichannel (range 20-200 microL; accuracy degrades from ~3% mid-
range to ~5-10% at 20-25 microL). For dose-response assay rigor,
consider redesigning MTT prep: e.g., 100 microL of 3 mM MTT (instead
of 25 microL of 12 mM) gives same 300 nmol per well at a more
accurate pipette volume. Cascades back through Q6: post-MTT well
total changes from 225 to 300, decant + incubation volumes shift.
Defer until next wet-lab protocol revision; current YAML carries a
note in the prompt about freshly-calibrated tips.
Renderer currently emits per-channel volume in dispense bullets (e.g., "draw 25 uL from the 12 mM MTT solution") without noting the multichannel aggregation (8 channels x 25 uL = 200 uL per stroke drawn from the bottle). Wet-lab students may mis-interpret the bottle drawdown rate. Possible fix: when source target is a multichannel pipette, append "(per channel; 8 x N = M uL per stroke)" to the dispense bullet.
Rendered manual for an all_wells target reads "the well
all_wells of the 96-well plate". Awkward but not wrong. The
renderer should special-case region / block groups to read
"every well of the 96-well plate" or similar natural phrasing.
Scope: validation/manual/protocol_manual.py.
The current protocol models twelve visible multichannel column strokes and
describes the repeated whole-plate distribution skill consistently. The stale
whole-plate all_wells wording mismatch no longer applies.
Resolved: the author-entity -> codegen-decode -> DOM-glyph convention is now
the documented, canonical rendering path. Authors write HTML entities in
committed YAML (µM, α); codegen decodes each entity to its
Unicode glyph at the string-emit choke point, so generated/** carries the
real character; the runtime renders that string as a plain DOM text node,
never innerHTML. See the "Glyph rendering" convention in
specs/MATERIAL_YAML_FORMAT.md,
cross-linked from OBJECT_YAML_FORMAT.md, PROTOCOL_YAML_FORMAT.md, and
PROTOCOL_AUTHORING_GUIDE.md. The prior uL/uM ASCII stopgap is retired.
These were uncovered while landing M1b of the SVG asset pipeline refactor
(plan ref: ~/.claude/plans/cuddly-snuggling-feather.md). The reviewer
bisected against HEAD with M1b changes reverted and reproduced both
failures, so they are pre-existing and not caused by M1b. Surfaced here for
triage; M1c does NOT fix either. See the M1b CHANGELOG entry under
## 2026-05-09 for the full context.
- Outcome: fixed. Root cause was the capture-phase click handler in
scene_driver.tscallingtarget.getAttribute('data-item-id')on the raw click target, which is often an inner SVG shape (ellipse/rect/path) inside the.hood-itemwrapper, not the wrapper itself. After adirectToolstep that calledrenderHoodScene()directly (skippingrunSceneRender), the per-item bubble-phase listeners fromhood_shared.tswere not re-attached on the rebuilt DOM, leaving only the capture-phase listener -- and that listener could not resolve the data-item-id from a nested SVG element. Fix: resolve the nearest ancestor viatarget.closest('[data-item-id]')before reading the id. See the CHANGELOG entry under## 2026-05-09### Fixes and Maintenancefor full evidence (cell_culture 25/25, all 10 protocols pass, smoke 9/9, tsc clean).
- Outcome: fixed. M6 of the SVG asset pipeline refactor authored the
missing helper at
tests/_compile_for_test.mjs(usesnpx esbuild --bundle --platform=node --define:window=globalThisto compile a.tsentry to a tempdir.mjsand dynamic-import it). Bothtest_svg_color_patch.mjsand the newtest_svg_pipeline.mjsuse the helper to exercise production.tsmodules directly. See the M6 CHANGELOG entry under## 2026-05-09for full evidence.
Resolved 2026-05-17. All three sites fixed pedagogically:
-
content/protocols/cell_seeding_plate_setup/protocol.yaml: wrong instrument class (micropipette max 1000 uL used for mL-range transfers). Fixed: switched to serological_pipette; values converted uL->mL (2400->2.4, 9600->9.6). Also fixed bonus bug:well_plate_96.all_wells material_volume9600->100 uL per well. -
content/protocols/mtt_plate_reaction/protocol.yaml: biohazard_decant_binmaterial_volume21600 mL was mL/uL unit confusion (21600 uL total = 21.6 mL). Fixed: 21600->21.6. -
content/protocols/passage_hood_detachment/protocol.yaml: trypsin_bottle max:100 was too small; protocol assumes 500 mL stock (consistent with pbs_bottle and media_bottle). Fixed: trypsin_bottle max/default 100->500; protocol value 197->497.
Equipment V7 WARNINGs also resolved (same run):
- hemocytometer added
material_containercapability (was missing; caused WARNING). - V7 gate refined: now warns only when equipment has material fields but lacks
material_containercapability (previously warned on any equipment with material fields).
Dropped from the spec-content-drift-remediation plan (see
spec_content_drift_remediation.md
Objective #3). Reason: no current consumer renders material display_color
as a color swatch; the gate is forward-looking until a theme-aware visual
consumer ships. The gate should be included in the future "SVG asset
accessibility audit" follow-up plan as part of a WCAG audit on hard-coded
SVG fills. Note: V6a (cross-protocol material consistency) is deferred - validation/yaml/cross_protocol.py
lines 43-45 carry a "Deferred" comment. The gate was planned in
docs/archive/spec_content_drift_remediation.md WP-V6 but not implemented before plan
archive. Manual reconciliation of 9 divergent materials was done (CHANGELOG 2026-05-17),
but the automated enforcement gate does not yet exist. A future protocol addition could
re-introduce the same divergence silently.
The material overlay variant-collapse plan closed (see
archive/material_overlay_vocabulary.md),
and tests/test_object_asset_refs.py now hard-asserts missing == [] against
the recursive SVG registry. New authored asset_name gaps fail the fast suite
instead of drifting a baseline count.
-
(RESOLVED) Per-well distinct material state for
well_plate_96-- material plan COMPLETE (plandynamic-coalescing-flask.mdM0-M4). Per-well material state (834state_value_not_allowed-> 0), registry-backed material acceptance, scalar color resolution, PATH-B subpart geometry, and production render-path per-well color are all done. Per-well render proven via production Playwright harness (tests/playwright/test_subpart_well_plate_render.spec.ts). Per-well fill rendering is also implemented throughfill_height; the object now declares material identity and amount only on well subparts, with no obsolete object-level material placeholders. -
(RESOLVED 2026-08-03) Visible
adjustuses the in-flow set-point editor and completes through the same runtime-authoritative validator path as other gestures. Per-well protocols and all 31 generated protocol pages now complete through the visible Playwright walker. -
(#27, FUTURE, not this plan) Declared registry-backed field affordance: retire the
[empty, mixed]syntactic seam inwell_plate_96.yaml. Currently the runtime accepts registry-backed drug material names viaseed_target+ validatedset_object_state, but the YAMLallowedfield is[empty, mixed]. A future affordance would make the registry-backed field explicitly declared in the object schema so the enum is not a conflicting surface. Scope: object schema + validator + generator changes; no runtime behavior change required.
The current Solid implementation is broadly idiomatic; these are non-blocking refinements.
- Review imperative
createEffectcalls that only stampdata-*attributes onto DOM nodes. Where practical, replace them with JSX attribute bindings so Solid owns the DOM update directly. - Consider a shared Solid SVG loading/error component (or boundary) later; do not refactor
the current
createResourcepath unless real duplication or an error-handling problem appears. - Preserve the rule that SVG DOM access stays isolated behind the injection/lookup layer
(
injectSvgFromManifest/resolveAnchor); runtime state and control flow stay in Solid stores/signals, never DOM queries.