fix(ui): smart resolution + existence-validation for code-file paths#654
Open
backnotprop wants to merge 1 commit intomainfrom
Open
fix(ui): smart resolution + existence-validation for code-file paths#654backnotprop wants to merge 1 commit intomainfrom
backnotprop wants to merge 1 commit intomainfrom
Conversation
The bare-prose / backtick path detector linkifies anything that looks
like a code path. Two failure modes regularly produce dead links: prose
abbreviations like `editor/App.tsx` (real file is
`packages/editor/App.tsx`) and references to files the plan proposes
but hasn't created yet. Both 404 on click with no UX cue.
Resolves abbreviated paths via a case-insensitive suffix-match against
a cached project walk (`resolveCodeFile` in `packages/shared/resolve-file.ts`),
mirroring what `resolveMarkdownFile` already does for markdown. The walk
is pre-warmed when the plan/annotate server boots and on every
`/api/doc` request, with a 30s TTL so newly-created files can resolve
mid-review. Storing the walk as a Promise makes the cache race-safe —
concurrent callers piggyback rather than starting a second walk.
A new `POST /api/doc/exists` endpoint takes a batch of candidate paths
and reports `found` / `ambiguous` / `missing` / `unavailable` per path.
On the frontend, `useValidatedCodePaths` extracts candidates from the
markdown on load and POSTs once. The renderer reads the result via
`CodePathValidationContext`: `found` opens directly with the resolved
absolute path, `ambiguous` opens a `CodeFilePicker` popover listing all
matches (common in monorepos where `App.tsx` exists in several
packages), `missing` demotes the link to plain code, and `unavailable`
falls back to the optimistic linkification we have today. While
validation is in flight, every detected path renders as a link, so
first paint is unchanged.
The detection itself gets a shape filter (`isPlausibleCodeFilePath`)
that hard-rejects shell brace expansion (`{a,b}`), glob wildcards, and
whitespace, while explicitly allowing `[` / `]` so Next.js dynamic
routes (`app/[slug]/page.tsx`) still resolve. The bare-prose regex moves
out of `InlineMarkdown.tsx` into `code-file.ts` so the renderer and the
new server-side extractor use the same source of truth, and the
extractor strips fenced code blocks, HTML comments, and URL ranges
before scanning so it only emits candidates the renderer would actually
paint.
Pi extension mirrors the Bun changes (handler upgrade, pre-warm,
`/api/doc/exists` route). When the popout's `/api/doc` request 404s the
dialog now surfaces "File not found in repo: <path>" instead of
silently swallowing the error.
Tests: `code-file.test.ts` extended with shape-filter and Next.js-route
cases; new `extract-code-paths.test.ts` covers extraction, dedup,
fenced/HTML/URL exclusion, and the URL-with-parens regression; new
`resolve-file.test.ts` covers the suffix-match strategy, leading `./`
handling, ambiguous results, and ignored-dir behavior.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
/api/doc: a literal miss now falls back to a case-insensitive suffix match against a cached project walk, mirroring what already works for markdown. Abbreviated paths from prose (editor/App.tsx→packages/editor/App.tsx) resolve correctly.POST /api/doc/existsendpoint + frontenduseValidatedCodePathshook. The renderer demotes paths the project doesn't contain (e.g. files the plan proposes creating) to plain code instead of leaving broken-link buttons that 404 on click.App.tsxexists in multiple packages, clicking opens a small dropdown of all matches; selecting one opens that file in the popout.The server walk is pre-warmed at plan/annotate load and on every
/api/docrequest, stored as a Promise (race-safe), with a 30s TTL so newly-created files resolve mid-review. The frontend renders optimistically (every detected path is a link on first paint) and demotes missing ones once the POST returns — first paint is unchanged.A shape filter (
isPlausibleCodeFilePath) hard-rejects shell brace expansion ({a,b}.ts), glob wildcards, and whitespace, while still allowing[/]so Next.js dynamic routes survive. Pi extension mirrors all server changes. The popout now surfaces "File not found in repo: <path>" instead of silently swallowing 404s.Original failure case:
~/.plannotator/plans/salvage-pr-380-shortcut-regist-2026-05-03-approved.mdproduced 404s foreditor/App.tsx,review-editor/App.tsx, andpackages/ui/shortcuts/{core,runtime,index}.ts. After: the first two resolve to theirpackages/...real paths; the shortcuts files render as plain code (they don't exist yet — the plan proposes creating them).Test plan
bun test packages/shared/— 218 pass./breaking suffix match)bun run --cwd apps/review buildandbun run build:hookboth succeed~/.plannotator/plans/salvage-pr-380-shortcut-regist-2026-05-03-approved.mdindev:hookand verify:editor/App.tsx(line 7) is clickable, openspackages/editor/App.tsxreview-editor/App.tsx(line 7) is clickable, openspackages/review-editor/App.tsxpackages/ui/shortcuts/core.tsetc. render as plain<code>, no shimmer, no click handlerpackages/editor/App.tsx(full path, lines 81/82/109/110) still clickable — regression checkPOST /api/doc/existsafter the initial renderapp/[slug]/page.tsxpaths still linkify in a test planApp.tsxin a test plan opens the picker showing bothpackages/editor/App.tsxandpackages/review-editor/App.tsx