Skip to content

fix(markdown): follow-up workspace inline path link handling#264

Open
DerrickBarra wants to merge 5 commits intodaggerhashimoto:masterfrom
GambitGamesLLC:bugfix/workspace-inline-reference-slice
Open

fix(markdown): follow-up workspace inline path link handling#264
DerrickBarra wants to merge 5 commits intodaggerhashimoto:masterfrom
GambitGamesLLC:bugfix/workspace-inline-reference-slice

Conversation

@DerrickBarra
Copy link
Copy Markdown
Contributor

@DerrickBarra DerrickBarra commented Apr 11, 2026

Summary

  • linkify embedded /workspace/... path slices inside surrounding text
  • narrow inline path matching so the renderer does not overreach
  • fix wrapped / punctuation-adjacent workspace inline links
  • tighten workspace-rooted matching and resolve-path guardrails

Closes #262.

In Plain English

This PR makes workspace path links feel less brittle and more human.

Before this follow-up, the feature worked for the basic case, but there were still several real-world chat/message patterns where links either failed to render or could match too aggressively. That meant people could paste something that clearly looked like a workspace path and not get a clickable link — or, worse, nearby text could get treated like part of the path when it should not.

These changes make valid workspace paths render more reliably in natural writing patterns, while tightening the matcher so only intended workspace paths become actionable. In short: better link detection, fewer weird misses, and safer rendering behavior.

Provenance

Clean branch commit stack:

  • 36cca92fix(markdown): linkify embedded workspace path slices
  • d3d3a64fix(markdown): narrow inline path match typing
  • a04876eFix wrapped workspace inline path links
  • 6cf2616Tighten workspace-rooted inline path matching

Validated downstream via local workhorse cherry-picks:

  • 4e3c8fb <- 36cca92
  • 2b064f0 <- d3d3a64
  • 719db38 <- a04876e
  • 5435706 <- 6cf2616

Context

This is the post-merge follow-up lane to the original workspace path links feature from #237 / #239.

Summary by CodeRabbit

  • New Features

    • Workspace path references in inline code blocks are now clickable and linkified.
  • Improvements

    • Enhanced path normalization to support absolute paths, file:// URIs, and wrapped path formats.
    • Improved workspace path resolution and validation for better accuracy.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 11, 2026

📝 Walkthrough

Walkthrough

Adds workspace-aware path normalization to server file resolution and tightens markdown inline path linkification. Tests expanded for host-absolute workspace paths, wrapped/ punctuated inline tokens, inline-code linkification, and workspace-root guardrails returning 404.

Changes

Cohort / File(s) Summary
Server path normalization & resolve tests
server/routes/file-browser.ts, server/routes/file-browser.test.ts
normalizeWorkspaceLookupPath now accepts an optional workspaceRoot and normalizes host-absolute paths relative to it. /api/files/resolve passes workspace.workspaceRoot for path and relativeTo. Added tests for absolute-host path normalization, relativeTo behavior with absolute paths, and workspace-root (openable) rejection (404).
Inline reference parsing
src/features/markdown/inlineReferences.tsx
Replaced leading/trailing wrap trimming with structured slice parsing: recognizes wrapped forms (`...`, '...', "...", <...>), strips trailing punctuation, decodes/remaps file:///workspace/... and configured prefixes, and returns {display, candidate, after} for precise link rendering.
Markdown renderer & tests
src/features/markdown/MarkdownRenderer.tsx, src/features/markdown/MarkdownRenderer.test.tsx
Inline code rendering uses InlineCodeContent -> renderInlinePathReferences(...) (suppressed when inside workspace <a>). Introduced MarkdownLinkContext for workspace-link click handling; non-workspace anchors get consistent target/rel. Expanded tests to cover inline code linkification, wrapped links, percent-decoding, trailing punctuation preservation, and non-link suppression cases.
Documentation / plans
.plans/2026-04-11-workhorse-packaging-and-chat-links-hardening.md
New planning document added (audit/packaging notes and TODOs for CHAT_PATH_LINKS.json hardening). No runtime code changes.

Sequence Diagram(s)

sequenceDiagram
    participant Renderer as MarkdownRenderer
    participant API as /api/files/resolve
    participant FS as FileSystem

    Renderer->>API: Request resolve(normalizedTarget, relativeTo?)
    API->>API: normalizeWorkspaceLookupPath(path, workspaceRoot)
    API->>FS: Lookup resolved path
    FS-->>API: file metadata / 404
    API-->>Renderer: {ok:true, path:..., type:..., binary:...} / 404
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 I hop through paths both wrapped and bare,
Decoding quotes and dots with gentle care.
Links made tidy, roots guarded from the night,
Punctuation kept, false matches put to flight. ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 18.18% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'fix(markdown): follow-up workspace inline path link handling' accurately describes the main changes: improving workspace path linkification in markdown rendering with follow-up edge-case fixes.
Description check ✅ Passed The PR description is comprehensive and well-structured, including a clear summary, plain-English explanation, provenance details with commit mapping, and context. It follows the expected structure and provides substantial information about the changes.
Linked Issues check ✅ Passed All code changes directly address the objectives in #262: embedded path linkification (inlineReferences.tsx), inline path matching narrowing (MarkdownRenderer.tsx/test), wrapped link handling (MarkdownRenderer.tsx/test), and workspace-rooted guardrails (file-browser.ts/test). The planning document tracks integration progress.
Out of Scope Changes check ✅ Passed All changes are scoped to the #262 objectives: markdown rendering improvements, inline path reference handling, file resolution guardrails, and integration planning. The planning document is supporting context documentation with no out-of-scope code changes.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
src/features/markdown/MarkdownRenderer.test.tsx (1)

86-96: Assert “no links rendered” in the overmatch regressions.

These cases still pass if the renderer links the whole token (path=/workspace/...) instead of the inner slice. Since the failure mode here is overreaching tokenization, I’d make the assertion stricter and require zero links.

Suggested test tightening
-    expect(screen.queryByRole('link', { name: '/workspace/src/App.tsx' })).toBeNull();
+    expect(screen.queryAllByRole('link')).toHaveLength(0);
...
-    expect(screen.queryByRole('link', { name: '/home/derrick/.openclaw/workspace/src/App.tsx' })).toBeNull();
-    expect(screen.queryByRole('link', { name: '/workspace/src/App.tsx' })).toBeNull();
+    expect(screen.queryAllByRole('link')).toHaveLength(0);

Also applies to: 186-197

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/features/markdown/MarkdownRenderer.test.tsx` around lines 86 - 96,
Tighten the test assertion in MarkdownRenderer.test.tsx so it requires that no
links are rendered (not just that the exact slice isn't linked): update the test
using the MarkdownRenderer case with content "Open path=/workspace/src/App.tsx,
now" (and the similar test around lines 186-197) to assert that
queryAllByRole('link') returns zero (i.e., no anchor elements are present)
instead of only asserting the specific link name is absent; keep the same props
(onOpenWorkspacePath, pathLinkPrefixes) and behavior but replace the specific
queryByRole(name...) check with a global no-links assertion to catch overmatch
regressions.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/features/markdown/inlineReferences.tsx`:
- Around line 22-42: normalizeWorkspaceCandidate currently forwards
percent-escaped strings verbatim; decode percent-escapes before returning to
ensure paths like "file:///workspace/My%20Doc.md" map correctly. In
normalizeWorkspaceCandidate (and when handling the file:// branch and the
prefix-matching branch where you build `${CANONICAL_WORKSPACE_PREFIX}${...}`),
call decodeURIComponent on the candidate or the computed suffix (wrap in
try/catch and fall back to the original string on error) so you return decoded
workspace paths; keep the length checks and leading-slash trimming logic but
apply them to the decoded values.

In `@src/features/markdown/MarkdownRenderer.tsx`:
- Around line 340-347: The inline branch currently calls
renderInlinePathReferences(...) unconditionally and can create nested <a> tags;
modify the inlineContent assignment to first detect whether the current code
span is rendered inside an existing markdown link (reuse the existing
processChildren safeguard or a new isInsideLink check) and only call
renderInlinePathReferences when not inside a link; otherwise keep the original
children (i.e., let processChildren leave <a> descendants alone). Update
references: change the inlineContent logic around renderInlinePathReferences,
using onOpenWorkspacePath and currentDocumentPath as before, and add a
regression test that renders a code span inside a markdown link (e.g.,
[`/workspace/src/App.tsx`](docs/todo.md)) asserting no nested anchor is produced
and only the outer link handler fires.

---

Nitpick comments:
In `@src/features/markdown/MarkdownRenderer.test.tsx`:
- Around line 86-96: Tighten the test assertion in MarkdownRenderer.test.tsx so
it requires that no links are rendered (not just that the exact slice isn't
linked): update the test using the MarkdownRenderer case with content "Open
path=/workspace/src/App.tsx, now" (and the similar test around lines 186-197) to
assert that queryAllByRole('link') returns zero (i.e., no anchor elements are
present) instead of only asserting the specific link name is absent; keep the
same props (onOpenWorkspacePath, pathLinkPrefixes) and behavior but replace the
specific queryByRole(name...) check with a global no-links assertion to catch
overmatch regressions.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 43d7a09b-c4aa-4052-bba9-bade704c4ab3

📥 Commits

Reviewing files that changed from the base of the PR and between a5f7973 and 6cf2616.

📒 Files selected for processing (5)
  • server/routes/file-browser.test.ts
  • server/routes/file-browser.ts
  • src/features/markdown/MarkdownRenderer.test.tsx
  • src/features/markdown/MarkdownRenderer.tsx
  • src/features/markdown/inlineReferences.tsx

@DerrickBarra
Copy link
Copy Markdown
Contributor Author

Addressed the actionable review feedback on this branch in d0d4b40.

What changed:

  • decode percent-escaped inline workspace candidates before dispatching to onOpenWorkspacePath (for example file:///workspace/My%20Doc.md -> /workspace/My Doc.md)
  • avoid nested anchors by skipping inline code path-linkification when the code span is already rendered inside a markdown link
  • tighten the overmatch regressions so those cases now assert zero rendered links
  • added regressions for encoded inline workspace paths and markdown-link-wrapped inline code

Validation:

  • npm test -- src/features/markdown/MarkdownRenderer.test.tsx
  • npx eslint src/features/markdown/MarkdownRenderer.tsx src/features/markdown/inlineReferences.tsx src/features/markdown/MarkdownRenderer.test.tsx

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
.plans/2026-04-11-workhorse-packaging-and-chat-links-hardening.md (1)

187-196: Consider making the final summary explicitly partial instead of fully pending.

Since Tasks 1–3 are complete, a brief “Partial results” summary here would improve scanability for reviewers.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.plans/2026-04-11-workhorse-packaging-and-chat-links-hardening.md around
lines 187 - 196, Update the "Final Results" section to reflect a partial summary
instead of marking everything as Pending: rename or add a "Partial Results"
subsection under the "Final Results" header and set Status to something like
"Partial — Tasks 1–3 complete"; briefly list completed items (e.g., "Tasks 1–3
completed") and note remaining items as pending (e.g., "What We Built: partial;
Commits: pending; Lessons Learned: pending") so reviewers can quickly see
progress without losing the existing pending placeholders.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.plans/2026-04-11-workhorse-packaging-and-chat-links-hardening.md:
- Line 150: Update the review follow-up note to capitalize "Markdown" where it
currently reads "markdown tests" so the sentence reads "...Focused Markdown
tests and targeted eslint passed after the fix."; locate the sentence
referencing "inline workspace candidates...", "inline code stops linkifying..."
and change the lowercase "markdown" token to "Markdown" to maintain proper noun
consistency.

---

Nitpick comments:
In @.plans/2026-04-11-workhorse-packaging-and-chat-links-hardening.md:
- Around line 187-196: Update the "Final Results" section to reflect a partial
summary instead of marking everything as Pending: rename or add a "Partial
Results" subsection under the "Final Results" header and set Status to something
like "Partial — Tasks 1–3 complete"; briefly list completed items (e.g., "Tasks
1–3 completed") and note remaining items as pending (e.g., "What We Built:
partial; Commits: pending; Lessons Learned: pending") so reviewers can quickly
see progress without losing the existing pending placeholders.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 47d3782a-84c3-45a7-8c87-a1987e7912d2

📥 Commits

Reviewing files that changed from the base of the PR and between 6cf2616 and d0d4b40.

📒 Files selected for processing (4)
  • .plans/2026-04-11-workhorse-packaging-and-chat-links-hardening.md
  • src/features/markdown/MarkdownRenderer.test.tsx
  • src/features/markdown/MarkdownRenderer.tsx
  • src/features/markdown/inlineReferences.tsx
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/features/markdown/MarkdownRenderer.tsx
  • src/features/markdown/inlineReferences.tsx
  • src/features/markdown/MarkdownRenderer.test.tsx

- `719db38` <- `a04876e`
- `5435706` <- `6cf2616`
- Packaging note: the new issue explicitly references prior art `#237` / merged PR `#239` and scopes this lane as a post-merge bugfix follow-up rather than reopening the original feature request.
- Review follow-up update (2026-04-11): addressed PR `#264` review feedback on the owning branch — inline workspace candidates now percent-decode before dispatch, inline code stops linkifying when already inside a markdown link to avoid nested anchors, and overmatch regressions now assert zero rendered links. Focused markdown tests and targeted eslint passed after the fix.
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Capitalize “Markdown” in the review follow-up note.

Use “Markdown” (proper noun) for consistency in technical docs at Line 150.

🧰 Tools
🪛 LanguageTool

[uncategorized] ~150-~150: Did you mean the formatting language “Markdown” (= proper noun)?
Context: ... stops linkifying when already inside a markdown link to avoid nested anchors, and overm...

(MARKDOWN_NNP)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.plans/2026-04-11-workhorse-packaging-and-chat-links-hardening.md at line
150, Update the review follow-up note to capitalize "Markdown" where it
currently reads "markdown tests" so the sentence reads "...Focused Markdown
tests and targeted eslint passed after the fix."; locate the sentence
referencing "inline workspace candidates...", "inline code stops linkifying..."
and change the lowercase "markdown" token to "Markdown" to maintain proper noun
consistency.

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.

Bug: follow-up fixes for workspace inline path links after #239

1 participant