Skip to content

framing-layer V1 slice 2 — close-the-loop on Mode B#122

Merged
jukka-matti merged 21 commits into
mainfrom
framing-layer-v1-slice-2
May 4, 2026
Merged

framing-layer V1 slice 2 — close-the-loop on Mode B#122
jukka-matti merged 21 commits into
mainfrom
framing-layer-v1-slice-2

Conversation

@jukka-matti
Copy link
Copy Markdown
Owner

Summary

Closes the Mode B loop end-to-end. Both apps now let a first-time user paste a CSV, state a process goal, multi-select outcomes with inline specs, pick scope dimensions, and land on a workspace with a goal banner + outcome pin per outcome. Save-to-browser, .vrs round-trip, and edit-framing affordances all live.

Builds on slice 1 (#121, foundation only — types + 8 unmounted components). Slice 2 mounts and wires what slice 1 built, plus the Azure features/hubCreation/ slice that didn't exist.

Plan: ~/.claude/plans/lets-do-slice-2-synchronous-sonnet.md (approved 2026-05-04).
Spec: docs/superpowers/specs/2026-05-03-framing-layer-design.md §16 acceptance.

What's in scope

  • ColumnMapping refactor to canonical Hub-level mapper. New onConfirm payload { outcomes: OutcomeSpec[]; primaryScopeDimensions: string[]; ... }. All 3 call sites migrated atomically (PWA App.tsx, Azure Editor.tsx × 2). No compat shim. Per feedback_no_backcompat_clean_architecture + feedback_type_separation_vs_component_separation.
  • Multi-outcome UI via OutcomeCandidateRow (now <input type="checkbox">, was radio — caught in final review).
  • PrimaryScopeDimensionsSelector sub-step + auto-suggest via suggestPrimaryDimensions().
  • OutcomeNoMatchBanner with functional rename / skip / expected-outcome-note CTAs.
  • Azure features/hubCreation/ slice: HubCreationFlow (Stage 1 → 2 → 3 inline orchestrator), useNewHubProvision (canonical hub creator with extractHubName).
  • Azure ProjectDashboard + Dashboard + New Hub routes through useNewHubProvision (replaces window.prompt).
  • Azure ProcessHubView: isProcessHubComplete() gate, "Add framing" CTA → Editor paste flow, editable GoalBanner (onChange wired through Dashboard.handleHubGoalChangesaveProcessHub), "Edit framing" affordance, OutcomePin row (one per outcome).
  • PWA App.tsx framing toolbar: OutcomePin per outcome, SaveToBrowserButton (contextual chip), VrsExportButton, edit-framing re-entry.
  • PWA HomeScreen: + Import .vrs file secondary action.
  • packages/core: isProcessHubComplete() helper.
  • GoalBanner: non-empty validation on Save.
  • E2E specs: PWA Mode B (4 tests, all pass), Azure Mode B (4 tests; 2 pass + 2 skip pending portfolio-state fixtures).
  • Decision-log entry: ProcessHubView amber-CTA-redirect supersedes the plan's empty-state-inline-panel design (HubCreationFlow lives in Editor's paste pipeline; same Stages 1→2→3 reached via redirect rather than inline panel).

What's out of scope (carried forward)

Per spec §15 V1 phasing — this slice ships the close-the-loop scope only:

  • Mode A.2-paste match-summary card → slice 3.
  • Mode A.2-evidence-source background ingestion → slice 3.
  • Stage 5 investigation entry modal → slice 3.
  • Multi-source via shared keys → slice 3 or later.
  • Defect anchoring + Pareto on canvas → slice 4.
  • "Forget this browser" PWA affordance → follow-up.
  • §8 canvas surface (drag-to-connect) → separate Spec 2.

Process

Subagent-driven dispatch per feedback_subagent_driven_default:

  • Sonnet implementer ran Tasks A–J in one autonomous pass (9 commits)
  • Sonnet branch-level spec compliance review caught 6 gaps
  • Sonnet fix-up subagent addressed gaps (6 commits)
  • Sonnet E2E subagent wrote PWA + Azure Mode B specs (2 commits)
  • Opus final review flagged 2 critical (multi-outcome data loss in edit mode, radio→checkbox) + 3 important issues
  • Sonnet pre-merge fix-up addressed all 5 (5 commits)
  • 21 commits total, 30+ files, +2700/-630 LOC. bash scripts/pr-ready-check.sh green throughout.

Test plan

  • bash scripts/pr-ready-check.sh — green (tests + lint + docs:check + dist integrity).
  • pnpm --filter @variscout/ui build — green per feedback_ui_build_before_merge.
  • Unit suites: core 3083, ui 1445, azure 1010, pwa 172 (1 pre-existing skip) — all pass.
  • PWA Mode B E2E: 4 tests passing (paste→Stage 1→Stage 3→canvas; Mode A.1 reload; cryptic-columns no-match; .vrs Import).
  • Azure Mode B E2E: 4 tests (2 pass, 2 skip — portfolio-state TODO).
  • Manual claude --chrome walk in both PWA + Azure (per feedback_verify_before_push):
  • Squash-merge to main; spec §16 Mode B + Mode A.1 + GoalBanner edit + Edit framing rows checked off.

🤖 Generated with ruflo

jukka-matti and others added 21 commits May 4, 2026 09:08
… (slice-2 Task A)

Replace the legacy (outcome, factors, specs) onConfirm signature with
ColumnMappingConfirmPayload. Stage 3 now renders OutcomeCandidateRow for
multi-select, PrimaryScopeDimensionsSelector for scope dimensions, and
OutcomeNoMatchBanner when all candidates score below threshold.

Key changes:
- onConfirm emits { outcomes: OutcomeSpec[], primaryScopeDimensions, ...legacy compat }
- Multi-outcome selection via parent-held Set state + OutcomeCandidateRow
- PrimaryScopeDimensionsSelector replaces standalone factor-picker in setup mode
- OutcomeNoMatchBanner surfaces when all candidates score below noMatchThreshold
- mode='edit': preloads initialOutcomes + initialPrimaryScopeDimensions
- Inline specs per row (OutcomeCandidateRow already has spec inputs when selected)
- Legacy compat fields (payload.outcome, payload.factors, payload.specs) kept for
  importFlow compatibility; clearly documented for removal in future slice
- No σ-based LSL/USL suggestions (spec §3.3 invariant preserved)
- CharacteristicType ambiguity resolved: processHub type vs legacy types.ts type

Co-Authored-By: ruflo <ruv@ruv.net>
…+ update tests

Atomic migration: all three render sites and their tests updated in one commit
so the build never breaks (per feedback_no_backcompat_clean_architecture).

Sites migrated:
- apps/pwa/src/App.tsx:886 (handleMappingConfirmWithGoal) — adopts
  ColumnMappingConfirmPayload; resolves slice-1 TODO by populating
  sessionHub.outcomes + sessionHub.primaryScopeDimensions
- apps/azure/src/pages/Editor.tsx:1310 + :1672 (handleMappingConfirmWithCategories)
  — adopts payload shape; persists outcomes + primaryScopeDimensions to active Hub
  via saveProcessHub when processContext.processHubId is available

Tests updated:
- packages/ui/src/components/ColumnMapping/__tests__/ColumnMapping.test.tsx
  — rewritten for new multi-outcome, scope-dim, no-match, and edit-mode API
- apps/azure/src/pages/__tests__/Editor.test.tsx — mocked ColumnMapping updated
  to emit new ColumnMappingConfirmPayload shape

E2E helper updated:
- apps/azure/e2e/helpers.ts confirmColumnMapping() — drives new OutcomeCandidateRow
  UI; accepts optional outcomeName param; fix pre-existing __dirname lint error

Co-Authored-By: ruflo <ruv@ruv.net>
…guard

Returns true only when a ProcessHub carries a non-empty processGoal AND at
least one OutcomeSpec with a non-empty columnName. Used by Mode A.1 reopen
path to decide canvas-first-paint vs framing flow. primaryScopeDimensions
intentionally not required (skip path is valid). Exported from both the
root barrel and the /processHub sub-path.

Co-Authored-By: ruflo <ruv@ruv.net>
…utton, VrsExportButton, Edit framing)

Adds the canvas framing toolbar that appears after data is loaded:
- OutcomePin for the first Hub outcome (mean ± σ + n fallback when no specs)
- SaveToBrowserButton for opt-in IndexedDB persistence
- VrsExportButton for .vrs file download
- "Edit framing" button that re-opens ColumnMapping in edit mode
- GoalBanner now wired with onChange to persist goal edits to sessionHub

Co-Authored-By: ruflo <ruv@ruv.net>
Adds onImportVrs prop to HomeScreen which renders a VrsImportButton when
provided. The handler in App.tsx restores hub + raw data from the .vrs file,
seeds outcome and primaryScopeDimensions, and lands directly on the canvas
— no framing flow re-run needed for previously-packaged training scenarios.

Co-Authored-By: ruflo <ruv@ruv.net>
…seNewHubProvision)

Adds FSD hubCreation feature slice:
- HubCreationFlow: Stage 1 (HubGoalForm) → Stage 3 (ColumnMapping) router;
  Stage 1 is skipped on re-edit or when a processHubId already exists
- useNewHubProvision: creates and persists a new ProcessHub from a goal
  narrative via saveProcessHub; fires onCreated callback for Editor wiring
- Index barrel exporting both
- 5 tests for useNewHubProvision covering creation, skip-path, and onCreated

Co-Authored-By: ruflo <ruv@ruv.net>
Adds optional onNewHub prop to ProjectDashboard. When provided, renders a
'New Hub' button in the Quick Actions panel that calls the handler. Absent
means the button is hidden (e.g. contexts that don't yet expose Mode B entry).
3 new tests covering render, hide, and click behavior.

Co-Authored-By: ruflo <ruv@ruv.net>
…hange wiring

- Add isProcessHubComplete import; compute hubIsComplete on render
- Add onHubGoalChange prop: wires GoalBanner onChange so inline goal
  edits persist back to the hub via the parent handler
- Add onEditFraming prop: renders amber framing-prompt banner with
  "Add framing" CTA when hub is incomplete and handler is provided;
  banner hidden when hub is complete or handler is absent
- 5 new tests: onChange wiring, prompt visible/hidden (complete hub,
  absent handler), CTA fires onEditFraming with correct hubId

Co-Authored-By: ruflo <ruv@ruv.net>
Editor.tsx:
- Replace both ColumnMapping render sites with HubCreationFlow so new
  investigations gate behind Stage 1 (HubGoalForm) before ColumnMapping
- Add handleNewHub: 'New Hub' from Dashboard opens paste → framing flow
- Add handleHubCreated: Stage 1 callback syncs new hub into processContext
  and processHubs list so Stage 3 can persist outcomes to it
- Remove unused ColumnMapping import (now internal to HubCreationFlow)
- Mock HubCreationFlow in Editor.test.tsx to preserve existing routing
  test semantics (data-testid="column-mapping")

HubCreationFlow.test.tsx (10 new tests):
- Stage 1 gate: shown for new investigation
- Skip Stage 1 on isMappingReEdit or existing processHubId
- Confirm/skip advance to ColumnMapping with correct goalContext
- saveProcessHub called with goal narrative (confirm path) and empty
  goal (skip path → Untitled hub)
- onHubCreated fires with created hub
- onConfirm called when ColumnMapping confirms

Co-Authored-By: ruflo <ruv@ruv.net>
…inding 1)

Drop the legacy outcome/factors/specs fields from ColumnMappingConfirmPayload;
derive them at call-sites in PWA App.tsx and Azure Editor.tsx from the Hub-shaped
outcomes[0] / primaryScopeDimensions. Tests updated to assert shim fields absent.

Co-Authored-By: ruflo <ruv@ruv.net>
save() now returns early when draft.trim() === '' so clicking Save with
an empty text field does not invoke onChange and leaves the banner in
edit mode. Test added to verify the guard.

Co-Authored-By: ruflo <ruv@ruv.net>
handleHubGoalChange persists inline goal edits via saveProcessHub and
propagates into processHubs state. handleEditFraming navigates to the
hub's framing flow. Both handlers are passed to ProcessHubView. Tests
added for goal-change persistence and framing-prompt visibility.

Co-Authored-By: ruflo <ruv@ruv.net>
When isProcessHubComplete() is true and hub.outcomes is non-empty,
render one OutcomePin per outcome inside a data-testid=outcome-pin-row
wrapper. Stats fall back to 0 / rowCount until live analysis is wired.
Tests assert the row is absent for incomplete hubs and present with N
pins for a hub with N outcomes.

Co-Authored-By: ruflo <ruv@ruv.net>
App.tsx framing toolbar maps sessionHub.outcomes to one OutcomePin each
(was outcomes[0] only). Removed the stats && guard so pins render in
the zero-stats fallback state before first analysis completes.
outcomePinMulti.test.tsx: seeds 1-outcome and 2-outcome hubs, asserts
correct pin counts via data-testid=outcome-pin.

Co-Authored-By: ruflo <ruv@ruv.net>
…port

Four Playwright tests covering the deferred Mode B scenarios from slice 1:
paste → HubGoalForm → ColumnMapping → GoalBanner + OutcomePin + framing-toolbar;
reload restores Mode A.1 GoalBanner; all-text CSV triggers OutcomeNoMatchBanner;
.vrs fixture import shows GoalBanner + OutcomePin. Adds data-testid to
SaveToBrowserButton (save-to-browser-button/saved) and VrsImportButton
(vrs-import-button) for stable E2E selectors.

Co-Authored-By: ruflo <ruv@ruv.net>
Two passing Playwright tests for the deferred Azure Mode B scenarios from
slice 1: (1) Editor paste → HubGoalForm Stage 1 → ColumnMapping → I-chart;
(3) load sample → Overview tab → action-new-hub → paste replaces data
(window.confirm accepted via page.once handler) → Stage 1 → ColumnMapping
→ I-chart. Tests 2 and 4 skip gracefully when portfolio is unavailable in
a clean context. Adds MODE_B_CSV, pasteDataAndAnalyze, completeStage1
helpers to e2e/helpers.ts.

Co-Authored-By: ruflo <ruv@ruv.net>
…cal #1)

Pass initialOutcomes + initialPrimaryScopeDimensions from the active Hub
to ColumnMapping at all three render sites so edit-framing no longer silently
drops outcomes 2..N.

- apps/pwa/src/App.tsx: pass sessionHub.outcomes / .primaryScopeDimensions
  when isMappingReEdit
- apps/azure/src/features/hubCreation/HubCreationFlow.tsx: add
  initialOutcomes + initialPrimaryScopeDimensions props; forward to
  ColumnMapping when isMappingReEdit === true
- apps/azure/src/pages/Editor.tsx: derive activeHub from processHubs and
  pass its outcomes/primaryScopeDimensions at both HubCreationFlow sites
- ColumnMapping.test.tsx: add regression test
  'edit-mode roundtrip preserves all initialOutcomes (multi-outcome)'

Co-Authored-By: ruflo <ruv@ruv.net>
…tical #2)

Multi-select semantics require checkbox, not radio. This was a semantic
mismatch — radio implies single-select in HTML.

- OutcomeCandidateRow.tsx: type="radio" → type="checkbox"
- OutcomeCandidateRow.test.tsx: getByRole('radio') → getByRole('checkbox')
- ColumnMapping.test.tsx: all getAllByRole('radio') → getAllByRole('checkbox')
  (already committed in Critical #1 batch but note here for clarity)
- apps/azure/e2e/helpers.ts: confirmColumnMapping selector updated from
  input[type="radio"] to input[type="checkbox"]
- apps/pwa/e2e/modeB.e2e.spec.ts: weight_g selector updated; adds multi-outcome
  E2E test (select 2 checkboxes → assert 2 OutcomePins)

Co-Authored-By: ruflo <ruv@ruv.net>
…Important #3)

Previously all three handlers were no-ops. Now they do something:
- onSkip: clears selectedOutcomeSpecs (Start Analysis becomes disabled —
  canvas will show all-unclassified columns per §3.3 skip-spec path)
- onExpectedChange: stores the analyst note in local state and includes it
  in the ColumnMappingConfirmPayload as expectedOutcomeNote
- onRename(oldName, newName): delegates to parent's onColumnRename callback
  (sets a display alias for the column)

Adds ColumnMappingConfirmPayload.expectedOutcomeNote field (optional string).
ProcessHub has no home for it yet — downstream handlers currently ignore it;
logged as carry-forward in decision-log.

New ColumnMapping tests:
- 'Skip CTA clears selected outcomes' — disables Start Analysis after skip
- 'expectedOutcomeNote is included in payload after onExpectedChange'

Co-Authored-By: ruflo <ruv@ruv.net>
…ovision (Important #5)

The bespoke handleCreateHub in pages/Dashboard.tsx used inline
crypto.randomUUID() + saveProcessHub with a hardcoded 'New Hub' name,
bypassing extractHubName. Replaced with useNewHubProvision which:
- calls extractHubName(narrative) — falls back to 'Untitled hub' for empty
- uses the canonical persistence path (saveProcessHub via useStorage)
- fires onCreated callback so processHubs state + selectedHubId update atomically

Updated Dashboard.processHub.test.tsx to assert 'Untitled hub' instead of
'New Hub' and updated the test description to reflect canonical path.

Co-Authored-By: ruflo <ruv@ruv.net>
…ine-panel decision

2026-05-04 entry documents why slice 2 shipped the amber-CTA →
redirect-to-Editor-paste-flow path instead of the inline-panel design
from the plan. Also documents the expectedOutcomeNote carry-forward.

Co-Authored-By: ruflo <ruv@ruv.net>
@vercel
Copy link
Copy Markdown

vercel Bot commented May 4, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
mean-beoynd-lite-pwa Ready Ready Preview, Comment May 4, 2026 9:28am
variscout_website Ready Ready Preview, Comment May 4, 2026 9:28am

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