Skip to content

fix(ui): smart resolution + existence-validation for code-file paths#654

Open
backnotprop wants to merge 1 commit intomainfrom
fix/path-detection-again
Open

fix(ui): smart resolution + existence-validation for code-file paths#654
backnotprop wants to merge 1 commit intomainfrom
fix/path-detection-again

Conversation

@backnotprop
Copy link
Copy Markdown
Owner

Summary

  • Smart resolution for code-file paths in /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.tsxpackages/editor/App.tsx) resolve correctly.
  • Existence pre-validation via a new POST /api/doc/exists endpoint + frontend useValidatedCodePaths hook. 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.
  • Picker popover for ambiguous matches: when a name like App.tsx exists 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/doc request, 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.md produced 404s for editor/App.tsx, review-editor/App.tsx, and packages/ui/shortcuts/{core,runtime,index}.ts. After: the first two resolve to their packages/... 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
  • New unit tests pin the two regressions caught in self-review (URL-with-parens leaking path-shaped substrings; leading ./ breaking suffix match)
  • bun run --cwd apps/review build and bun run build:hook both succeed
  • Open ~/.plannotator/plans/salvage-pr-380-shortcut-regist-2026-05-03-approved.md in dev:hook and verify:
    • editor/App.tsx (line 7) is clickable, opens packages/editor/App.tsx
    • review-editor/App.tsx (line 7) is clickable, opens packages/review-editor/App.tsx
    • packages/ui/shortcuts/core.ts etc. render as plain <code>, no shimmer, no click handler
    • packages/editor/App.tsx (full path, lines 81/82/109/110) still clickable — regression check
    • Network tab shows exactly one POST /api/doc/exists after the initial render
  • Verify Next.js-style app/[slug]/page.tsx paths still linkify in a test plan
  • Verify a backtick App.tsx in a test plan opens the picker showing both packages/editor/App.tsx and packages/review-editor/App.tsx

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.
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.

1 participant