Skip to content

Improve agent recovery, integrations, and visual editing - #2498

Merged
steve8708 merged 31 commits into
mainfrom
changes-536
Jul 29, 2026
Merged

Improve agent recovery, integrations, and visual editing#2498
steve8708 merged 31 commits into
mainfrom
changes-536

Conversation

@steve8708

Copy link
Copy Markdown
Contributor

Summary

  • Improve Design visual editing across live iframe access, responsive Interact, pan and zoom, selection and hover bridges, undo and redo synchronization, and React source locations.
  • Strengthen agent run recovery, continuation telemetry, and chat recovery behavior.
  • Improve workspace organization URLs, MCP integration UX, and Analytics provider reliability.

Current status

This is the initial visibility PR Steve requested while the visual-edit completion workflow continues. Follow-up commits are expected for the remaining Design E2E gaps before final shipping and merge.

The current Design snapshot has 3,544 tests passing and a clean TypeScript check from the active workflow. GitHub Actions is the authoritative validation for the combined branch.

@netlify

This comment has been minimized.

@netlify

This comment has been minimized.

@netlify

This comment has been minimized.

@netlify

This comment has been minimized.

@netlify

This comment has been minimized.

@netlify

This comment has been minimized.

@netlify

This comment has been minimized.

@netlify

This comment has been minimized.

@netlify

This comment has been minimized.

@netlify

This comment has been minimized.

@netlify

This comment has been minimized.

@builder-io-integration builder-io-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Builder reviewed your changes and found 3 potential issues 🔴

Review Details

Code Review Summary

This large PR strengthens agent run recovery and terminal telemetry, adds workspace URL/org and MCP integration behavior, improves analytics reliability, and substantially expands Design visual-editing access and interaction flows. The overall direction is thoughtful: terminal-state ordering is covered by focused tests, migrations are additive, and the new request-context/loopback plumbing is explicit. I classified this as High risk because the change introduces session handoff and access-control behavior around a local visual editor.

Key Findings

  • 🔴 HIGH — A visual-edit action returns a normal bearer session token in a URL that can be retained or exposed through agent/tool transcripts.
  • 🔴 HIGH — Server-side loopback peer detection can treat remote tunnel visitors as local editors and expose write-capable preview access.
  • 🟡 MEDIUM — Terminal telemetry may be emitted even when terminal status persistence and reconciliation both fail, producing an analytics event that claims a durable terminal state that is not actually stored.

The recovery and telemetry changes otherwise show good attention to ordering, failure isolation, and test coverage. 🧪 Browser testing: Will run after this review (PR touches UI code)

Comment thread templates/design/actions/open-visual-edit.ts Outdated
Comment thread templates/design/server/lib/design-data-access.ts Outdated
Comment thread packages/core/src/agent/run-manager.ts Outdated
…rtRun options

The bridge guard requires each *.bridge.ts to have a committed .generated
counterpart; source-location.bridge.ts was missing one. .generated is
gitignored but its bridge artifacts are force-added by convention.

webhook-handler builds runOptions once and deliberately mutates it after
handing it to startRun (model/engineName are filled in inside the run
callback so run-manager's terminal event can read them in .finally()), and
it carries attemptCount from construction. The exact-object assertions were
the stale side; these two tests are about which soft-timeout ceiling is
chosen, so they now pin those fields via objectContaining.
@github-actions

github-actions Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Here's a visual recap of what changed:

Visual recap

Open the full interactive recap

Large diff — this recap is a summarized view (top files + schema/API deltas).

@builder-io-integration builder-io-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Builder reviewed your changes and found 1 potential issue 🟡

Review Details

Code Review Summary

PR #2498 is a large cross-cutting update covering Design visual editing, agent continuation/recovery, workspace routing, MCP/data-source integrations, and terminal observability. The approach is generally sound: the new recovery state is explicit about transient continuation budgets, organization URLs are normalized before comparison, provider connection failures preserve an unknown state rather than falsely reporting disconnection, and the localhost visual-edit authorization now combines loopback origin with localhost source. I also verified that the previously reported loopback authorization issue and terminal telemetry ordering issue are fixed and resolved those stale threads; the bearer session token embedded in the visual-edit action link remains the existing open issue and was not reposted.

Key Findings

🟡 MEDIUM — The cookie-preserving _session redirect does not preserve the staged Referrer-Policy: no-referrer header, allowing the live session token in the source URL to be exposed as a referrer to the destination document/server.

The PR is high risk because it changes authentication/session handling and security-sensitive visual-edit access. Apart from the finding above, the reviewed changes show good attention to failure-state honesty, scoped access, and persistence ordering.

🧪 Browser testing: Will run after this review (PR touches UI code)

Comment thread packages/core/src/server/open-route.ts
…ource anchors, board drop

- resolveServerFiles: design?.files ?? EMPTY constant, killing the render loop
  that fired 1000+/min (setPendingNodeRewriteProposals([]) re-entering on a
  fresh [] identity while the design query was still pending)
- layer lock/hide now reach the live frame via the layer-states message
- delete resolves live selectors for in-canvas selections
- React source anchors read _debugSource (<=18) and _debugStack (19) plus
  owner/ownerKey to disambiguate .map() siblings
- board primitive -> live screen structural insert path
@netlify

This comment has been minimized.

@netlify

This comment has been minimized.

@netlify

This comment has been minimized.

@builder-io-integration builder-io-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Builder reviewed your changes and found 1 potential issue 🟡

Review Details

Code Review Summary

This increment expands PR #2498 with additional Design live-layer operations, deletion handling, render-loop stabilization, and continued recovery/integration work. The broader approach remains well structured: live snapshots now use real history entries, empty design-file identity is stabilized, and the new layer-state paths distinguish preview-only handoffs from unresolved runtime anchors.

Key Findings

🟡 MEDIUM — Breakpoint-scoped deletion still structurally removes the selected node from the live iframe. The persisted snapshot receives a breakpoint-scoped display: none patch, but the live-preview path calls deleteFromLiveDom, so the node disappears at all breakpoints until the iframe is reloaded or fully synchronized.

I verified that the previously reported bearer-token and no-referrer findings remain present and left both existing comments untouched. The previously reported loopback authorization concern was also not reposted because it is the same issue at the same location rather than a distinct regression. The MCP scope concern was not included because the dialog resets scope when its validated default changes, so the reported stale-state scenario was not confirmed.

The PR remains high risk because it includes authentication/session handling and security-sensitive visual-edit access. Browser testing is planned for the affected Design flows.

🧪 Browser testing: Will run after this review (PR touches UI code)

Comment thread templates/design/app/pages/DesignEditor.tsx Outdated
…e delete, label anchor precision

- appendCanvasPrimitiveToHtml/insertClonedHtmlLayers fail closed on URL-shaped
  content: a localhost screen's file.content IS its route URL, and parsing it as
  HTML replaced the route with a static document, silently destroying the
  screen's live binding. Draw/paste/duplicate/image-drop now refuse loudly and
  name the working path instead.
- locked layers paint a neutral dashed outline via the same layer-states message
  that already carried hidden, so lock is no longer invisible
- delete records a pending live edit and accepts layers-panel selections
- React source anchors carry their provenance: a React 19 _debugStack position is
  a transformed line, not an authored one, and no longer claims otherwise
… complete insert lifecycle

- replacePreviewContent and handleDesignStateSelect refuse when the content is a
  standalone http URL: a localhost screen's file.content IS its route URL, and
  pushing it as document content silently replaced the running app with that text
  in single view (survived hard refresh, layers went empty, no error)
- Edit/Annotate chosen from a focused screen now return to the canvas instead of
  stranding a single-screen Edit state; toolbar hidden while Interact is active
- insert lifecycle: Apply preflight exempts inserts (no subject anchor by
  definition), redo reissues runtime-structure-insert rather than a move that
  cannot find its subject, and delete cancels the matching pending insertion
- owner source provenance threaded through to the agent handoff; execution-mode
  routing narrowed to the board->live-screen case
…d writes

- postOneShotBridgeMessage returned true while silently queueing forever: a
  live-edit screen keeps its iframe across a canvas remount, so the replacement
  DesignCanvas never saw editor-chrome-ready and recovery was passive. It now
  actively pokes the frame so the queue drains. A call that reports success and
  delivers nothing is the failure shape this repo's rules single out.
- commitVisualStyles owns the localhost branch: push to the live DOM, then queue
  a pending edit. Inspector style commits previously updated only the snapshot
  and undo stacks, which is why the value appeared solely on redo.
- handleCreatePrimitive stamps baseUpdatedAt on the optimistic write; without it
  the pending overlay retired on content equality and an in-flight get-design
  reverted the board, so a drawn primitive vanished until reload.
@sidmohanty11

Copy link
Copy Markdown
Contributor

there are already some changes to fix this issue where it overflows the texts on the screen:
image

Follow-up on the $& bridge-truncation fix

The diagnosis here is exactly right, and the DesignCanvas.tsx change is the
load-bearing one — that's the injection that plants the corruption. I hit this
independently (screens rendering ~320KB of editor-chrome source as a single
text node in overview mode) and landed on the same root cause, so this is
confirmation rather than a counter-proposal.

Three things I think are worth picking up before this ships, in priority order.


1. Two of the three new tests pass on the unfixed code

The appender tests assert that $-patterns in the subject html aren't
substituted — but subject-side $& was never the hazard. String.replace only
interprets $ sequences in the replacement argument. Both appenders take a
module-level constant as their replacement (CONTENT_SIZE_REPORT_BRIDGE,
LIGHTWEIGHT_HIT_TEST_BRIDGE_SCRIPT), neither of which contains $&, so the
function-replacer change is defensive-only there and the tests are tautological:

const priorScript = '<script>var re = "\\\\$&-$\'-$`";</script>';
const input = `<html><body>${priorScript}</body></html>`;

// string replacement (pre-PR)   -> toContain(priorScript)=true, bodyCount=1 -> PASSES
// function replacement (this PR) -> toContain(priorScript)=true, bodyCount=1 -> PASSES

A test that fails before the fix has to ent side,
which only DesignCanvas.tsx does:

const bridge = `<script>var esc = String\\\$&");</script>`;

const out = `<html><body><h1>hi</h1></body></html>`
  .replace("</body>", bridge + "</body>"

expect(out).toContain('"\\\\$&"');                 // fails pre-PR: became "\\</body>"

DesignCanvas.embedded-frame-live.test.tsx does cover the real thing end-to-end 
that one's great. The other two could either move to the replacement side or be
dropped as no-ops.

Related nit: only editor-chrome.generatey one, in
escapeIdent). The other nine compiled br

editor-chrome.generated.ts  1
hit-test.generated.ts       0     <- hittes it here
(+ 8 more, all 0)

So the comment in hit-test.ts ("the hit-test bridge's compiled source can
contain literal $&") points at the wrong bridge. The hazard is real but
inherited  it arrives via the html argu's own source.

Also, for a string pattern $1–$9 are not substituted (only $&, $`,
$', $$ are; $1 needs a regex with groups

"a</body>b".replace("</body>", "$1|$&")

  1. First-occurrence targeting is still there

Function replacers stop $& from expandinhich
occurrence is matched. replace("", …) still takes the first one, so a
screen whose own JavaScript contains the literal string "" reproduces a
sibling failure with no $& involved — thde that
script, where it never executes:

const screen = `<html><body><script>var /body></html>`;
const out = screen.replace("</body>", () => BRIDGE + "</body>");   // this PR's shape

<!-- result: bridge nested in the screenhe bridge's own </script> -->
<script>var tpl = "<script data-an-bridgipt>

Measured: bridge nested inside the screehe editor
bridge never runs, so no selection/hoverext in the DOM.
Rarer trigger than the $& path (needs a code sample, an innerHTML template, or
a document.write demo in the design), but the same root and the same symptom
class.

Index arithmetic against the last close tag removes both failure modes at
once, and needs no $-awareness from any

/**
 * Splice markup in at the end of a docu: `$&`/`` $` ``/`$'`
 * in the markup are substitution patterns, and first-match aims at any "</body>"
 * inside the document's own scripts.
 */
export function injectBeforeDocumentClosring): string {
  if (!markup) return html;
  const lower = html.toLowerCase();
  const bodyClose = lower.lastIndexOf("<
  if (bodyClose !== -1) return html.slic html.slice(bodyClose);
  const htmlClose = lower.lastIndexOf("<
  if (htmlClose !== -1) return html.slic html.slice(htmlClose);
  return html + markup;
}

(For the first match is the correct target — a stray "" string
in a body script sits after the real one — so that wants a sibling helper rather
than the same one.)

shader-fills.ts, board-file.ts, and design-export.ts already splice by index
with lastIndexOf; the broken sites are teplace. One
shared helper would make the correct sha


  1. Six sibling call sites outside this P

Same shape, verified absent from this PR's 211 files:

Site: packages/core/src/cli/design-connect.ts:1341
Exposure: Localhost visual-edit bridge — identical $& bug, so visual-edit against a running app still
truncates the same editor-chrome bridge. Highest-value one to fix with this PR.
────────────────────────────────────────
Site: templates/design/actions/migrate-board-objects-to-file.ts:185
Exposure: Writes persisted HTML: a $&/$` in a board object's markup corrupts the saved board file ($`
inserts the whole preceding document).
────────────────────────────────────────
Site: templates/design/server/routes/[...page].get.ts:52
Exposure: SSR agent-context script — a $&/$' in a resource title/description mangles the discovery JSON.
────────────────────────────────────────
Site: templates/plan/app/lib/plan-annotation-runtime.ts:1422
Exposure: Plan's annotation runtime, injected the same way.
────────────────────────────────────────
Site: templates/plan/server/routes/[...page].get.ts:62,64
Exposure: Same SSR shape.
────────────────────────────────────────
Site: templates/analytics/server/routes/[...page].get.ts:144,147
Exposure: Same SSR shape.

Plus two low-risk CSS injections (board-surface-html.ts:326,
embedded-frame.ts:41) where the replacement is a <style> block.

Plus two low-risk CSS injections (board-surface-html.ts:326,
embedded-frame.ts:41) where the replacem

Repro sweep:

grep -rn 'replace(\s*""|replace("'
--include=".ts" --include=".tsx" pacnode_modules


Why the HTML integrity gate didn't catch

Worth noting since it's the natural placintegrity
validates the content being saved. This corruption is assembled in the browser
when building the iframe srcdoc, downstrthe saved
design HTML is genuinely {"valid": true}d srcdoc it
also misdirects, reporting attribute-unt
for (var i = 0; i < input.length; i += 1) { — a < in the spilled JS, i.e. the
symptom rather than the cause. So the guard for this class has to live at the
injection boundary, not the write bounda

…producers

A localhost screen's file.content IS its route URL. Four separate gestures have
now each found a way to concatenate HTML onto it, permanently destroying the
live binding — the last one survived a full reload and was masked on a React
target by Vite's SPA fallback. Point-guards were added reactively three times;
this closes it at the shared write boundary instead.

moveNodeBetweenDocuments and applyVisualEdit both refuse when the base content
is URL-shaped, and the persist gate reports a typed url-backed-screen-replaced
issue. applyVisualEdit was the remaining hole: ~40 call sites pass file.content
in raw, including the agent-callable apply-visual-edit/apply-shader-fill/
apply-a11y-fix actions. Re-pointing the URL itself still succeeds — the guard is
one-way, not a freeze.
A localhost screen runs two disjoint node-id namespaces: the injected bridge
stamps runtime-<hash> on the live document, while the host stamps an-<hash> on
an independently-fetched source projection. The forward style push already sent
runtimeSourceId, but the recorded pending edit kept only the projection pair, so
every undo/redo replay addressed nodes that do not exist in the running document
and silently no-opped while the queue still popped.

pending-edits now carries runtimeSelector/runtimeSourceId and resolves them
through one runtimeStyleTarget helper, runtime-first with the canonical pair as
fallback, so inline screens are byte-identical. DesignCanvas's redo/repaint
replay uses the same helper.

Also refuses a patch with no resolvable candidates: findRuntimeTarget returns
the current selection when the candidate list is empty, so an unaddressable
revert was restyling whatever happened to be selected.
Completes the live-namespace fix. recordPendingVisualStyleEdit stored only the
source-projection pair, so undo/redo replays addressed nodes that do not exist
in the running document; the redo branch builds its patch inline and needed the
pair explicitly too. Both now carry runtimeSelector/runtimeSourceId, which
runtimeStyleTarget prefers when resolving against the live frame.
setProperty(prop, "") already removes the declaration per CSSOM, so the
removeProperty branch was equivalent and its comment claimed the opposite.
Verified in Chrome: clearing border-top-left-radius on an element with
inline border-radius:44px falls back to the stylesheet's 12px.
@netlify

This comment has been minimized.

A queued one-shot command reports success, so the single fire-and-forget
readiness probe had to land. A frame mid-navigation drops it and an idle
frame never speaks again, stranding the queue permanently — undo of a live
style edit ran, reported sent, and never reached the running app.
@netlify

This comment has been minimized.

@netlify

This comment has been minimized.

@netlify

This comment has been minimized.

@netlify

This comment has been minimized.

@steve8708
steve8708 merged commit 279e855 into main Jul 29, 2026
95 of 97 checks passed
@steve8708
steve8708 deleted the changes-536 branch July 29, 2026 17:21
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.

2 participants