Skip to content

v3 Designer: Variables editor (Phase 5) + validation/Reset/delete (Phase 6) — v0.11#77

Merged
mbreiser merged 3 commits into
mainfrom
phase5/variables-ux
May 29, 2026
Merged

v3 Designer: Variables editor (Phase 5) + validation/Reset/delete (Phase 6) — v0.11#77
mbreiser merged 3 commits into
mainfrom
phase5/variables-ux

Conversation

@mbreiser
Copy link
Copy Markdown
Contributor

@mbreiser mbreiser commented May 28, 2026

Completes the Variables feature set for the v3 Experiment Designer and ships the deferred Phase 6 affordances. Three unmerged commits over main:

Commit Phase Version
259254e Phase 4 cleanup (listener accumulation, selection drift, ruler ticks, convert _unknownKeys) v0.9
de31235 Phase 5 — Variables editor (inline table, 🔗 anchor-binding popover, rename cascade, cascade-unbind delete) v0.10
75a4b87 Phase 6 — pre-export validation modal, Reset, library-row delete v0.11

Phase 5 — Variables editor (v0.10)

Inline-editable Variables table in Settings (rename cascades to every *alias, cascade-unbind delete, complex map/seq anchors as read-only badges); a 🔗 anchor-binding popover on every editable scalar (bind to existing / create-and-bind / rebind / unbind); rename cascade as a single undo step.

Phase 6 — validation + Reset + delete (v0.11, this session)

  • collectBlockingErrors(experiment) — a blocking sibling to collectExportWarnings. Composes validateReferences and adds two CST checks via YAML.visit: duplicate anchor names (yaml@2 accepts them silently, so they're counted) and dangling aliases (a safety net for the in-memory mutation model — a fully-dangling alias throws at import). Exported from all three surfaces.
  • Pre-export validation modal — Export runs the validator first; on blocking errors it shows a modal listing each one with an Export anyway escape hatch (Cancel aborts). Soft warnings stay in the non-blocking banner — the two tiers are distinct.
  • Reset button — header ⟲ Reset confirms, then loads the blank skeleton. Reversible: pushUndo() + a new loadYamlText({ keepUndo: true }) option, so a single Undo restores the prior doc.
  • Library-row delete (✕) — blocked while usage > 0 ("remove from sequence first"); when unused, confirm then docDelete(['conditions', idx]). Clears selection if the deleted condition was selected.

Testing

  • npm test → 446/446 (arena 10, v2 137, v3 446). Suite 30 (17 checks) covers the Phase 6 validator + library delete; Suite 29 covers the Phase 5 variable lifecycle.
  • Browser-verified (preview MCP, no console errors): the 6 Phase 5 checks (rename cascade lists refs + single-undo revert, bind, unbind, create-and-bind, cascade-unbind delete, complex-anchor read-only badge) plus the 3 Phase 6 checks (validation modal blocks/cancels on a duplicate anchor, Reset collapses to skeleton and Undo restores, library delete blocked-when-used / works-when-unused).

Notes

  • The v3 .js files are hand-formatted and predate strict Prettier conformance (main itself fails prettier --check on them, and no CI enforces it). New code matches the surrounding hand-style; no global reformat, to keep the diff focused.
  • Footer bumped to v0.11. Handoff doc v3-editor-handoff-2.md updated for Phase 6; the original v3-editor-handoff.md marked superseded.

🤖 Generated with Claude Code

mbreiser and others added 3 commits May 27, 2026 19:57
…ler ticks, convert _unknownKeys (v0.9)

Six Codex-flagged bugs in landed Phase 4 work:

- A1: #sequenceList drag/drop listeners moved to one-time startup wiring.
  Previously re-attached on every renderSequence(); a single drop after N
  renders fired N handlers, inserting N duplicate refs and pushing N undo
  entries. Surfaced within a normal editing session.
- A2: onMoveSequenceEntry and onInsertSequenceRefFromLib now walk
  selection.index across the full displacement, not just the moved entry.
  Library drag-drop no longer steals selection focus — it shifts the
  existing selection through the insert (matches the +Add Ref button's
  intent of selecting the new entry only for explicit button clicks).
- A3: Timeline ruler tick positions are computed piecewise by walking the
  layout array (rulerXForRealTime), so ticks stay aligned with clamped
  step widths. Flat pxPerSec drifted as soon as any short step clamped to
  the 60px / 40px minimum.
- A4: isConvertibleBlock rejects entries with non-empty _unknownKeys.
  Block→ref convert was silently dropping forward-compat fields like
  retry_on_fail, undermining the Tier 1 unknown-passthrough guarantee.
  The user-facing error message now enumerates every blocker including
  forward-compat keys.
- A5: _buildSequenceEntry mirrors the parser's positive-integer
  repetitions validation. Closed a doc/JS-mirror divergence path that
  D4/paste-import would hit (entry.repetitions = 0 → YAML '0' but mirror
  = 1 via `|| 1` default).
- A6: onMoveCommand defensively early-returns on no-op / out-of-bounds,
  matching the onMoveSequenceEntry pattern. Avoids pushUndo pollution.

Tests: 375/375 (was 369). Added 6 new builder-side validation cases to
Suite 10b for A5.

Verified in browser:
- A1: 30 forced re-renders + 1 simulated library drop adds exactly 1 entry.
- A4: Right-click on future_keys block shows full error listing all
  blockers including `forward-compat keys: retry_on_fail, abort_if`.
- A3: Multi_block fixture (mixed durations exercising 60px clamping)
  shows "1m" tick at x=2447 of a 3516-px ruler — piecewise positioning.

Footer bumped to v0.9 | 2026-05-27 19:54 ET.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…e cascade (v0.10)

Closes the largest user-facing feature gap in the v3 designer. Three
pieces, all additive — only one existing function (renderEditableField)
gains a 🔗 button.

## Inline-editable Variables table

Replaces the read-only Variables section in the Settings drawer with:
- Editable anchor-name input on every row (renames cascade via the
  modal).
- Editable value input (numeric or text by inferred type).
- Read-only "complex anchor" badge for map/seq anchors.
- ✕ delete button — blocked when references exist, with confirmation
  modal offering cascade-unbind.
- "+ Add variable" footer row with name + value + Add button.

## Anchor binding popover

Every editable scalar (controller/plugin command fields) gains a 🔗
button. Single global popover positioned beneath the trigger; outside-
click and Escape close it. Content varies by alias state:

- Literal scalar: "Bind to existing anchor" select (with type-mismatched
  options visibly disabled with a "(type: X)" suffix) + "Create new
  anchor from this value" with name input and "Create & bind".
- Aliased scalar: summary card "→ &name = value" + "Edit in Variables…"
  jump button + "Rebind to a different anchor" select + "Unbind (convert
  to literal)" danger button.

Create-and-bind runs as a single pushUndo → so one Ctrl+Z reverts both
the variable creation and the binding atomically.

## Rename cascade

Confirmation modal lists every reference path that will update; on
confirm, a single pushUndo wraps docRenameVariable, which atomically
walks every *alias source in one synchronous pass via YAML.visit. One
undo step for the whole rename.

Empty-refs case skips the modal (no friction for unused anchors).

## New helpers in js/protocol-yaml-v3.js (10 added → 27 total)

- docCreateVariable / docDeleteVariable / docRenameVariable
- docSetVariableValue (mutates Scalar.value in place — preserves anchor)
- docBindToAnchor / docUnbindAnchor
- findAliasesTo (recursive walk of doc.contents — simpler/safer than
  the YAML.visit ancestors-chain reconstruction)
- variableIsComplex / isValidAnchorName / anchorExists

extractVariables now prefers pair.value.anchor as identity (falls back
to map key). This means renaming via the table cascades the anchor
without churning the map key — per the plan-mode user decision.

## New UI primitive: confirmModal({title, body, list, confirmLabel})

Promise-based, backdrop-dismiss, Escape/Enter keys. ~70 lines. Phase 6's
full pre-export validation modal will reuse it.

## Tests

54 new tests in Suite 29 (Variable lifecycle + anchor binding):
- Create / delete (incl. ANCHOR_HAS_REFS + cascade-unbind)
- Rename (anchor + every alias, count match, collision rejection,
  comment preservation, merge-key cascade `<<: *foo`)
- Bind literal → alias / unbind alias → literal round-trip
- setVariableValue preserves anchor declaration
- findAliasesTo count matches grep on canonical fixture
- variableIsComplex / isValidAnchorName / anchorExists basics

Total: 429/429 (was 375 after cleanup PR).

## Verified in browser

- Variables table renders 4 editable rows + Add row for canonical_a
- Rename `&dur_long → &duration_long` shows modal listing all 9
  references, applies in one undo step
- Click 🔗 on aliased duration: popover shows current binding, rebind
  options (with color_command disabled as type-mismatched string),
  unbind action
- Click 🔗 on literal: bind-existing dropdown + create-new section
- Create-and-bind: single Ctrl+Z reverts both ops; Variables table
  loses the new anchor on undo
- Unbind: alias chip replaced by editable input with resolved value

## Updated copy

The BETA banner now says "Editing. Scalar fields, sequence layout, and
Variables are all editable. Click the 🔗 icon next to any value..."
(was "anchor-bound fields are read-only until the Variables editor
lands"). Footer bumped to v0.10 | 2026-05-27 20:07 ET.

## Decisions logged

- Variables table identity: anchor name only (per plan-mode Q&A).
  Rename cascades the anchor; the map key is preserved untouched
  through round-trip but not surfaced. Rare existing-YAML cases where
  they differ appear "renamed" in the UI.
- Codex plan-review pass on Phase 5: skipped (per plan-mode Q&A).

## Open items deferred (not in this PR)

- Phase 6 (full validation modal + Reset)
- Phase 7 (comment-preservation tests at strategic positions)
- Phase 8 (MATLAB-validation flow docs)
- Phase 9 (quickstart HTML)
- D4 (cross-library import) — now unblocked since Phase 5 closes the
  prefix-clutter UX gap

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…e (v0.11)

Builds on the Phase 5 Variables editor (de31235). Adds the deferred Phase 6
variables-adjacent work, all browser-verified this session.

- collectBlockingErrors(experiment) in protocol-yaml-v3.js: a blocking
  sibling to collectExportWarnings. Composes validateReferences and adds two
  CST checks via YAML.visit — duplicate anchor names (yaml@2 accepts them
  silently, so they're counted) and dangling aliases (safety net for the
  in-memory mutation model). Exported from all three surfaces.
- Pre-export validation modal: Export runs collectBlockingErrors first; on
  errors it shows a confirmModal listing each one with an "Export anyway"
  escape hatch. Soft warnings stay in the non-blocking banner.
- Reset button (header): confirms, then loads the blank skeleton. Reversible
  via a new loadYamlText({ keepUndo: true }) option + pushUndo, so one Undo
  restores the prior doc.
- Library-row delete (✕): blocked while usage > 0 ("remove from sequence
  first"); when unused, confirm then docDelete(['conditions', idx]). Clears
  selection if the deleted condition was selected.
- Suite 30 (17 checks) covering the validator + library delete. Full suite
  446/446 (arena 10, v2 137, v3 446). Footer bumped v0.10 -> v0.11.

Browser-verified: the 6 Phase 5 checks (rename cascade, bind, unbind,
create-and-bind, cascade-unbind delete, complex-anchor badge) plus the 3
Phase 6 checks (validation modal block/cancel, Reset + Undo, library delete
blocked-when-used / works-when-unused).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@mbreiser mbreiser changed the title feat(v3): Phase 5 — Variables editor + anchor binding + rename cascade (v0.10) v3 Designer: Variables editor (Phase 5) + validation/Reset/delete (Phase 6) — v0.11 May 29, 2026
@mbreiser mbreiser merged commit 9dca364 into main May 29, 2026
3 checks passed
@mbreiser mbreiser deleted the phase5/variables-ux branch May 29, 2026 03:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant