Skip to content

fix(extension): hub-client file clicks die silently — the open-file handler's no-posture branch needs a named outcome + the runtime wiring of linkResolution needs a bridge-level test #1845

Closed

Description

@aarontrowbridge

Important

Problem — Live-reported during the v0.3.8-alpha exercise (Aaron, on the macbook/erlich hub-client posture): clicking files in session output (the edit/write tool parts' rows, and some review rows) does nothing — no open, no toast, no console error. Three sibling bugs were found and fixed along the way (#1839 the layout gate, #1841 the diffCanRender gate, #1843 the webview string-frame stream bug + the whole-row click target — all merged, all live on the dist). But the core symptom persists in the last tested state: a verified-rendered <button data-slot="message-part-title-filename" data-clickable> (devtools-probed in the correct iframe context, so the part HAS a path) still produces nothing on click.
The chain, statically verified end-to-end: iframe onClick → openFileInEditor (posts {source:"amicode", kind:"open-file", url:"file://…"} to the parent) → the top-document relay (origin-pinned to the app origin, open-file in the allowlist) → vscode.postMessage → chat_bridge's open-file handler → resolveChatFileLink → Preview. Every link exists in source. The failure is runtime.
Prime suspect — the handler's own documented silent branch (chat_bridge.ts, the if (!linkResolution) path): "without the context the pre-#1826 local-stat gate stands" — if (!fs.existsSync(fsPath)) return true — a hub path on the client's disk fails the stat and returns with NO named outcome. On the hub-client posture that's exactly the observed behavior: total silence. That would mean io.linkResolution is undefined in the live panel's io despite the activation wiring (extension.ts:1209 registers the callback; chat_panel.ts:252 reads it at io construction) — the #1826 slice's unit tests covered the handler logic but never the runtime wiring of the context into the panel io.
Approach — Two fixes: (1) the silent branch becomes a NAMED OUTCOME — the handler must never die silently (the same discipline as the block system's named outcomes: a toast naming "no posture context" or "not found locally (pre-posture gate)"); (2) root-cause the runtime wiring: verify ChatPanel.linkResolutionContextCallback?.() is non-undefined in a real panel's io at click time, and add a BRIDGE-LEVEL test (the panel io construction with the callback registered), not just unit tests over the resolver. If the context is genuinely absent at runtime, find where the registration's activation ordering loses it (stale panel instance? per-message io rebuild? the deck variant?).
Scope — in: chat_bridge's silent branch, the io wiring + its bridge-level test. out: the app-side fixes (already landed: #1839/#1841/#1843 + the row-click target).
Assumptions — Last tested state: extension v0.3.8-alpha.3, dist bundle index-D2RIBKJy.js, amicode.hubServerUrl armed (the P4 telemetry assert passes), posture should be hub-client.

The debug kit (discriminators, ready to run)

  1. Top-context relay observer (webview devtools, context top, then click a row):
    window.addEventListener("message", (e) => { if (e.data?.source === "amicode") console.log("RELAY-SAW", e.data.kind, e.origin) })
    • RELAY-SAW open-file → the post lands; the break is extension-side (the silent branch / wiring) — the prime suspect confirmed.
    • nothing → the iframe post never fires or the origin pin rejects it; next probe goes inside the iframe context (note: cross-origin property writes from the iframe are blocked — wrap from the top context).
  2. Iframe-context button probe (context = the 127.0.0.1:4097 frame):
    const btn = document.querySelectorAll("[data-slot='message-part-title-filename']")[0]; let fired = 0; btn.addEventListener("click", () => { fired++; console.log("BTN CLICK", fired) }); btn.click()
    — distinguishes handler-fires-but-no-ops from overlay-intercepted physical clicks. NOTE: the devtools default context is top (vscode-file://vscode-app); the app is the cross-origin iframe — context selection matters (a probe from top threw SecurityError on this box).
  3. Post-reload bundle check: the last tested state predates the D2RIBKJy dist (the row-click target + stream fix); confirm the panel runs it before re-testing.

Prior Art

Source

Found by Aaron live-exercising the v0.3.8-alpha build. Filed at his call to park the live debug and move on — the trail above is the complete state.

Activity

  1. aarontrowbridge commented on Oct 25, 2026

    @aarontrowbridge
    MemberAuthor

    Fixed in v0.3.9 (#1871).

  2. aarontrowbridge commented on Oct 25, 2026

    @aarontrowbridge
    MemberAuthor

    Root cause found with a headless repro (panel framed like the webview, an inline hub path, click, record the parent envelope). Opening a session by /<dir>/session/<id> redirects to /server/<key>/session/<id> and remounts the server-scoped shell. The outgoing AmicodeFileRefBridge's onCleanup ran after the incoming one registered and called registerFileRefResolver(undefined), wiping the live resolver. Result: zero resolve-file requests, pills never linkified, authored links kept dead relative hrefs, clicks posted nothing. The direct /server/ URL worked, which is why this looked intermittent.

    Fix on lane/0.3.10 (#1890): the unregister handle clears only its own registration, and a reactive version re-linkifies already-rendered messages. Verified both routes: linkified, resolved, open-file posted; the extension resolves hub-client clicks to preview-file, and the Preview tab reads the hub file. The v0.3.9 silent-branch toasts were real but not the cause.

  3. aarontrowbridge commented on Oct 27, 2026

    @aarontrowbridge
    MemberAuthor
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions