Skip to content

fix(web): resolve file-chip editor opens through the workspace index

MacroscopeApp / Macroscope - UI Consistency failed Aug 26, 2026 in 5m 46s

UI Consistency: 1 issue found

apps/web/src/components/ChatMarkdown.tsx — The chip's browser-preview action was not migrated with the rest of the open actions. Panel open (openFileInPanel), reveal (revealMarkdownFileInFileManager), and now editor open (openMarkdownFileInEditor) all resolve a chip's workspaceRelativePath through the workspace index, but onOpenInBrowser still passes the raw fileLinkMeta.filePath (the literal cwd-joined path). Because shouldUseMarkdownFileBrowserPrimaryAction promotes browser preview to the primary click for PDFs, one chip can open the real file on ⌘-click/menu and a missing path on a plain click; for .html chips the menu item diverges from every other item in the same menu. Suggested fix posted inline: mirror the reveal callback and route the path through findWorkspaceBasenameMatch before openMarkdownFileInPreview, keeping the isBrowserPreviewFile gate on the chip's own extension.

Previously flagged items that are now resolved: the editor open action is routed through the index (this run's earlier finding), and the unreachable basename fallback tail in workspaceBasenameLookup.ts has been deleted.

Details

Note

Your check run agent prompt is: .macroscope/check-run-agents/ui-consistency.md
More information about how Check Run Agents work can be found in our Help Center.

Scope reviewed: apps/web/src/components/ChatMarkdown.tsx, apps/web/src/workspaceBasenameLookup.ts, apps/web/src/workspaceBasenameLookup.test.ts, plus apps/web/src/markdown-links.ts, apps/web/src/editorPreferences.ts, and apps/web/src/browser/openFileInPreview.ts as context.

Checks performed and found clean:

  • Environment routing: findWorkspaceBasenameMatch, openMarkdownFileInEditor, openInPreferredEditor, and revealFileInFileManager all derive environmentId from threadRef?.environmentId ?? explicitEnvironmentId ?? null with no active-environment fallback; the new editor path adds environmentId to its dependency chain and returns early when it is null.
  • Position preservation: openMarkdownFileInEditor re-appends line/column to the resolved match before resolvePathLinkTarget, matching the old targetPath semantics; the fallback branch keeps the original targetPath (with position), while reveal correctly uses the position-free filePath.
  • User-gesture safety: useOpenInPreferredEditor dispatches a server RPC (no window.open / scheme navigation), so awaiting the index lookup before the open does not break popup/gesture rules.
  • Lookup claim scoping: scopedThreadKey(threadRef) is only called after the !threadRef early return, and the per-scope Map preserves the newest-click-wins contract within a thread; the added test covers cross-thread isolation.
  • pickWorkspaceBasenameMatch reachability: after the last commit every branch is reachable (exact path, unique suffix, ambiguous suffix, folded suffix, no match), and the retained comment now sits on the rule it describes.

No Tailwind, global CSS, theme-variant, scroll/mask, or primitive-composition changes are present in this diff, so those rule sets did not apply.


🔍 Agent activity — 22 tool calls

💭 I want to check the current state of the PR head to evaluate it. My main concern is the environment routing logic in the shared renderers, particularly how openMarkdownFileInEditor relies on findWorkspaceBasenameMatch which needs an environmentId. There was also a prior comment about dead code in an earlier commit, so I should check what's changed since then between that commit and the current head.

1. git_tools — b0a0281..ed9cfa5

2. browse_code — apps/web/src/workspaceBasenameLookup.ts

💭 Good, the dead code I flagged earlier—those tail exact/folded basename fallbacks—has been removed. Now I want to check how commit ed9cfa5 addresses the editor open finding, so I'm looking at ChatMarkdown.tsx around those changes.

3. git_tools — ed9cfa5

💭 Let me check ChatMarkdown.tsx around lines 1800-1960 to see how environment routing works and whether MarkdownFileLink's onOpen still receives a target path argument that might now be ignored.

4. browse_code — apps/web/src/components/ChatMarkdown.tsx

💭 I'm checking how MarkdownFileLink invokes onOpen, since the new call site ignores the path argument passed by the child — I need to verify whether that path ever carries extra info like line/column that the previous handler relied on.

5. git_tools — onOpen

6. git_tools — MarkdownFileLink

7. browse_code — apps/web/src/components/ChatMarkdown.tsx

💭 I'm checking where onOpen gets called within MarkdownFileLink, specifically the menu item click and ctrl-click handlers.

8. browse_code — apps/web/src/components/ChatMarkdown.tsx

💭 I want to check whether the memo comparator for markdown file link props actually compares onOpen by reference — since onOpen used to be a stable callback but is now an inline arrow created fresh each render, that could break memoization even if the outer markdownComponents memo itself doesn't regenerate.

9. browse_code — apps/web/src/components/ChatMarkdown.tsx

💭 I'm noticing the memo comparator checks onOpen by reference, but since onReveal and onOpenInBrowser are already inline arrows in the existing code, the memoization is already defeated whenever those props are present—so this issue may already exist regardless of my change. I'm now checking that markdownComponents is memoized, so the renderer arrows are stable until ReactMarkdown re-renders with new deps; since onReveal already breaks equality, this is a pre-existing pattern rather than a new regression, so it's not worth flagging. Now I want to verify the behavior of openMarkdownFileInEditor against how onOpen previously handled the target path, since it now routes through findWorkspaceBasenameMatch scoped to the environmentId. I need to check whether targetPath includes a line/column suffix by looking at resolveMarkdownFileLinkTarget and the MarkdownFileLinkMeta definition in markdown-links.ts.

10. browse_code — apps/web/src/markdown-links.ts

11. browse_code — apps/web/src/markdown-links.ts

💭 I want to check how buildFileLinkMetaFromTarget handles filePath, targetPath, and workspaceRelativePath to understand their relationship.

12. browse_code — apps/web/src/markdown-links.ts

💭 I'm confirming targetPath keeps absolute position info while workspaceRelativePath stays relative without it, and that the new openMarkdownFileInEditor correctly preserves line/column when resolving to an absolute path. I'm also noting a behavior change: the click handler used to open synchronously, but now it awaits a search step first, making it async. I want to check whether other call sites, like inline path links rendered elsewhere in ChatMarkdown, still call openInPreferredEditor directly with a workspace-relative path that could now be out of sync with this new resolution logic.

13. git_tools — openInPreferredEditor|openFileInPanel|openMarkdownFileInPreview

💭 I notice that the browser preview action still uses the literal cwd-joined path instead of resolving through the index, unlike the panel, reveal, and editor actions which were fixed to use index resolution. This looks like the same inconsistency I flagged for the editor before, so I should consider whether to flag the browser preview too, and I want to think through what each chip action actually does before deciding. For a file like docs/diagram.png living deeper in a subfolder, browser preview would fail to find the right location, even though pdf/image/html files are exactly the ones gated to show this preview—so this isn't just a theoretical edge case, it's directly applicable. Given the patch's core premise is that chip paths need index resolution, fixing three of four actions but leaving the fourth broken does seem like a genuine inconsistency worth reporting, though I want to weigh how strongly to phrase this before deciding whether it should count as a failing finding. That extra RPC per click seems acceptable given the PR's intent. I'm also checking the guard change in openFileInPanel from a lookup check to a direct null comparison on environmentId, which looks fine, and tracing whether openMarkdownFileInEditor's return type properly matches the expected async signature for handleOpenInEditor. I'm also considering whether wrapping openInPreferredEditor in a new inline arrow breaks memoization for the MarkdownFileLink chip, though onReveal is already inline in some cases so it may not matter much. The real question is whether making onOpen always a fresh function causes unnecessary re-renders during streaming chat, since the memo comparator explicitly checks onOpen, but the actual re-render cost seems minor given the chip's simple content. The one clean fix I'm considering is having onOpen accept the target path directly and resolve everything itself, so the callback stays stable rather than being reconstructed per-render. But I realize onReveal already has this same inline-callback issue pre-existing in the code, so the memoization was already partially defeated before this diff. This performance/memoization angle feels marginal and risks being noise, so I should refocus on something more concrete. Let me look more closely at the browser-preview inconsistency and check how isBrowserPreviewFile is used.

14. git_tools — isBrowserPreviewFile

💭 I'm noticing the browser preview only handles .html/.pdf files, so an html/pdf chip could still open the literal cwd-joined path in the integrated browser even while the panel opens the resolved one. There's also an inconsistency where the primary click action for a pdf chip uses the unresolved path, while that same chip's editor and reveal actions use the resolved path. This is the same class of issue as the one already fixed, and narrow in scope, but before the PR all four chip actions were equally broken and now three work — so it's an inconsistency rather than a regression, which puts it near the edge of what's worth flagging given the PR's stated scope. I also want to check that environment routing for editor-open and workspace-match functions properly falls back through threadRef and explicit environmentId without defaulting to an active environment, and confirm the scopedThreadKey import correctly handles the ScopedThreadRef type.

15. browse_code — In packages (client-runtime) find the definition of scopedThreadKey exported from @t3tools/client-runtime/environment. Show its signature and implementation, and note whether it accepts an optional/undefined ScopedThreadRef.

16. git_tools — export function scopedThreadKey

💭 That early return guard confirms threadRef is safe there. The latestLookupSequenceByScope Map does grow unbounded per thread, but it's a minor memory concern rather than a UI consistency bug since it's bounded by session thread count. Now I'm checking whether openFileInPanel diverges from the editor path in how it handles the class reference. Actually the more important issue is that ⌘-click used to open the editor synchronously inside the click handler, but now it awaits an RPC lookup before calling openInPreferredEditor. If the editor-open logic relies on a redirect like window.location or window.open for remote editor schemes, browsers might block that outside a direct user gesture — this async change could be a real regression I need to verify.

17. git_tools — useOpenInPreferredEditor

18. browse_code — apps/web/src/editorPreferences.ts

💭 Since this is a server-side RPC rather than window.open, there's no user-gesture concern there. Checking the shell action permission logic looks fine too. I'm noting that the "open in integrated browser" path and PDF click handler still reference the unresolved literal path, which is inconsistent with the now-resolved panel/editor/reveal logic. I should also check whether existing tests cover the new editor-open behavior, since the guideline requires focused tests when composition, prop forwarding, or state transitions change — and here openMarkdownFileInEditor changes the chip's ⌘-click behavior and makes onOpen ignore its argument. I need to check ChatMarkdown.test.tsx to see if it mocks useOpenInPreferredEditor in a way that could mask identity churn issues, and whether any test asserts that ⌘-click opens the targetPath directly or could break due to the new async lookup throwing if searchProjectEntries isn't mocked.

19. git_tools — searchEntries|searchProjectEntries

20. git_tools — searchEntries

💭 There's no component test exercising the lookup itself, only unit tests for pickWorkspaceBasenameMatch, so requiring one might be excessive. Given the check defaults to failure and I've already flagged issues in five prior runs, I should be judicious and only report something genuinely concrete—so let me trace through finding #1 more carefully: an agent output markdown link [report.html](docs/report.html) with cwd /repo resolves via resolveMarkdownFileLinkTarget to /repo/docs/report.html, giving a workspaceRelativePath of docs/report.html. For a PDF chip, checking shouldOpenMarkdownFileLinkInBrowserByDefault shows primary click opens in browser using the unresolved path while ⌘-click opens the resolved editor path—exactly the inconsistency this PR is meant to fix, since a plain click on a moved PDF still hits a 404 preview while ⌘-click finds the real file. The preview action itself is properly scoped through threadRef and httpBaseUrl, so that's fine. This looks like a legitimate, narrow, actionable finding squarely within the changed scope, so I want to double-check the helper around line 179 to make sure my description of it is accurate before writing it up.

21. browse_code — apps/web/src/components/ChatMarkdown.tsx

💭 For a .pdf chip, the primary click action is browser preview using the unresolved fileLinkMeta.filePath, and handleOpenInFilePreview falls back to handleOpenInEditor when there's no workspaceRelativePath. I should also verify how workspace image chips render, since openMarkdownFileInPreview only covers html/pdf while images use asset URLs. I'll flag the browser-preview action around line 1954 for still using the unresolved path, and suggest adding an openMarkdownFileInBrowser callback that mirrors the reveal logic — resolving the workspace-relative path via findWorkspaceBasenameMatch before falling back to filePath, then wiring it into the call site and useMemo deps. Since this spans multiple hunks, a single inline suggestion won't work cleanly, so I'll need a diff-free description or unified diff instead. Weighing whether this should fail the check: it's a narrow but genuine consistency risk, and since the check defaults to failure whenever there's a finding (only passing when explicitly "all clear"), this should count as a failure. The PR's stated scope is resolving file-chip opens through the workspace index, and the browser-preview action is the one remaining chip-open path still using the literal path — squarely within scope and introduced by this PR, so it's worth flagging as a failure. For the inline comment, I need to find a commentable line since the actual divergent code isn't directly in the diff hunk; I'll attach the comment to the changed onOpen line instead, since that's within the diff and close enough to explain the remaining divergence. I'll go ahead and post it there.

22. modify_pr — One finding: the chip's "Open in integrated browser" action (and, for PDFs, the chip's primary click) still targets the literal cwd-joined path while panel/editor/reveal now resolve through the workspace index.


Agent Credits: 148 credits