Skip to content

feat(8f-followup): close 19 of 20 retrospective findings (single-PR cleanup workstream)#166

Merged
jukka-matti merged 23 commits into
mainfrom
canvas-viewport-8f-followups
May 14, 2026
Merged

feat(8f-followup): close 19 of 20 retrospective findings (single-PR cleanup workstream)#166
jukka-matti merged 23 commits into
mainfrom
canvas-viewport-8f-followups

Conversation

@jukka-matti
Copy link
Copy Markdown
Owner

@jukka-matti jukka-matti commented May 13, 2026

Summary

A 3-agent retrospective on shipped 8f (PRs #160#165) surfaced 20 findings — 5 HIGH that qualified the "shipped" verdict, 8 MEDIUM spec-vs-shipped drift, 7 LOW cleanups. This PR closes 19 of them across 22 commits on canvas-viewport-8f-followups. Plan: docs/superpowers/plans/2026-05-13-canvas-viewport-8f-followups.md. Tracking: docs/investigations.md entry "8f canvas viewport — followup findings from 3-agent retrospective".

HIGH findings closed

  • chore(deps): bump pnpm/action-setup from 4.1.0 to 4.4.0 #1 Azure Blob sync gapuseCanvasViewportLifecycle (Azure) now rehydrates from Blob after Dexie cache, debounced-persists to both with ETag-conditional PUT, last-write-wins per spec §11 with App Insights telemetry on conflict. New loadBlobCanvasViewport / saveBlobCanvasViewport in blobClient.ts mirror updateBlobEvidenceSnapshotsConditional. ADR-081 §2 now honored.
  • chore(deps): bump actions/upload-artifact from 4.6.2 to 7.0.0 #2 AuthorL3View parallel-implements FRAME column-assignment — extracted getStepColumnAssignments to @variscout/core/frame; both AuthorL3View and LocalMechanismView now consume the shared helper. ADR-074 amendment + ADR-081 honored.
  • chore(deps): bump actions/checkout from 4.2.2 to 6.0.2 #3 Legacy variscout-wall-layout Dexie never deleteddeleteLegacyWallLayoutDb() exported from canvasViewportStore; fires fire-and-forget at module init. Misleading test tightened to assert deletion.
  • chore(deps): bump @microsoft/teams-js from 2.49.0 to 2.50.0 #4 Lens × level matrix narrower than spec §10 — resolved via AMEND path. Git blame showed performance + yamazumi were intentional V2 placeholders ("Future ... lens" descriptions). Spec §10 amended; matrix reflects as-shipped truth.
  • chore(deps): bump lucide-react from 0.475.0 to 0.577.0 #5 ~30+ hardcoded English UI strings — migrated to typed MessageCatalog (70 new keys across 32 locales; non-en use English placeholders pending translation pass). Plus the Canvas/index.tsx empty-state leak caught by review.

MEDIUM findings closed (all 8)

  • LOD cross-fade is real now (was opacity:1 constant)
  • Snap-to-LOD on wheel-stop wired via d3-zoom 'end' handler
  • L1 specLimits contractually derived from measureSpecs[map.ctsColumn] with ADR-073 regression test
  • 4 remaining response-path CTAs (Focused Investigation / Charter / Sustainment / Handoff) surfaced at L3 column granularity
  • Mobile L3 without focalStep: confirmed already correct (NoFocalStepPrompt renders the step-list — retrospective was a misread)
  • d3-zoom subscribe selector-scoped via reference-equality short-circuit
  • setViewportLevel replaces throw with warn + no-op
  • 6px click-vs-drag deadband enforced via clickDistance(6)

LOW findings closed (6 of 7)

  • STORE_LAYER renamed annotation-per-projectannotation-per-hub
  • Dead worldToWallSvg identity deleted
  • CanvasViewport.tsx confirmed alive — JSDoc added
  • Stale wallLayoutStore doc-string references in viewStore.ts + preferencesStore.ts refreshed
  • CanvasLensPicker test added (33 new tests; 18-cell predicate matrix)
  • FIT_TO_CONTENT_ZOOM_BY_LEVEL co-located with LOD_THRESHOLDS in @variscout/core/canvas/viewport

Deferred

Workflow

Executed via superpowers:subagent-driven-development per feedback_subagent_driven_default. Sonnet implementer + Sonnet reviewer per task; the user accepted single-PR-at-the-end mode partway through, so per-task PR ceremony was dropped for inline commits on this branch.

Test plan

  • pnpm --filter @variscout/core test — 3392 passed (+6 stepColumns)
  • pnpm --filter @variscout/stores test — 257 passed (+2 deletion-assertion)
  • pnpm --filter @variscout/hooks test — 1192 passed
  • pnpm --filter @variscout/ui test — 2039 passed (+39 across CanvasLensPicker, SystemLevelView, LODSwitcher, LocalMechanismView)
  • pnpm --filter @variscout/azure-app test — 1304 passed (+17 across blobClient.viewport + useCanvasViewportLifecycle.blob)
  • pnpm --filter @variscout/pwa test — 335 passed (no regression)
  • pnpm --filter @variscout/ui build — green
  • pnpm --filter @variscout/azure-app build — green
  • --chrome walk completed 2026-05-14 (PWA on worktree)

--chrome walk summary (2026-05-14)

Walked PWA at desktop (1440×900) + mobile (420×900) on the Showcase: Fill Weight Investigation dataset against the followup-branch worktree.

Verified visually / live:

Verified by code + test evidence (not visually reachable in Showcase):

Side findings (NOT introduced by this PR, NOT blocking):

  • React setState-in-render warning fires from AppMain repeatedly across LOD/mode/tab transitions: Cannot update a component (AppMain) while rendering a different component (AppMain). apps/pwa/src/App.tsx is not touched on this branch (git log origin/main..HEAD -- apps/pwa/src/App.tsx is empty), so this is pre-existing — likely a 70+-selector AppMain calling a hook that triggers setState during render. Deserves an docs/investigations.md entry post-merge.

🤖 Generated with ruflo

jukka-matti and others added 23 commits May 13, 2026 23:07
Closes 8f followup HIGH #3 — pre-8f users carried an orphan IndexedDB
forever after the wallLayoutStore → canvasViewportStore shape change.
Mirror PwaHubRepository's legacy-DB cleanup pattern. Tightens the
existing test that lied about asserting deletion.

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

Closes 8f followup HIGH #5 — 47 hardcoded English strings across
SystemLevelView, CanvasLensPicker, MobileLevelPicker, NoFocalStepPrompt,
AuthorL3View, LocalMechanismView now route through MessageCatalog.
CANVAS_LENS_REGISTRY labels/descriptions translated at render time in
CanvasLensPicker via LENS_LABEL_KEY / LENS_DESC_KEY maps; the hooks
registry keeps English for non-UI consumers. Non-English locales receive
English placeholders pending a translation pass (TODO(i18n) comment).

Co-Authored-By: ruflo <ruv@ruv.net>
Closes the i18n reviewer's flagged follow-up: Canvas/index.tsx:1042
rendered the lens registry's English label and a local
CANVAS_LEVEL_LABELS map as user-facing copy, bypassing i18n.

- Export LENS_LABEL_KEY from CanvasLensPicker for cross-component reuse
- Reuse canvas.mobile.{system,process,step} for level labels
- Add canvas.lensPicker.invalidAtLevel parameterized key (32 locales,
  English placeholder elsewhere per the catalog's translation pass plan)
- Drop CANVAS_LEVEL_LABELS + remove now-unused CANVAS_LENS_REGISTRY import

Co-Authored-By: ruflo <ruv@ruv.net>
Closes 8f followup LOW #20 — load-bearing CanvasLensPicker had no
dedicated test. 18 enabled/disabled cells (3 levels × 6 lenses) +
click-dispatch + aria-label assertions.

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

Closes 8f followup LOW #18 — viewStore.ts:140 + preferencesStore.ts:178
still mentioned wallLayoutStore in doc-strings after the PR1 rename to
canvasViewportStore.

Co-Authored-By: ruflo <ruv@ruv.net>
…/core/frame

Closes 8f followup HIGH #2 — AuthorL3View's private focalStepColumns
helper duplicates business logic that should live in core/frame. The
helper now lives at packages/core/src/frame/stepColumns.ts with 6
unit tests; AuthorL3View imports it. Per ADR-074 amendment + ADR-081:
Canvas embeds owner-surface computation, doesn't re-derive.

Co-Authored-By: ruflo <ruv@ruv.net>
Closes 8f followup MEDIUM #8 — SystemLevelView trusted a flat specLimits
prop without ADR-073-anchored contract. Now accepts measureSpecs keyed by
column and derives from measureSpecs[map.ctsColumn] internally; old prop
renamed to specLimitsOverride (advisory/debug only, deprecated). Canvas
passes { [ctsColumn]: { usl, lsl, target, cpkTarget } } as measureSpecs.
Two regression tests assert the leak scenario: wrong-column measureSpecs
key produces '--' Cpk, not a silently wrong value.

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

Same ADR-074 amendment violation that PR2 fixed in AuthorL3View. The
private helper now delegates to getStepColumnAssignments from
@variscout/core/frame (introduced in the prior commit), flattening the
structured result into the string[] this view needs.

Co-Authored-By: ruflo <ruv@ruv.net>
Closes 8f followup HIGH #4 via AMEND path (not expand). Git blame shows
both `performance` and `yamazumi` lenses were introduced with
`enabled: false` AND registry descriptions explicitly labeling them as
"Future ... lens" — intentional V2 placeholders, not bugs. Spec §10 was
over-promised at original ship.

- Spec §10 matrix amended: 6 cells marked `(V2 — deferred; lens not
  enabled in V1)` instead of pretending they ship enabled
- V2 expansion path documented inline
- investigations.md entry marked RESOLVED 2026-05-13 with rationale

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

l3 without focalStepId now emits console.warn and returns the viewport
unchanged instead of throwing. fitToContent guards the same path.
Updated test asserts warn was called and state did not change.

Co-Authored-By: ruflo <ruv@ruv.net>
…viewport (4.7)

Move FIT_TO_CONTENT_ZOOM_BY_LEVEL from canvasViewportStore into
@variscout/core/canvas/viewport.ts and re-export it through the barrel.
Add LOD_SNAP_BOUNDARIES (L2_OVERVIEW_LOW=0.5, L2_DETAIL_HIGH=1.8) for
the upcoming snap-to-LOD feature. Single source of truth for level math.

Co-Authored-By: ruflo <ruv@ruv.net>
…ce(6) (4.3)

Adds .clickDistance(6) to the d3-zoom behavior in useCanvasViewportInput.
Pointer moves ≤5px are treated as clicks; ≥6px as drags. Matches spec §6.3.

Co-Authored-By: ruflo <ruv@ruv.net>
…ort (4.5/4.6)

worldToWallSvg was an identity function with no callers outside its own test;
deleted function and test. CanvasViewport is used in Canvas/index.tsx — added
JSDoc comment explaining its role so the seam is documented.

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

Adds a 'end' listener to the zoom behavior. When the user releases the wheel
with zoom in [0.3, 0.5) or [1.8, 2.0), the viewport eases back to 0.5 or 1.8
respectively over 150ms. Exports snapTarget() for unit testing. LOD_SNAP_BOUNDARIES
lives in @variscout/core/canvas/viewport alongside LOD_THRESHOLDS.

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

LODSwitcher now renders both outgoing and incoming renderers in stacked
absolute divs during a 150ms window, then unmounts the outgoing. Uses
useState+useEffect+setTimeout — no external animation library needed.

useCanvasViewportInput snap-to-LOD uses d3-transition via
selection.transition().duration(150).call(zoomBehavior.transform, ...).
Adds d3-transition + @types/d3-transition to @variscout/hooks deps.

Tests: 4 LODSwitcher tests assert dual-render during transition and
single-render after 150ms via fake timers.

Co-Authored-By: ruflo <ruv@ruv.net>
Closes 8f followup HIGH #1 part 1/2 — adds loadBlobCanvasViewport +
saveBlobCanvasViewport mirroring the updateBlobEvidenceSnapshots
ETag-conditional pattern. Per ADR-081 §2 (Azure = IndexedDB + Blob sync
with ETag per ADR-079) and ADR-079.

Also adds getLocalViewportUpdatedAt to @variscout/stores so the Azure
lifecycle hook can compare timestamps without reading Dexie directly.

Co-Authored-By: ruflo <ruv@ruv.net>
Closes 8f followup HIGH #1 part 2/2 — useCanvasViewportLifecycle (Azure)
now rehydrates from Blob after Dexie cache, debounced-persists to both
Dexie and Blob with ETag, and treats precondition-failed as last-write-
wins per spec §11 with App Insights telemetry on the conflict path.

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

Closes 8f followup MEDIUM #9 — LocalMechanismView previously only
exposed Quick Action. Spec §5.3.a lists 5 CTAs at column-mechanism
granularity (Quick Action / Focused Investigation / IP / Sustainment /
Handoff). Threaded the 4 step-level callbacks already on CanvasProps
through to LocalMechanismView; per-column button row added with new
i18n keys (8 new MessageCatalog keys across 32 locale files). Parent
callbacks are step-only; column is visible only within the card row.

Co-Authored-By: ruflo <ruv@ruv.net>
Closes 8f followup MEDIUM #10 — MobileLevelPicker previously called
setLevel('l2') before setZoom(2.5) as an explicit l2 redirect comment
implied. The final committed state was already l3 (setZoom(2.5) fires
inferLevel→l3), but the intent was undocumented. Updated comment to
clarify the atomicity: both calls run synchronously before React
re-renders, so the final state is l3 with no focalStepId, and canvas
renders NoFocalStepPrompt (the step-list surface) per spec §7.

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

Closes 8f followup MEDIUM #11 — useCanvasViewportInput previously
subscribed to the whole canvasViewportStore; every unrelated mutation
(setRailOpen, setViewMode, openChartCluster, etc.) fired
syncElementToStoreViewport (which has its own diff-check guard, but
still did needless work). Now tracks prevViewportRef and short-circuits
on reference equality of state.viewports[hubId] — sync is skipped
entirely when the hub's viewport slice hasn't changed. Test added:
setRailOpen → d3 element __zoom unchanged.

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

Closes 8f followup LOW #15 — canvasViewportStore state is keyed by hubId
not projectId since the 8f shape change. The annotation-per-project
label was technically truthful (per-project umbrella, hub-keyed inside)
but invited confusion. Renamed to annotation-per-hub; layerBoundary
test + packages/stores/CLAUDE.md table updated. 'annotation-per-project'
is now in the reserved/unused set; 'annotation-per-hub' is live.

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

vercel Bot commented May 13, 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 13, 2026 9:49pm
variscout_website Ready Ready Preview, Comment May 13, 2026 9:49pm

@jukka-matti jukka-matti merged commit cd93691 into main May 14, 2026
3 checks passed
jukka-matti added a commit that referenced this pull request May 14, 2026
- investigations.md "8f followups" entry: STATUS → RESOLVED 2026-05-14, PR
  #166 squash-merged as cd93691. LOW #16 (Canvas/index.tsx 1122-line
  refactor) + LOW #19 (brand ProcessHubId) explicitly carried forward.
- investigations.md gains two new entries from the --chrome walk:
  (1) pre-existing setState-in-render warning from AppMain across canvas
  transitions (App.tsx untouched by #166), (2) designer-lens canvas
  journey-clarity observations (9 UX items: Frame→Canvas rename, desktop
  LODSwitcher parity, Lock-canvas mode-toggle relabeling, L1 capability
  empty-state copy, L3 CTA visual weight, etc.).
- decision-log.md "8f canvas viewport SHIPPED" entry: Amendment 2026-05-14
  followups complete block prepended; original 2026-05-13 amendment retained
  as history.

Co-Authored-By: ruflo <ruv@ruv.net>
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