Skip to content

Guided tutorial: make the spotlit layer actually visible, and separate forcing from ringing - #140

Merged
jon-bell merged 1 commit into
mainfrom
fix/tutorial-spotlight-visibility
Aug 12, 2026
Merged

Guided tutorial: make the spotlit layer actually visible, and separate forcing from ringing#140
jon-bell merged 1 commit into
mainfrom
fix/tutorial-spotlight-visibility

Conversation

@jon-bell

@jon-bell jon-bell commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Steps 7 (u4a-compare) and 8 (u4-patching) both need layer 20 on screen in each heatmap. Step 8 asked for it and appeared not to get it; step 7 never asked at all.

What was actually wrong

Not the spotlight mechanism. setTarget accepts arrays, per-grid resolution is correct, allLayers.indexOf(20) hits on the 32-layer model, and against the real Llama-3.1-8B tokenization position 5 really is " Tower" / "um" as the content claims. All verified.

The layer was being forced into the grid and then clipped out of view. Auto-fit sizes its column budget as if no spotlight existed, and the widget renders the spotlit layer on top of that set. With the tutorial docked at 1366×768 each grid gets a one-column budget, renders [0, 31], and the forced layer 20 lands past the scroll container's right edge — 380px of content in a 252px port.

Measured in Chromium against the real widget: 69% visible at the fielded 528px panel, 0% at 420px and on the result grid.

That it depends on panel width is why this read as intermittent, and why the previous fix (f3d193a) verified green — its harness used 80 layers at a width where three columns happened to fit.

The widget half is edulogitlens 82a7327 (jon-bell/edulogitlens#3), pinned here: auto-fit now budgets in rendered columns, and a grid scrolls its own container to bring a spotlit column into view. Ring visibility goes to 1.00 at every tested width.

Changes on this side

forceLayers on a unit. A spotlight does two jobs — it rings a cell, and it keeps auto-fit from downsampling that layer away. Step 7's task is to find the landmark row, so it needs the second job without the first; ringing on arrival would do the step's work for the participant. u4a-compare now forces layer 20 in both grids and still rings nothing until its stage-2 hint. The existing assertion that it has no unit-level spotlights stays true on purpose, with a sibling assertion for the new behavior.

One derived spotlight payload instead of three effects overwriting each other. This fixes two real bugs:

  • The hint-reveal write was imperative and nothing re-applied it, so any remount — a page reload, or collapsing and re-expanding the tutorial dock — dropped the rings while the hint still displayed as revealed. The revealed stage is already persisted, so resolveUnitSpotlights derives the whole payload from it. Extracted as a pure function and unit-tested directly (there's no React harness in this repo).
  • The arrival effect had no store.active guard, so closing the tutorial on a patch step re-lit its rings on a closed panel.

Validation. position is now optional, and forceLayers rejects an entry that carries one — a silently dropped position would leave the author waiting for a ring that never comes.

Verification

gate main this branch
scripts/test.sh 194 pass, 0 fail
tsc --noEmit 34 errors 34, none in touched files
bun run lint 35 err / 25 warn identical

Ring/column visibility confirmed in real Chromium at 32 and 80 layers across 420 / 528 / 538 / 801 / 809px panels, including the fielded 1366×768 docked layout. The no-spotlight control is byte-identical before and after.

Notes for review

  • Merge Causal mediation: spotlight, auto-fit, and the guided-tutorial widget surface jon-bell/edulogitlens#3 first — the pin here points at 82a7327 on spotlight-layer-reachable-v1.
  • The fielded tutorial.json at the repo root is untracked and has the matching forceLayers block; it still needs loading through the workshop admin UI. ensureSeedTutorial only inserts when absent and never updates, so any environment seeded before 60eea6a is still serving pre-unit-spotlight content regardless of this PR.
  • Known, not addressed: once a patch lands, three grids share the vertical space and the source/target rings fall below their fold. Pre-existing and unchanged; the result ring, where attention goes, went from 0% to fully visible.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Tutorial steps can now keep specific layers visible without highlighting individual cells.
    • Tutorial highlights now combine unit targets, revealed hints, and patch results consistently.
    • Layer-only spotlight targets are supported.
  • Bug Fixes

    • Improved spotlight behavior when tutorial steps remount or hints are revealed.
  • Tests

    • Added coverage for spotlight combinations, forced layers, validation rules, and compare-step behavior.

@vercel

vercel Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

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

Project Deployment Actions Updated (UTC)
workbench Ready Ready Preview Aug 12, 2026 4:39pm

Request Review

@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Tutorial spotlight handling now derives a single payload from unit targets, persisted hints, patch results, and forced layers. Tutorial validation and Prolific fixtures support layer-only targets. The edulogitlens dependency commit also changed.

Changes

Tutorial spotlight behavior

Layer / File(s) Summary
Spotlight contracts and resolution
workbench/_web/src/types/tutorial-content.ts, workbench/_web/src/types/__tests__/unitSpotlights.test.ts
SpotlightTarget.position is optional. ForceLayerTarget and TutorialUnit.forceLayers support layer-only targets. resolveUnitSpotlights combines forced layers, unit spotlights, revealed hints, and patch-result cells.
Tutorial content validation
workbench/_web/src/lib/queries/tutorialContentDb.ts, workbench/_web/src/db/__tests__/tutorials.test.ts
Validation accepts position-less spotlights and checks forced-layer grids, layers, array contents, and forbidden positions.
Activity panel spotlight integration
workbench/_web/src/app/workbench/[workspaceId]/patch-lens/[chartId]/components/tutorial/TutorialActivityPanel.tsx
The panel derives and applies one spotlight payload from active state, persisted hints, unit data, and patch state. Hint revelation no longer updates spotlights imperatively.
Compare-step forced layers
workbench/_web/src/tutorials/prolificSeed.ts, workbench/_web/src/tutorials/__tests__/prolificSeed.test.ts
The compare step forces source and target patch layers without highlighting cells. Tests verify the forced layers match the patch step.

Dependency reference update

Layer / File(s) Summary
edulogitlens commit reference
workbench/_web/package.json
The edulogitlens Git dependency now uses commit 82a7327 instead of fa59004.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

Suggested labels: preview-public

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main tutorial changes: visible spotlit layers and separate layer forcing from cell highlighting.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
⚔️ Resolve merge conflicts 💡
  • Resolve merge conflict in branch fix/tutorial-spotlight-visibility
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/tutorial-spotlight-visibility

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@workbench/_web/src/tutorials/__tests__/prolificSeed.test.ts`:
- Around line 136-139: Update the test around the forced spotlight comparison in
prolificSeed.test.ts to explicitly assert that every layer in forced has value
20, while preserving the existing parity check against u4-patching.

In `@workbench/_web/src/types/tutorial-content.ts`:
- Around line 294-300: Update the spotlight selection in the revealed hints
reduce logic to prioritize the plural spotlights field whenever it is present,
including when it is an empty array, instead of checking its length before
falling back to spotlight. Add a test covering both fields with spotlights: []
and confirming the plural field wins.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: f0c94526-f534-4f3f-8b69-e795eac88250

📥 Commits

Reviewing files that changed from the base of the PR and between 6b64cc7 and f4d5179.

⛔ Files ignored due to path filters (1)
  • workbench/_web/bun.lock is excluded by !**/*.lock
📒 Files selected for processing (8)
  • workbench/_web/package.json
  • workbench/_web/src/app/workbench/[workspaceId]/patch-lens/[chartId]/components/tutorial/TutorialActivityPanel.tsx
  • workbench/_web/src/db/__tests__/tutorials.test.ts
  • workbench/_web/src/lib/queries/tutorialContentDb.ts
  • workbench/_web/src/tutorials/__tests__/prolificSeed.test.ts
  • workbench/_web/src/tutorials/prolificSeed.ts
  • workbench/_web/src/types/__tests__/unitSpotlights.test.ts
  • workbench/_web/src/types/tutorial-content.ts

Comment on lines +136 to +139
// The same layer the next step drags across, so the participant has already
// looked at the column they are about to patch.
const patched = unit("u4-patching").spotlights ?? [];
expect(forced.map((f) => f.layer)).toEqual(patched.map((c) => c.layer));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Assert the required layer number.

The test checks parity with u4-patching, but it does not enforce the tutorial requirement that both compare grids render layer 20. If both fixtures change to another layer, this test still passes. Add an explicit f.layer === 20 assertion.

This follows the PR objective that u4a-compare must force layer 20 in both grids.

Proposed assertion
         const patched = unit("u4-patching").spotlights ?? [];
+        expect(forced.every((f) => f.layer === 20)).toBe(true);
         expect(forced.map((f) => f.layer)).toEqual(patched.map((c) => c.layer));
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// The same layer the next step drags across, so the participant has already
// looked at the column they are about to patch.
const patched = unit("u4-patching").spotlights ?? [];
expect(forced.map((f) => f.layer)).toEqual(patched.map((c) => c.layer));
// The same layer the next step drags across, so the participant has already
// looked at the column they are about to patch.
const patched = unit("u4-patching").spotlights ?? [];
expect(forced.every((f) => f.layer === 20)).toBe(true);
expect(forced.map((f) => f.layer)).toEqual(patched.map((c) => c.layer));
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@workbench/_web/src/tutorials/__tests__/prolificSeed.test.ts` around lines 136
- 139, Update the test around the forced spotlight comparison in
prolificSeed.test.ts to explicitly assert that every layer in forced has value
20, while preserving the existing parity check against u4-patching.

Comment on lines +294 to +300
const revealed = unit.hints
.filter((h) => h.stage <= hintStage)
.sort((a, b) => a.stage - b.stage)
.reduce<SpotlightTarget[]>((acc, h) => {
const rung = h.spotlights?.length ? h.spotlights : h.spotlight ? [h.spotlight] : [];
return rung.length ? rung : acc;
}, []);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Preserve plural spotlight precedence for empty arrays.

Line 298 treats spotlights: [] as absent. It then uses spotlight when both fields exist. This conflicts with the HintRung contract that spotlights wins over spotlight.

Select spotlights based on field presence, not array length. Add a test for an empty spotlights array with a singular spotlight.

Proposed fix
-            const rung = h.spotlights?.length ? h.spotlights : h.spotlight ? [h.spotlight] : [];
+            const rung = h.spotlights !== undefined ? h.spotlights : h.spotlight ? [h.spotlight] : [];
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const revealed = unit.hints
.filter((h) => h.stage <= hintStage)
.sort((a, b) => a.stage - b.stage)
.reduce<SpotlightTarget[]>((acc, h) => {
const rung = h.spotlights?.length ? h.spotlights : h.spotlight ? [h.spotlight] : [];
return rung.length ? rung : acc;
}, []);
const revealed = unit.hints
.filter((h) => h.stage <= hintStage)
.sort((a, b) => a.stage - b.stage)
.reduce<SpotlightTarget[]>((acc, h) => {
const rung = h.spotlights !== undefined ? h.spotlights : h.spotlight ? [h.spotlight] : [];
return rung.length ? rung : acc;
}, []);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@workbench/_web/src/types/tutorial-content.ts` around lines 294 - 300, Update
the spotlight selection in the revealed hints reduce logic to prioritize the
plural spotlights field whenever it is present, including when it is an empty
array, instead of checking its length before falling back to spotlight. Add a
test covering both fields with spotlights: [] and confirming the plural field
wins.

… from ringing

Steps 7 and 8 both need layer 20 on screen in each heatmap. Step 8 asked for it
and appeared not to get it; step 7 never asked at all. Neither was the mechanism
failing — the emission side is sound, `setTarget` takes arrays, and against the
real Llama-3.1-8B tokenization position 5 is " Tower" / "um" as the content
claims.

The layer WAS being forced into the grid. It was then clipped out of view.
Auto-fit sizes its column budget as if no spotlight existed, and the widget
renders the spotlit layer on top of that set — so with the tutorial docked at
1366x768 each grid gets a one-column budget, shows [0, 31], and the forced layer
20 lands past the scroll container's right edge. Measured at 69% visible, and 0%
at a 420px panel. That is width-dependent, which is why it read as intermittent
and why an 80-layer harness (f3d193a) showed it passing.

The widget half is edulogitlens 82a7327, pinned here: auto-fit now budgets in
rendered columns, and a grid scrolls its own container to bring a spotlit column
into view. Ring visibility goes to 1.00 at every tested width.

Three changes on this side:

- `forceLayers` on a unit. A spotlight does two jobs — it rings a cell and it
  keeps auto-fit from downsampling that layer away — and step 7's task is to
  FIND the landmark row, so it needs the second job without the first. Ringing
  the cells on arrival would do the step's work for the participant. u4a-compare
  now forces layer 20 in both grids and still rings nothing until its stage-2
  hint; the assertion that it has no unit `spotlights` stays true on purpose.

- One derived spotlight payload instead of three effects overwriting each other.
  The hint-reveal write was imperative and nothing re-applied it, so any remount
  — a reload, or collapsing and re-expanding the dock, which unmounts the column
  the panel portals into — dropped the rings while the hint still read as
  revealed. The revealed stage is persisted, so `resolveUnitSpotlights` derives
  the whole payload from it and is unit-tested directly.

- The arrival effect had no `active` guard, so closing the tutorial on a patch
  step re-lit its rings on a closed panel.

Validation stays looser than the code, so `position` is now optional there too
and `forceLayers` rejects an entry carrying one — a dropped position would have
the author waiting for a ring that never comes.

Verified: 194 tests pass; tsc (34 errors) and lint (35/25) unchanged from main.
Ring visibility confirmed in Chromium against the real widget at 32 and 80
layers across 420/528/538/801/809px panels.

Known, not addressed: once a patch lands, three grids share the vertical space
and the source/target rings fall below their fold — pre-existing, and the result
ring is now fully visible where it was 0%.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@argos-ci

argos-ci Bot commented Aug 12, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Argos notifications ↗︎

Build Status Details Updated (UTC)
default (Inspect) 👍 Approved by Jonathan Bell 1 changed Aug 12, 2026, 4:43 PM

@github-actions

github-actions Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

🧹 Preview for PR #140 torn down.

@jon-bell
jon-bell merged commit b7c22df into main Aug 12, 2026
8 checks passed
@jon-bell
jon-bell deleted the fix/tutorial-spotlight-visibility branch August 12, 2026 18:37
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