Skip to content

Fix markdown files with trailing punctuation detected as URLs - #4594

Merged
austinywang merged 3 commits into
manaflow-ai:mainfrom
jasonko:fix/markdown-trailing-punctuation-4569
May 24, 2026
Merged

austinywang merged 3 commits into
manaflow-ai:mainfrom
jasonko:fix/markdown-trailing-punctuation-4569

Conversation

@jasonko

@jasonko jasonko commented May 22, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #4569

Problem

When terminal output contains a file path followed by sentence punctuation (e.g. Spec written to docs/specs/2026-05-22-test.md.), Ghostty's link detection matches the entire string including the trailing period. The OPEN_URL handler classifies it as a web URL (https://docs/specs/2026-05-22-test.md.) and opens the browser sidebar instead of the markdown preview.

Fix

Added a file-path resolution step in the OPEN_URL handler before URL classification. For strings without a URL scheme and that aren't absolute paths, the handler calls cmuxResolveQuicklookPath — which already strips trailing sentence punctuation (., ,, ;, etc.) — against the terminal's working directory. If the trimmed path resolves to an existing file that cmux can preview (markdown or supported file type), it routes through the file viewer instead of the browser.

Guards


View with Codesmith Autofix with Codesmith
Need help on this PR? Tag @codesmith with what you need. Autofix is disabled.


Summary by cubic

Fixes incorrect URL detection for relative file paths with trailing punctuation by resolving local paths before URL classification, so docs/spec.md. opens in the file viewer instead of the browser. Adds a fallback to open externally if in-app routing fails. Fixes #4569.

  • Bug Fixes

    • Added a pre-classification file-path check in OPEN_URL using cmuxResolveQuicklookPath to trim trailing punctuation and route previewable files through the viewer, with a system-opener fallback if routing fails.
    • Guarded behavior: only for schemeless, non-absolute strings; skips remote terminals; falls back to normal URL handling.
    • Added tests for relative paths with trailing dot/comma, non-existent paths, and the URL(string:)?.scheme gate.
  • Refactors

    • Extracted resolveWorkingDirectory and used it across the link handler and GhosttyNSView to unify CWD lookup.
    • Added deferredOpenFileInCmux to centralize deferred split creation with workspace re-resolution and TOCTOU checks; replaced regex scheme detection with URL(string:)?.scheme == nil.

Written for commit 5a1e70e. Summary will update on new commits. Review in cubic

Summary by CodeRabbit

  • Bug Fixes

    • Command-click now better detects and opens local file paths (including relative paths with trailing punctuation) instead of incorrectly opening them in a browser.
    • Improved working-directory selection when resolving file paths from the terminal, including safer handling for remote sessions.
    • More reliable routing to terminal panes for local file opens, with a deferred open fallback when needed.
  • Tests

    • Added regression and scheme-validation tests for file-path and URL resolution edge cases.

Review Change Stack

@vercel

vercel Bot commented May 22, 2026

Copy link
Copy Markdown

Someone is attempting to deploy a commit to the Manaflow Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented May 22, 2026 •

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This PR centralizes terminal working-directory resolution and deferred cmux file opening in CommandClickFileOpenRouter, updates GhosttyTerminalView to use that router for relative and file-URL routing and for resolved-word working directories, and adds tests for relative-path punctuation trimming and URL-scheme gating.

Changes

Command-click routing refactor

Layer / File(s) Summary
Working directory resolution and deferred routing
Sources/CommandClickFileOpenRouter.swift
CommandClickFileOpenRouter exports resolveWorkingDirectory(workspace:surfaceId:) to compute terminal working directory with per-panel, surface-requested, then workspace-current precedence; and deferredOpenFileInCmux(...) to defer cmux routing to the next main runloop tick, re-resolve workspace at dispatch time, gate remote surfaces, attempt openInCmux, and invoke an optional fallback when routing/opening fails.
GhosttyTerminalView URL handling updates
Sources/GhosttyTerminalView.swift
GHOSTTY_ACTION_OPEN_URL now trims payloads and pre-processes raw strings as potential relative file paths, resolves candidates via cmuxResolveQuicklookPath using CommandClickFileOpenRouter.resolveWorkingDirectory(), and routes eligible files through CommandClickFileOpenRouter.deferredOpenFileInCmux(); local-file-URL routing and resolved-word working-directory selection now delegate to the centralized router.
Test coverage for relative path and URL scheme behavior
cmuxTests/TerminalAndGhosttyTests.swift
Added regression tests for relative paths with trailing punctuation (dot, comma) and non-existent relative paths returning nil; new TerminalOpenURLSchemeGateTests validates that relative paths and host-like strings without explicit scheme yield nil from URL(string:)?.scheme, while explicit schemes (https://, file://, mailto:) and absolute paths behave as expected.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 I hop to routes both near and far,
Deferring leaps until the next tick's star,
Trimming dots and commas from the trail,
Resolving paths so quicklook won't fail,
Tests nod, and off the router bounds we sail.


Caution

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

  • Ignore

❌ Failed checks (1 error, 2 warnings)

Check name Status Explanation Resolution
Cmux Swift @Concurrent ❌ Error GhosttyTerminalView.swift calls cmuxResolveQuicklookPath (file I/O-heavy via FileManager) from OPEN_URL handler inside performOnMain (@MainActor context) without explicit actor hop. Move cmuxResolveQuicklookPath call to background queue via actor-hop to avoid file I/O on MainActor, or use @concurrent annotation if intentionally UI-bound.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ⚠️ Warning The description provides a clear problem statement, detailed fix explanation, and lists specific guards. However, the required Testing and Demo Video sections from the template are missing. Add Testing section describing how the change was tested locally and what was verified manually. Include a Demo Video section or note if no UI changes occurred.
✅ Passed checks (14 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Cmux Swift Actor Isolation ✅ Passed CommandClickFileOpenRouter properly uses @MainActor/nonisolated; closure param correctly @MainActor @Sendable; no implicit isolation or mutable Sendable issues.
Cmux Swift Blocking Runtime ✅ Passed PR introduces deferred main.async to avoid Ghostty os_unfair_lock deadlock, documented and matching allowed case; no blocking patterns, semaphores, sleeps, or polling detected.
Cmux No Hacky Sleeps ✅ Passed All modified files are Swift (.swift), which are explicitly out of scope for this check (covered by swift-blocking-runtime.md instead).
Cmux Swift Concurrency ✅ Passed DispatchQueue.main.async in deferredOpenFileInCmux is required for the Ghostty callback boundary to avoid deadlocking the os_unfair_lock, which is explicitly permitted by the review rules.
Cmux Swift File And Package Boundaries ✅ Passed 90-line CommandClickFileOpenRouter has single, clear responsibility. GhosttyTerminalView shrinks 27 lines net. No mixed responsibilities or testable logic misplaced in app target.
Cmux Swift Logging ✅ Passed PR's new feature code uses cmuxDebugLog() with #if DEBUG guards. CommandClickFileOpenRouter.swift has no logging. Print statements are pre-existing.
Cmux User-Facing Error Privacy ✅ Passed All production code changes contain only internal logic with no user-facing error messages, alerts, or privacy violations. Debug logs are wrapped in #if DEBUG. Tests are developer-only.
Cmux Full Internationalization ✅ Passed No user-facing strings added. CommandClickFileOpenRouter contains only logic; GhosttyTerminalView changes add code to existing localized flow; test changes exempt from i18n rules.
Cmux Swiftui State Layout ✅ Passed CommandClickFileOpenRouter has no SwiftUI state. DispatchQueue.main.async only in event handlers (allowed per rule). TerminalSurface state is existing legacy, incidentally touched for this feature.
Cmux Architecture Rethink ✅ Passed Small correctness fix with clear owners, unified routing path, required platform bridge (documented Ghostty deadlock #3370), and test coverage. No new state, duplicate wiring, or split UI lifecycle.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed PR adds file routing helpers and URL path resolution without creating or materially changing standalone cmux-owned windows; test windows are fixtures; lint script passed.
Title check ✅ Passed The title clearly summarizes the main fix: handling markdown files with trailing punctuation that were being misdetected as URLs.
✨ 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.

@jasonko
jasonko force-pushed the fix/markdown-trailing-punctuation-4569 branch from 93015e1 to 146fe04 Compare May 22, 2026 23:37
jasonko and others added 2 commits May 22, 2026 16:46
…anaflow-ai#4569)

Tests the two key behaviors introduced by the fix:

1. cmuxResolveQuicklookPath correctly resolves relative file paths with
   trailing sentence punctuation against a CWD (the core fix for manaflow-ai#4569).
   - Relative markdown path with trailing dot
   - Relative path with trailing comma
   - Non-existent relative path returns nil

2. URL(string:)?.scheme gate correctly distinguishes schemeless strings
   (relative paths, bare domains) from strings with URL schemes (https,
   file, mailto), validating the guard condition that determines whether
   file-path resolution is attempted before URL classification.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ow-ai#4569)

When terminal output contains a file path followed by sentence punctuation
(e.g. 'Spec written to docs/specs/test.md.'), Ghostty's link detection
matches the entire string including the trailing period as a URL. The
OPEN_URL handler then classifies it as a web URL (https://docs/specs/test.md.)
instead of recognizing the underlying markdown file.

Fix: before URL classification in the OPEN_URL handler, attempt to resolve
the raw link text as a local file path using cmuxResolveQuicklookPath, which
already handles trailing-punctuation trimming. If the path resolves to an
existing file that cmux can preview (markdown or supported file type), route
it through the file viewer instead of the browser.

Refactoring: extracted three shared helpers into CommandClickFileOpenRouter:
- resolveWorkingDirectory: consolidated the 3-step CWD lookup (panel dir →
  requested working dir → workspace dir) previously duplicated in
  GhosttyNSView.resolvedWordPathWorkingDirectory and the new code path.
- deferredOpenFileInCmux: consolidated the lock-avoidance pattern for
  deferred file opens (manaflow-ai#3370/manaflow-ai#3560), previously duplicated inline in the
  file-URL and relative-path handlers. Handles workspace re-resolution,
  remote-surface re-check, TOCTOU re-validation, and fallback.
- Replaced custom regex scheme detection with URL(string:)?.scheme == nil.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@jasonko
jasonko force-pushed the fix/markdown-trailing-punctuation-4569 branch from 9439724 to 82f8cfb Compare May 22, 2026 23:46
@jasonko
jasonko marked this pull request as ready for review May 22, 2026 23:49
@greptile-apps

greptile-apps Bot commented May 22, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a bug where Ghostty's link detection matched trailing sentence punctuation as part of file paths (e.g. docs/specs/2026-05-22-test.md.), causing the OPEN_URL handler to classify the result as a web URL and open a browser instead of the markdown preview. The fix adds a pre-classification file-path resolution step using cmuxResolveQuicklookPath — which already strips trailing punctuation — before the URL scheme is evaluated.

  • Bug fix: Added an early-exit path in OPEN_URL that resolves schemeless, non-absolute strings against the terminal CWD. If the trimmed path exists and cmux can handle it, the click is routed to the file viewer with a NSWorkspace.shared.open fallback, matching the existing file:// path behavior.
  • Refactor: Extracted resolveWorkingDirectory from GhosttyNSView into CommandClickFileOpenRouter and introduced deferredOpenFileInCmux to centralise the deferred split-creation pattern, used by both the new relative-path check and the existing file:// path.
  • Tests: Added regression coverage for relative paths with trailing . and ,, non-existent paths, and the URL(string:)?.scheme scheme-gate.

Confidence Score: 5/5

Safe to merge — the pre-classification file-path check is well-guarded, both call sites supply a NSWorkspace fallback, and the refactored deferredOpenFileInCmux faithfully reproduces the original inline logic.

The change is a targeted bug fix with clear guards (schemeless, non-absolute, local surface only) and a correct fallback chain. The extracted deferredOpenFileInCmux centralises an already-working pattern without altering its semantics. Tests cover the key regression scenarios.

No files require special attention.

Important Files Changed

Filename Overview
Sources/CommandClickFileOpenRouter.swift Added resolveWorkingDirectory (extracted from GhosttyNSView) and deferredOpenFileInCmux centralising the deferred split-creation pattern. Logic mirrors the previously-inline DispatchQueue.main.async block with workspace re-resolution and TOCTOU checks; both new call sites supply a fallback closure.
Sources/GhosttyTerminalView.swift Adds the new schemeless file-path pre-check in OPEN_URL, delegates to deferredOpenFileInCmux with a NSWorkspace fallback; removes the large inline deferred-dispatch block in favour of the new helper, and delegates resolvedWordPathWorkingDirectory to the shared CommandClickFileOpenRouter implementation.
cmuxTests/TerminalAndGhosttyTests.swift Adds regression tests for trailing-dot and trailing-comma relative paths, non-existent paths, and URL scheme detection gate; uses the existing cmuxResolveQuicklookPathForTesting harness correctly.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[GHOSTTY_ACTION_OPEN_URL\nraw urlString] --> B{trimmedUrlString\nempty?}
    B -- yes --> Z[return false]
    B -- no --> C{isAbsolutePath?}
    C -- yes --> G
    C -- no --> D{URL scheme == nil?}
    D -- no --> G
    D -- yes --> E[performOnMain:\nresolveWorkingDirectory\ncmuxResolveQuicklookPath\nshouldRouteInCmux]
    E -- path not found\nor not routable --> G
    E -- file resolved --> F[deferredOpenFileInCmux\nnext runloop tick\nfallback: NSWorkspace.open]
    F --> R[return true]
    G[resolveTerminalOpenURLTarget] --> H{target?}
    H -- nil --> Z2[return false]
    H -- file URL\nlocalhost --> I[performOnMain:\ndeferredOpenFileInCmux\nfallback: NSWorkspace.open]
    I --> R2[return true]
    H -- external / browser --> J[NSWorkspace.open\nor embedded browser]
    J --> R3[return true]
Loading

Reviews (2): Last reviewed commit: "Add fallback to relative-path deferred o..." | Re-trigger Greptile

Comment thread Sources/GhosttyTerminalView.swift

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Sources/GhosttyTerminalView.swift`:
- Around line 4382-4392: The deferredOpenFileInCmux call currently returns true
immediately without supplying a fallback, so if workspace lookup or split
creation fails the click is swallowed; update the
CommandClickFileOpenRouter.deferredOpenFileInCmux invocation to pass a fallback
closure (same pattern used in the file:// branch) that attempts the
immediate/local open of resolvedPath (e.g., reusing the existing file-open
routine for this surface/workspace or calling the same handler used for file://
opens with workspace, surfaceId and resolvedPath) so failures in the async route
fall back to the immediate open instead of dropping the click.
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 1562bf37-33a4-411d-8d74-ab8aa952eb57

📥 Commits

Reviewing files that changed from the base of the PR and between db26771 and 82f8cfb.

📒 Files selected for processing (3)
  • Sources/CommandClickFileOpenRouter.swift
  • Sources/GhosttyTerminalView.swift
  • cmuxTests/TerminalAndGhosttyTests.swift

Comment thread Sources/GhosttyTerminalView.swift
Address CodeRabbit and Greptile review feedback: the relative-path branch
of deferredOpenFileInCmux was called without a fallback closure, so if the
deferred workspace lookup or split creation failed, the click was silently
swallowed (the handler already returned true to Ghostty). Now passes an
NSWorkspace.shared.open fallback matching the file:// path's pattern.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@jasonko

jasonko commented May 23, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented May 23, 2026

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@jasonko

jasonko commented May 23, 2026

Copy link
Copy Markdown
Contributor Author

Re: Cmux Swift @Concurrent check — this is a false positive in context. The existing file:// path in the same handler (line ~4419) does the same thing: shouldRouteInCmux → isReadableRegularFile → FileManager I/O, all inside performOnMain. The main-actor context is required to access surfaceView.terminalSurface and workspace before any file check can happen.

Moving just this call off-main while the surrounding code stays on-main would be inconsistent and provide no real benefit. A proper fix would require refactoring the entire OPEN_URL handler to separate actor-bound workspace resolution from file I/O — that is a larger change out of scope for this bug fix.

@vercel

vercel Bot commented May 24, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
cmux Ready Ready Preview, Comment May 24, 2026 6:50pm

@austinywang
austinywang merged commit 702985f into manaflow-ai:main May 24, 2026
18 checks passed

This branch was successfully deployed

1 active deployment
Preview – cmux — 5a1e70e5 Deployed May 24, 2026 by vercel[bot]
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.

Preview Markdown been detected as URL

2 participants