Conversation
🎩 PreviewA preview build has been created at: |
d94ab24 to
3962370
Compare
The assistant could not see a single word written on a sticky note, and had no way to leave one. Notes are the one canvas element whose whole purpose is to carry human intent — "don't touch this branch", "this threshold came from the Q3 eval" — so it would restructure a canvas around annotation content someone had placed deliberately, without ever knowing it was there. Notes now appear in the pipeline state the assistant reads, with their text, colours, size, position, lock state and author, at the top level and inside every subgraph. It can also add, edit and delete them. A note can be placed anywhere, and the prompt treats that as the normal case: notes are freeform, and plenty of them are about no single step — a heading over a region, a caveat about a whole branch, a reminder parked in empty space. Explicit coordinates are the general path, and work in any graph including empty canvas far from every node. Anchoring a note to a task, input or output is offered as a shortcut for the narrower case where the note really is about that one thing, so the assistant does not have to work out coordinates for it. Its own notes are authored as "AI assistant" rather than "user", which is both visible in the details panel and the signal the prompts use: anything the user wrote is theirs, and it is told not to reword, recolour, move or delete it unless asked. Locked notes are refused outright. Colours are restricted to the values the picker can produce, so it cannot write a note that renders as nothing. Two smaller corrections in the same area. A task the assistant added could land on top of an existing note, because the placement logic only looked at tasks and ports; it now accounts for notes. And a sticky note mentioned in chat now renders as a working chip that navigates to it, like every other entity link. The run view gains the read but not the writes, so the debug assistant can use a note as context for a failure while the spec stays read-only there. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The bridge returns the serialized spec to the agent worker over Comlink, so
the payload has to survive postMessage's structured clone. Several values
read straight off the keystone models are MobX observables, which throw
DataCloneError there and take the whole get_pipeline_state call down with
them — the agent then reports "the state tool returned an error" and carries
on blind, which is exactly the failure mode this stack is meant to remove.
Three field classes could trigger it, none of them obvious:
- a dynamicData argument value (secrets / system data), which the
deserializer keeps as an object rather than turning into a binding
- a structured TypeSpecType on an input, output or component port
- an array out of an annotation codec
toJS is not a fix: the returned object is a plain one, and MobX only recurses
into observable containers. A JSON round-trip is lossless for this shape,
since AiSpec is declared plain JSON and pickDefined has already dropped every
undefined.
Guarded with a test that fails without the fix.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
3962370 to
0e8d879
Compare
camielvs
left a comment
There was a problem hiding this comment.
Correctness pass over the branch. tsc --noEmit is clean and all four touched test files pass (112 tests).
The toPlainJson structured-clone fix checks out — no mobx-keystone models reach AiSpec, so the JSON round-trip is lossless as described. Also verified: subgraph note coverage via getSubgraphState reusing the same serializer, EntityChip's "flex" kind against NodeEntityType and selectNode, RunView read-only handlers covering all three new mutations, allTools wiring into architect/repair with the deliberate omission from debugAssistant, the annotation codec always being applied on deserialize (so getFlexNodes never returns a raw string), nested undo.withGroup usage, the locked-note refusal and documented locked: false unlock path, and colour validation on both add and update.
Two findings inline. The first is one instance of a trap that also shows up twice in #2767: the codebase treats (0,0) as "never placed" (buildUtils.resolvePosition substitutes taskDefaultPosition(index)), but Annotations.get("editor.position") returns the codec default {x:0,y:0} rather than undefined. Anything that branches on the raw annotation to decide whether a node has a position gets the wrong answer. Might be worth one shared helper across the stack rather than three separate fixes.
Anchored notes landed at (0,-140) on any pipeline with no saved layout:
`Annotations.get("editor.position")` returns the codec default {x:0,y:0}
rather than undefined, so `resolveNoteAnchor`'s fallback branch was dead
and the anchor read as placed at the origin.
Hoist the canvas's own id -> position map out of ConduitNode into
`resolveEntityPositions`, and read anchors and `computeNextPosition`
through it, so the AI-side paths see the positions the user sees.
Also make `position` actually take precedence over `anchorEntityId` as
the tool schema promises — it previously set the coordinates but left
the note in the anchor's subgraph.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
b25ab70 to
0f8b05a
Compare
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Mbeaulne
left a comment
There was a problem hiding this comment.
AI comment. beyond that this is good
The canvas clamps user resizing to 50x50, but the AI tools persisted whatever size they were given, so a note could collapse to a size that is hard to select or edit. Share the minimum as MIN_FLEX_NODE_SIZE and refuse an undersized note in the bridge, matching how colour validation already works. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Description
The assistant could not see a single word written on a sticky note, and had no way to
leave one. Notes are the one canvas element whose whole purpose is to carry human
intent — "don't touch this branch", "this threshold came from the Q3 eval" — so it
would restructure a canvas around annotation content someone had placed deliberately,
without ever knowing it was there.
Notes now appear in the pipeline state the assistant reads, with their text, colours,
size, position, lock state and author, at the top level and inside every subgraph. It
can also add, edit and delete them.
A note can be placed anywhere, and the prompt treats that as the normal case. Notes
are freeform, and plenty of them are about no single step — a heading over a region, a
caveat about a whole branch, a reminder parked in empty space off to the side. Explicit
coordinates are the general path and work in any graph, including empty canvas far from
every node. Anchoring a note to a task, input or output is offered as a shortcut for the
narrower case where the note really is about that one thing, so the assistant does not
have to work out coordinates for it — not as the default shape of a note.
Its own notes are authored as AI assistant rather than user, which is both
visible in the details panel and the signal the prompts use: anything the user wrote is
theirs, and it is told not to reword, recolour, move or delete it unless asked. Locked
notes are refused outright. Colours are restricted to the values the picker can produce,
so it cannot write a note that renders as nothing.
Two smaller corrections in the same area. A task the assistant added could land on top
of an existing note, because the placement logic only looked at tasks and ports; it now
accounts for notes. And a sticky note mentioned in chat now renders as a working chip
that navigates to it, like every other entity link.
The run view gains the read but not the writes, so the debug assistant can use a note as
context for a failure while the spec stays read-only there.
Related Issue and Pull requests
Bottom of a five-PR stack closing the assistant's canvas-awareness gaps:
this → canvas layout → update input/output → pipeline notes & tags → task colours.
Type of Change
Checklist
Test Instructions
this canvas say?" — it should quote them, not say it cannot see any.
Author "AI assistant" in the details panel, and be one Cmd/Ctrl+Z away from gone.
subgraph only, and not appear at the top level.
explain and offer the swatches rather than writing an invisible note.
select that note (including one inside a subgraph).
step should not land on top of the note.
the read-only message.
Also in here:
get_pipeline_statewas failing outrightFound while testing the stack in the app — the assistant reported "the state tool
returned an error" and then went on to edit the canvas blind, which is the exact
failure this stack exists to remove.
The bridge returns the serialized spec to the agent worker over Comlink, so the payload
has to survive
postMessage's structured clone. Several values read straight off thekeystone models are MobX observables, which throw
DataCloneErrorthere and take thewhole call down. Three field classes could trigger it, none of them obvious:
dynamicDataargument value (secrets / system data) — the deserializer keeps theseas objects rather than turning them into bindings
TypeSpecTypeon an input, output or component portThe first two are pre-existing on
master; I confirmed it by running the same checkthere. The fix is one conversion at the serializer's boundary, which is where the
module's own contract already claims the result is plain JSON.
toJSdoes not workhere — the returned object is a plain one, and MobX only recurses into observable
containers.
It sits at the bottom of the stack so every branch above inherits it, and it is guarded
by a test that fails without it.
🤖 Generated with Claude Code