CLUE-620: make the sketch tile a highlight target - #2956
Open
kswenson wants to merge 4 commits into
Open
Conversation
Drawing objects can be variable-bound: VariableChipObject stores a variableId outright, so the text tile's variable chip already points at them. Implementing getObjectsForVariable is all that was needed to reach them -- no new source, and no changes to the core highlight machinery. That also makes this a better reference implementation than Dataflow, which matches a derived string and so loses the association when a variable is renamed. The ring is drawn at layer level from getObjectBoundingBox rather than reusing renderSelectionBorders, which reads the object's own untransformed box and would misplace a ring on a group member. It renders outside drawing-layer's readOnly gate: that gate belongs to the editing affordances, while a highlight directs attention and must show in read-only documents, 4-up cells and thumbnails. Ring colors move to components/highlight-vars.scss, shared with the Dataflow node rings so one reference reads the same way wherever it lands. The demo fixture gains a Sketch tile with EMG and Gripper chips; asserting the Gripper chip stays dark is what proves the highlight discriminates rather than lighting every variable chip. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The highlight spec passed while the feature did nothing on screen, for two independent reasons, both of which made the test agree with the runner rather than with reality. The doc-editor restores a document from sessionStorage and builds a model from it, then replaces that model once the document= param finishes loading. Tile types that register lazily can stay bound to the superseded instance while the eagerly-present tiles move to the new one, leaving two document-content instances in a single pane. Highlight refs are volatile and per-document, so they cannot travel between them. Cypress starts with clean session storage and so never hits this; a browser tab open for a while hits it immediately. Pinning noStorage makes the spec depend on a stated condition. Filed as CLUE-626. It also renders up to three copies of the document, and the read-only remote copy is rebuilt from a snapshot on every change -- continuous, for a fixture with a running Simulator. Unscoped selectors were therefore satisfied by a ring in any copy. Disabling both read-only copies makes every assertion refer to the one document a person interacts with. Turning those off removed the sessionStorage the chip-deletion test had been leaning on between tests. It now selects the paragraph with a triple-click rather than Cmd+A, whose scope depends on whether focus landed on the tile or inside Slate: it selects nothing in one case and every tile in the document in the other. CLAUDE.md said the three panes share one content model. They do not. CLAUDE.md also picks up three testing notes learned while working CLUE-603: dispatching a single spec via the Manual Regression workflow, the /editor/ route's multi-pane rendering, and how branch preview URLs drop the ticket prefix. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## CLUE-603-linked-representation-references #2956 +/- ##
=============================================================================
- Coverage 86.14% 86.14% -0.01%
=============================================================================
Files 982 982
Lines 56076 56117 +41
Branches 14791 14802 +11
=============================================================================
+ Hits 48306 48341 +35
- Misses 7750 7756 +6
Partials 20 20
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
collaborative-learning
|
||||||||||||||||||||||||||||
| Project |
collaborative-learning
|
| Branch Review |
CLUE-620-highlight-drawing-tile
|
| Run status |
|
| Run duration | 03m 07s |
| Commit |
|
| Committer | Kirk Swenson |
| View all properties for this run ↗︎ | |
| Test results | |
|---|---|
|
|
0
|
|
|
0
|
|
|
0
|
|
|
0
|
|
|
4
|
| View all changes introduced in this branch ↗︎ | |
Contributor
There was a problem hiding this comment.
Pull request overview
Adds Sketch tile variable chips as highlight targets alongside Dataflow nodes.
Changes:
- Resolves and renders highlighted Sketch variable chips.
- Shares highlight colors across target tiles.
- Adds unit/Cypress coverage and improves
/editor/test isolation guidance.
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
src/public/demo/docs/emg-highlight-demo.json |
Adds the Sketch highlight fixture. |
src/plugins/shared-variables/drawing/variable-highlight.test.ts |
Tests variable target resolution. |
src/plugins/drawing/model/drawing-content.ts |
Exposes matching variable chips. |
src/plugins/drawing/components/drawing-tile.scss |
Styles Sketch highlight rings. |
src/plugins/drawing/components/drawing-layer.tsx |
Renders highlight borders. |
src/plugins/drawing/components/drawing-layer-highlight.test.ts |
Tests highlight collection. |
src/plugins/dataflow/nodes/node-states.scss |
Uses shared highlight colors. |
src/components/highlight-vars.scss |
Defines shared ring colors. |
docs/highlights.md |
Documents Sketch targeting. |
cypress/e2e/functional/tile_tests/highlight_references_spec.js |
Adds end-to-end Sketch coverage. |
CLAUDE.md |
Documents Cypress/editor behavior. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
conditionallyRenderObject omits an object when it is neither selected nor visible, so hiding a variable chip from the object list panel and then hovering the text chip drew a ring around empty space. docs/highlights.md already claimed hidden objects simply show nothing; this makes that true. The rule is deliberately not just `visible`: a hidden object that is selected still renders, and should still be ringed. Also scopes the CLAUDE.md warning about cross-tile state on the doc-editor route. It said such behavior cannot be verified there at all, which was written before the sessionStorage cause was found and contradicted the noStorage guidance a few lines above -- steering readers away from the setup this spec actually uses. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The earlier note credited the panes with splitting tiles across document instances. They do not. The editable pane and the Read Only Local copy share one document; only the Read Only Remote copy is separate, and deliberately so -- it is rebuilt from a snapshot to emulate a remote client. The extra instances that instrumentation found came from the sessionStorage restore-then-replace, which is the thing that actually breaks cross-tile ephemeral state. Verified after the fact: the branch deploy shows all three panes and the feature works there, because the URL passes noStorage=true. So noStorage is what matters for correctness, and disabling the read-only copies only makes unscoped assertions refer to the pane a person is looking at. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Makes the Sketch tile a highlight target. Hovering a variable chip in a Text tile now lights up the sketch's chip for that variable, alongside the Dataflow nodes CLUE-603 already highlighted.
Jira: CLUE-620
Based on #2953 (CLUE-603) and targeted at that branch, so the diff here is only the CLUE-620 work. Retarget to
masteronce #2953 merges.Why this turned out cheap
The CLUE-603 design deferred this increment behind CLUE-598, reasoning that "nothing can point at a sketch object until the AI can emit object references." That holds for rectangles and freehand strokes — but not for the drawing tile's variable chips.
VariableChipObjectstores avariableIdoutright, so CLUE-603's text chip already points at them. No new source, no CLUE-598 dependency, and no changes to the core highlight machinery.The whole tile-side contribution is six lines:
It also makes a better reference implementation than Dataflow, which matches a derived string (
"SIM" + variable.name) and therefore loses the association when a variable is renamed. The drawing tile stores the id and has no such flaw.Notable decisions
The read-only gate is untouched.
drawing-layer.tsxgates all highlighting off withif (!this.props.readOnly). That gate belongs to the editing affordances — canvas hover, object-list row hover — which are meaningless when you cannot edit. A highlight directs attention and must show in read-only documents, 4-up cells and thumbnails, as text chips and Dataflow nodes already do. So the highlight renders as a sibling outside the gate rather than changing it, andhighlightObjectkeeps itsstring | nullcontract. No existing behavior moves.renderSelectionBordersis deliberately not reused. It readsobject.boundingBox, the object's box in its own coordinate space. An object inside aGroupObjectrenders within the group'sscale()transform, so a layer-level ring drawn from the raw box lands in the wrong place. The highlight path usesgetObjectBoundingBox, which walks enclosing groups. Same reason the render enumeratesobjectMaprather thanobjects:objectsis top-level only, so a chip nested in a group would resolve as a target and never draw.Ring colors are shared, not copied.
src/components/highlight-vars.scssnow holds$highlight-preview-ringand$highlight-pinned-ring, consumed by both the sketch ring and the Dataflow node rings, so one reference reads the same way wherever it lands. Only geometry stays local — stroke width and dash length divide by the current zoom, which CSS cannot do. The accessibility rationale for the preview color (3.0:1 against a white canvas, per WCAG 1.4.11) moved into that file with the values.Testing
getObjectsForVariable(matching, multiple chips, non-variable objects, no match);collectHighlightedObjects; plus composition guards asserting the tile is reachable from a document-level variable reference at all, which depends on drawing-content routing throughtileContentAPIViews.highlight_references_spec.jsgains a test that hover previews and click pins the sketch chip. The fixture's second chip (Gripper) is load-bearing: the feature exists to direct attention to one thing, so asserting the Gripper chip stays dark is what proves the highlight discriminates rather than lighting every variable chip.check:typesclean,lint:buildclean.CI
The regression run has one failure:
document_tests/tiles_copy_test_spec.js→ "Verifies copy button states based on tile selection", asserting.tile-rowhas length 32 and finding 22.Known flaky and unrelated to this PR. It has been failing the same way on other PRs. Please disregard it when reviewing.
The second commit, and why it is here
The cypress spec passed while the feature did nothing on screen. Two independent harness problems, both of which made the test agree with the runner rather than with reality:
sessionStorage, then replaces that model oncedocument=finishes loading. Lazily-registered tile types can stay bound to the superseded instance while eagerly-present tiles move to the new one — two document-content instances in one pane. Highlight refs are volatile and per-document, so they cannot travel between them. Cypress starts with clean session storage and never hits it; a browser tab open for a while hits it immediately.The spec now pins
noStorage=trueand disables both read-only copies, so every assertion refers to the one document a person interacts with. That removed the session storage the chip-deletion test had been leaning on between tests; it now selects the paragraph with a triple-click rather thanCmd+A, whose scope depends on whether focus landed on the tile or inside Slate — selecting nothing in one case and every tile in the document in the other.CLAUDE.mdclaimed the three panes share one content model. They do not; corrected, with the diagnosis recorded so nobody re-derives it.Bugs filed along the way
Neither is a regression from this change, and neither blocks it.
VariableChipObjectextendsDrawingObjectrather thanSizedObjectand never implementssetUnrotatedDragBounds, whichcreateGroupcalls. Reachable from the Group toolbar button, which does not filter variable chips out of the selection.Reviewer notes
docs/highlights.mdis updated with the sketch tile as the second target implementation, and reframes the variable-only restriction as a source limitation — theobjectkind is fully resolved but has no producer, because a variable chip is the only way a user can currently author a cross-tile reference. Expect it to dissolve when AI-emitted references land, rather than be fixed.Try it on the branch deploy — hover the EMG chip in the text tile. The
sketch's EMG chip and the Dataflow Sensor node should ring together, and the sketch's Gripper
chip should stay dark; that last part is the point of the feature, not a detail. Click to pin,
click again to unpin.
The
noStorage=truein that link is required rather than decorative — without it you may hitCLUE-626 and watch the sketch do nothing, which is the dead end this PR's second commit exists
to prevent. (Relative
unit=anddocument=params resolve against the branch root viagetAssetUrl, so they work on the deploy as-is.)🤖 Generated with Claude Code