Bug: a second file-reference click on the already-active file does not jump to the line
Component: chat → IDE file references (openFile / editor reveal) Devpal: 2.3.2 (Next build _1PFDqhBr9nVIT3Ibh-BC), desktop app, macOS 27.2 Severity: minor-but-constant — hits every review/analysis answer that cites several lines of one file Reproducibility: 100 %
Summary
Clicking a file reference in a chat answer opens the file in the IDE and reveals the referenced line. Clicking a second reference to the same file (different line) does nothing: the tab is already active, so the editor neither moves the cursor nor scrolls. Opening any other file in between makes the next click work again.
Steps to reproduce
Open a workspace that has a project, and a chat beside the IDE.
Have the agent (or paste) an answer containing two references to the same file at different lines, e.g.
Copy
[Foo.vue:9](src/components/Foo.vue:9)
[Foo.vue:32](src/components/Foo.vue:32)
Click the first link → the file opens, line 9 is revealed and selected. Correct.
Click the second link → nothing happens; the view stays on line 9.
Click a link to any other file → it opens correctly.
Click the Foo.vue:32 link again → now it works.
Same behaviour for both reference forms (inline-code refs and markdown links), and regardless of whether the path is absolute or project-relative — the resolver is not involved.
Expected
Every click on a file reference reveals its line, including repeated clicks on the file that is already the active tab.
Actual
The jump is silently skipped whenever the target file is already the active editor tab. No error, no toast.
Root cause
In .next/static/chunks/app/(dashboard)/layout-<hash>.js.
The click handler stores the requested location in a ref and then opens the file:
js
Copy
tu = useCallback(async (t, s, n) => {
te.current = { path: t, range: s }; // requested location
tt.current = ...;
await (l ? aY.open(e, l, t) : aV.open(e, t)) || (X(`Cannot open ${...}`), te.current = null)
}, [e, l])
The actual reveal happens in a separate effect:
js
Copy
useEffect(() => {
let e = te.current, t = e8.current, s = e9.current;
if (!e || !t || !s || g !== e.path) return; // g = active file path
te.current = null;
let n = setTimeout(() => {
if (e.range) { let n = a9(s, e.range); t.setSelection(n); t.revealRangeInCenter(n) }
t.focus()
}, 30);
return () => clearTimeout(n)
}, [g, j?.externalVersion]); // <-- dependencies
And the editor store short-circuits when the file is already open:
js
Copy
async open(e, t) {
let s = aP(e).files.find(e => e.path === t);
if (s) return aB(e, e => ({ ...e, active: t })), s.media || aV.onDiskChange(e, t, "change"), s;
...
}
So on the second click the file is already open and already active: active is set to the value it already had, g does not change, externalVersion does not change, and therefore the reveal effect never re-runs. te.current keeps the new range and is never consumed. Opening another file in between changes g twice, which is why the workaround works.
The dedup set one effect above (oV keyed on r.at, at = Date.now()) is not the cause — each click produces a distinct timestamp.
Suggested fix
Make the reveal effect depend on the open request, not only on the resulting active file. Either add the request id that already exists upstream (r.at) to the dependency array, or bump a dedicated counter in tu and depend on that:
js
Copy
const openSeq = useRef(0);
// in tu:
te.current = { path: t, range: s, seq: ++openSeq.current };
// reveal effect:
}, [g, j?.externalVersion, openSeq.current]);
A state counter (rather than a ref) is needed for the dependency to actually trigger a re-render; alternatively, perform the reveal directly in tu after open() resolves when aV.open reports the file was already active.
Workarounds for users
Click a different file in between.
Open the file once and navigate with the editor's own go-to-line.
Close the tab and click again.
Notes
Also worth documenting while in this area: file references become clickable only from inline code spans and markdown link hrefs. Plain text — including plain text inside table cells — is never linkified, because paragraph text goes through tl(), which only recognises @ mentions, and the td renderer does not call it. That is easy to trip over when an agent writes a findings table with bare path:line cells.
Thanks for the report! You were right: when the file was already open and in front, a second click on a reference to it did nothing, because the editor only jumped to a line when the open file changed. Now every click on a file reference jumps to its line, also for the file that is already open, and the first file opened in an empty IDE lands on its line too.
We also took your note about plain text: a reference written as plain text, for example `src/app.ts:12` in a table of findings, now opens the file at that line as well, when the file exists in your projects. Everything else in the text stays as it was.
Both are in the latest Devpal update: use "Check for Updates…" in the app menu if it has not offered itself yet.