Skip to content

Open cmd-clicked Markdown paths in Markdown viewer - #4864

Merged
lawrencecchen merged 7 commits into
mainfrom
task-markdown-path-linkification
May 28, 2026
Merged

lawrencecchen merged 7 commits into
mainfrom
task-markdown-path-linkification

Conversation

@lawrencecchen

@lawrencecchen lawrencecchen commented May 27, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Adds cmd-click coverage for a relative Markdown path with trailing punctuation, using skills/marketing/data/lawrencecchen-tweets.md. as the shape.
  • Opens readable Markdown files in the cmux Markdown viewer by default, and updates settings schema/docs/localized copy.

Testing

  • jq empty Resources/Localizable.xcstrings web/data/cmux.schema.json
  • git diff --check HEAD~2..HEAD

Issues

  • Related: user-reported cmux task in chat, no public issue URL provided.

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


Note

Medium Risk
Changes default app behavior (Markdown opens in cmux viewer unless disabled) and expands terminal link/file-path resolution for absolute paths and punctuation edge cases.

Overview
Cmd-click and terminal link handling now resolve Markdown paths more reliably—including absolute and quoted paths with trailing punctuation—and route readable .md files into the cmux Markdown viewer when routing applies, instead of only handling relative paths.

app.openMarkdownInCmuxViewer is on by default and no longer described as requiring supported-file routing; settings use CmdClickMarkdownRouteSettings.setEnabled, a change notification, and updated schema/docs/localized subtitles. UI-test fixtures support nested paths, display suffixes, and the Markdown toggle env var.

Reviewed by Cursor Bugbot for commit cd1f05d. Bugbot is set up for automated code reviews on this repo. Configure here.


Summary by cubic

Cmd-clicking Markdown files now opens the rendered cmux Markdown viewer by default. Cmd-click and terminal link handling resolve relative, absolute, and quoted Markdown paths, trim trailing punctuation, and normalize fallback paths; schema/docs updated and the settings subtitle aligned across locales.

  • Bug Fixes

    • Normalize fallback path in openURL so absolute/quoted Markdown links with trailing punctuation open correctly (viewer when enabled, external if not).
  • Migration

    • To opt out, set app.openMarkdownInCmuxViewer to false in Settings > App.

Written for commit cd1f05d. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features

    • Markdown routing is enabled by default for Cmd+click; Cmd+click opens the rendered Markdown viewer with live reload. Settings toggle now persists and emits change notifications.
  • Bug Fixes

    • Improved handling of paths with trailing periods so Markdown files route correctly on Cmd+click.
    • Cmd+click routing now prefers the rendered viewer over the generic file preview.
  • Documentation

    • Updated localized strings and docs across multiple languages to reflect viewer behavior.
  • Tests

    • New unit and UI tests covering routing, trailing-period handling, and settings notifications.

Review Change Stack

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

@coderabbitai

coderabbitai Bot commented May 27, 2026 •

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This PR centralizes terminal-open-URL resolution (trims input, rejects URL schemes), flips Cmd-click Markdown routing default to enabled, extends DEBUG UI test display configuration, and adds unit/UI tests plus localization and schema/docs updates validating trailing-dot trimming and viewer routing.

Changes

Terminal-Open-URL Resolution and Default Markdown Routing

Layer / File(s) Summary
Terminal-open-URL path resolution
Sources/GhosttyTerminalView.swift, cmuxTests/TerminalAndGhosttyTests.swift
New resolver cmuxResolveTerminalOpenURLFilePath trims input, rejects URL schemes, delegates to quicklook resolution. DEBUG test helper wraps the resolver. Ghostty OPEN_URL handler refactored to use the resolver. Unit tests validate trailing-dot trimming.
Default markdown routing and settings wiring
Sources/cmuxApp.swift, cmuxTests/WindowAndDragTests.swift
CmdClickMarkdownRouteSettings.defaultValue flipped to true; added change-notification helpers and setEnabled(...)/notifyDidChange(...). SettingsView binding and reset flow updated to use the setter. Unit test confirms markdown routing is enabled by default and routes readable .md files.
UI test infrastructure: display and routing configuration
Sources/AppDelegate.swift, cmuxUITests/TerminalCmdClickUITests.swift
Parses CMUX_UI_TEST_TERMINAL_CMD_CLICK_DISPLAY_SUFFIX, CMUX_UI_TEST_TERMINAL_CMD_CLICK_DISPLAY_AS_ABSOLUTE_PATH, and CMUX_UI_TEST_OPEN_MARKDOWN_IN_CMUX_VIEWER. Computes baseDisplayToken and ensures suffixed tokens are used across log/raw/escaped modes. Fixture seeding creates parent directories. launchApp harness extended with displaySuffix, displayAsAbsolutePath, and openMarkdownInCmuxViewer env wiring.
UI tests: markdown routing with trailing-dot trimming
cmuxUITests/TerminalCmdClickUITests.swift
Two UI tests validate Cmd+click on markdown paths with trailing periods (unquoted relative and quoted absolute). Tests confirm path trimming, routing to cmux markdown viewer, and no external-opener invocation.
Command palette toggle notification and tests
Sources/CommandPalette/CommandPaletteSettingsToggle.swift, cmuxTests/CommandPaletteSettingsToggleTests.swift
Command-palette toggle descriptor posts CmdClickMarkdownRouteSettings.didChangeNotification on change; test verifies toggle flips stored value and posts the notification.
Documentation and schema updates
Resources/Localizable.xcstrings, skills/cmux-settings/references/all-keys.md, web/data/cmux.schema.json
Localizations updated to describe opening the rendered cmux Markdown viewer. Settings docs and JSON schema default updated from false to true for app.openMarkdownInCmuxViewer; conditional wording removed.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

  • manaflow-ai/cmux-dev-artifacts#1824 — Tests around TerminalCmdClickUITest for trailing-period markdown routing reference the same UI test cases added here.
  • manaflow-ai/cmux-dev-artifacts#1822 — Issue concerns cmd-click markdown routing and trailing-period trimming; the PR implements those behaviors.
  • manaflow-ai/cmux-dev-artifacts#1863 — Reports the specific UI test that verifies quoted absolute markdown path with trailing period; matches tests added here.

Possibly related PRs

  • manaflow-ai/cmux#4594 — Similar refactor in GhosttyTerminalView to treat schemeless payloads as file paths and trim trailing punctuation before routing to the app viewer.

Poem

"I sniffed a trailing dot in the night,
hopped up quick to make it right.
Paths now trim and viewers sing,
Cmd+click opens the rendered thing.
A tiny rabbit cheers: hooray!"


Caution

Pre-merge checks failed

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

  • Ignore

❌ Failed checks (1 error, 1 warning, 3 inconclusive)

Check name Status Explanation Resolution
Cmux Full Internationalization ❌ Error Missing translated entries in Localizable.xcstrings: settings.app.openMarkdownInCmuxViewer and .subtitle lack translations for 10 supported locales (ar, bs, da, nb, pl, pt-BR, ru, th, tr, uk). Add translations for both settings.app.openMarkdownInCmuxViewer and settings.app.openMarkdownInCmuxViewer.subtitle to all 10 missing locales in Resources/Localizable.xcstrings.
Docstring Coverage ⚠️ Warning Docstring coverage is 7.14% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Cmux Swift Actor Isolation ❓ Inconclusive No result was produced after verification. Marking as INCONCLUSIVE. Re-run the check or adjust instructions to produce a final result.
Cmux Algorithmic Complexity ❓ Inconclusive No result was produced after verification. Marking as INCONCLUSIVE. Re-run the check or adjust instructions to produce a final result.
Description check ❓ Inconclusive PR description provided with summary, testing details, and issue reference; however, lacks a demo video, complete checklist items, and explicit manual verification steps. Add a demo video showing the Markdown viewer behavior, explicitly document what was manually verified, confirm all checklist items, and request bot reviews using the copy/paste block from the template.
✅ Passed checks (13 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Open cmd-clicked Markdown paths in Markdown viewer' directly and clearly summarizes the main change: enabling cmd-click to open Markdown files in the cmux Markdown viewer.
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 Blocking Runtime ✅ Passed PR adds file path resolution and settings handling without new blocking primitives; file I/O in AppDelegate is within #if DEBUG test scaffolding, which is explicitly allowed.
Cmux No Hacky Sleeps ✅ Passed PR contains only Swift and configuration files. Rule scope is TypeScript, JavaScript, shell scripts, and non-Swift build/runtime scripts. Swift files are excluded and covered separately.
Cmux Swift Concurrency ✅ Passed New code uses only synchronous patterns: UserDefaults.set, NotificationCenter.post, SwiftUI Binding, and path resolution. No Tasks, DispatchQueues, Combine, or completion handlers introduced.
Cmux Swift @Concurrent ✅ Passed All new Swift functions are synchronous with no @concurrent violations. No nonisolated async work or improper @concurrent usage found in modified files.
Cmux Swift File And Package Boundaries ✅ Passed Small incremental additions to existing oversized files (+16/+29/+25 lines, all under 250 threshold). No new production files. AppKit glue and test configuration match allowed exceptions.
Cmux Swift Logging ✅ Passed New markdown routing production code passes Swift logging rules: no print/NSLog violations; debug logs properly guarded with #if DEBUG using cmuxDebugLog.
Cmux User-Facing Error Privacy ✅ Passed Localization strings and documentation use only safe terms (cmux, markdown, file types) with no vendor names, credentials, env vars, or sensitive implementation details exposed.
Cmux Swiftui State Layout ✅ Passed No new @Observable, @StateObject, @Published patterns. Existing legacy @AppStorage touched incidentally, test helpers properly DEBUG-guarded.
Cmux Architecture Rethink ✅ Passed PR extends existing pattern: UserDefaults as truth, @AppStorage for UI sync, setEnabled/notification consistent with CmdClickSupportedFileRouteSettings, no architectural violations.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed PR adds settings enum, modifies SettingsView binding, and adds file path resolvers. No new user-visible NSWindow, NSPanel, NSWindowController, Window, or WindowGroup created.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch task-markdown-path-linkification

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.

@vercel

vercel Bot commented May 27, 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 28, 2026 4:51am
cmux-staging Building Building Preview, Comment May 28, 2026 4:51am

@greptile-apps

greptile-apps Bot commented May 27, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

Cmd-clicking Markdown paths (relative or absolute, with or without trailing punctuation) now opens the cmux Markdown viewer by default, and the terminal link handler normalizes its fallback URL string to the clean resolved path so external openers receive a well-formed file URL instead of the raw token.

  • CmdClickMarkdownRouteSettings gains a didChangeNotification, a setEnabled helper, and a defaultValue flip from false → true; the Settings toggle now uses a custom Binding that posts the change notification, matching the pattern already in place for CmdClickSupportedFileRouteSettings.
  • cmuxResolveTerminalOpenURLFilePath replaces the inline file-path guard, dropping the previous absolute-path exclusion so quoted and absolute tokens (e.g., \"/tmp/file.md.\") are resolved and trimmed before falling through to resolveTerminalOpenURLTarget, which already knows how to turn an absolute file path into a file:// URL.
  • All nine locales in Localizable.xcstrings are updated; schema, skills docs, and unit+UI tests are updated to match.

Confidence Score: 5/5

Safe to merge; all nine locales are updated, the new path-resolution function is a clean extraction of existing logic, and the fallback normalizedOpenURLString hand-off to resolveTerminalOpenURLTarget is correct because that function already handles bare absolute paths via URL(fileURLWithPath:).

The default-value flip and path-routing changes are the highest-impact parts of this PR. The default flip is intentional and well-documented. The cmuxResolveTerminalOpenURLFilePath extraction correctly removes the now-unnecessary absolute-path exclusion. The normalizedOpenURLString fallback feeds a clean absolute path into resolveTerminalOpenURLTarget, which already handles absolute paths as file:// URLs — no gap there. All nine locales are updated in Localizable.xcstrings, resolving the previous internationalization concern. Unit and UI tests cover the key new paths.

No files require special attention.

Important Files Changed

Filename Overview
Sources/GhosttyTerminalView.swift Adds cmuxResolveTerminalOpenURLFilePath (and its testing wrapper) to handle both relative and absolute tokens; removes the prior absolute-path exclusion; stores the clean resolved path as normalizedOpenURLString so non-routed files fall through to resolveTerminalOpenURLTarget with a well-formed path.
Sources/cmuxApp.swift Flips defaultValue to true, adds setEnabled/notifyDidChange helpers matching the CmdClickSupportedFileRouteSettings pattern, and wires the Settings view toggle through markdownRoutingBinding so the change notification is posted on every toggle.
Resources/Localizable.xcstrings Updates settings.app.openMarkdownInCmuxViewer.subtitle for all nine locales (en, ja, ko, zh-Hans, zh-Hant, de, es, fr, it), removing the obsolete 'When supported file routing is on' conditional from every language.
Sources/AppDelegate.swift Adds UI-test env vars for displaySuffix, displayAsAbsolutePath, and openMarkdownInCmuxViewer; creates nested intermediate directories before writing fixture files so nested fileName paths like skills/marketing/data/… work correctly.
Sources/CommandPalette/CommandPaletteSettingsToggle.swift Adds didSet closure to the openMarkdownInCmuxViewer command-palette descriptor so toggling the setting from the palette also posts the didChangeNotification.
cmuxUITests/TerminalCmdClickUITests.swift Adds two end-to-end tests covering relative and quoted-absolute Markdown paths with trailing periods, and extends launchApp with displaySuffix, displayAsAbsolutePath, and openMarkdownInCmuxViewer parameters.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["Ghostty cmd-click / terminal link\nurlString"] --> B["Trim whitespace\n→ trimmedUrlString"]
    B --> C{"trimmedUrlString\nempty?"}
    C -- yes --> Z["return false"]
    C -- no --> D["performOnMain:\ncmuxResolveTerminalOpenURLFilePath\ntrimmedUrlString, cwd"]
    D --> E{"URL has\nscheme?"}
    E -- yes --> F["return nil → (false, nil)"]
    E -- no --> G["cmuxResolveQuicklookPath\n(strips trailing punctuation,\nquotes, resolves relative/absolute)"]
    G --> H{"file\nexists?"}
    H -- no --> F
    H -- yes --> I{"shouldRouteInCmux\n(Markdown viewer enabled\n& readable .md file)?"}
    I -- yes --> J["Open in cmux\nMarkdown viewer\n→ (true, resolvedPath)"]
    I -- no --> K["→ (false, resolvedPath)\nnormalizedOpenURLString = resolvedPath"]
    J --> L["return true ✓"]
    K --> M["resolveTerminalOpenURLTarget\nnormalizedOpenURLString"]
    F --> N["resolveTerminalOpenURLTarget\noriginal urlString"]
    M --> O{"absolute\npath?"}
    N --> O
    O -- yes --> P["URL(fileURLWithPath:)\n→ .external(fileURL)"]
    O -- no --> Q["parse URL scheme\n→ browser / external"]
    P --> R["NSWorkspace.open"]
    Q --> R
Loading

Reviews (5): Last reviewed commit: "Align Markdown routing settings subtitle" | Re-trigger Greptile

coderabbitai[bot]
coderabbitai Bot previously requested changes May 27, 2026

@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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
Sources/GhosttyTerminalView.swift (1)

4697-4733: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Preserve the normalized file path for the fallback open path.

When cmuxResolveTerminalOpenURLFilePath(...) succeeds but CommandClickFileOpenRouter.shouldRouteInCmux(...) is false, this drops resolvedPath and later reopens the raw token. For the new trailing-punctuation cases, opting out of markdown-in-cmux or hitting an unsupported file type falls through to foo.md. / /tmp/foo.md. instead of the real file, so the click stops opening the file at all.

💡 Proposed fix
-            if !trimmedUrlString.isEmpty {
-                let filePathRouted: Bool = performOnMain {
+            var resolvedOpenURLFilePath: String?
+            if !trimmedUrlString.isEmpty {
+                let filePathRouted: Bool = performOnMain {
                     guard let termSurface = surfaceView.terminalSurface,
                           let workspace = termSurface.owningWorkspace(),
                           !workspace.isRemoteTerminalSurface(termSurface.id) else {
                         return false
                     }
                     let cwd = CommandClickFileOpenRouter.resolveWorkingDirectory(
                         workspace: workspace,
                         surfaceId: termSurface.id
                     )
-                    guard let resolvedPath = cmuxResolveTerminalOpenURLFilePath(trimmedUrlString, cwd: cwd),
-                          CommandClickFileOpenRouter.shouldRouteInCmux(path: resolvedPath) else {
+                    guard let resolvedPath = cmuxResolveTerminalOpenURLFilePath(trimmedUrlString, cwd: cwd) else {
                         return false
                     }
+                    resolvedOpenURLFilePath = resolvedPath
+                    guard CommandClickFileOpenRouter.shouldRouteInCmux(path: resolvedPath) else {
+                        return false
+                    }
                     `#if` DEBUG
                     cmuxDebugLog("link.openURL resolvedAsFilePath=\(resolvedPath)")
                     `#endif`
                     let fileURL = URL(fileURLWithPath: resolvedPath)
                     CommandClickFileOpenRouter.deferredOpenFileInCmux(
@@
                 if filePathRouted {
                     return true
                 }
             }
 
-            guard let target = resolveTerminalOpenURLTarget(urlString) else {
+            let normalizedURLString = resolvedOpenURLFilePath ?? urlString
+            guard let target = resolveTerminalOpenURLTarget(normalizedURLString) else {
🤖 Prompt for 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.

In `@Sources/GhosttyTerminalView.swift` around lines 4697 - 4733, The code
currently discards the normalized resolvedPath when
cmuxResolveTerminalOpenURLFilePath(...) returns a path but
CommandClickFileOpenRouter.shouldRouteInCmux(...) is false, which later causes
the fallback to reopen the raw token (e.g., "foo.md.") instead of the normalized
file path; change the logic in the performOnMain block around
cmuxResolveTerminalOpenURLFilePath and shouldRouteInCmux so that if
cmuxResolveTerminalOpenURLFilePath returns a non-nil resolvedPath you preserve
that value (e.g., assign to a new local fallbackResolvedPath or replace
urlString) for use by the later fallback open logic, while still skipping
CommandClickFileOpenRouter.deferredOpenFileInCmux when shouldRouteInCmux is
false; reference cmuxResolveTerminalOpenURLFilePath,
CommandClickFileOpenRouter.shouldRouteInCmux, resolvedPath, and the
performOnMain closure to locate the code to update.
🤖 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/AppDelegate.swift`:
- Around line 1819-1823: Add a setEnabled(_:) helper to
CmdClickMarkdownRouteSettings (mirroring
CmdClickSupportedFileRouteSettings.setEnabled) that writes the boolean to
UserDefaults and posts the same change notification; then replace the direct
UserDefaults write in AppDelegate (the block checking
CMUX_UI_TEST_OPEN_MARKDOWN_IN_CMUX_VIEWER) to call
CmdClickMarkdownRouteSettings.setEnabled(rawOpenMarkdown == "1"). Ensure the
method name matches CmdClickMarkdownRouteSettings and that the notification
key/payload matches the pattern used by CmdClickSupportedFileRouteSettings.

---

Outside diff comments:
In `@Sources/GhosttyTerminalView.swift`:
- Around line 4697-4733: The code currently discards the normalized resolvedPath
when cmuxResolveTerminalOpenURLFilePath(...) returns a path but
CommandClickFileOpenRouter.shouldRouteInCmux(...) is false, which later causes
the fallback to reopen the raw token (e.g., "foo.md.") instead of the normalized
file path; change the logic in the performOnMain block around
cmuxResolveTerminalOpenURLFilePath and shouldRouteInCmux so that if
cmuxResolveTerminalOpenURLFilePath returns a non-nil resolvedPath you preserve
that value (e.g., assign to a new local fallbackResolvedPath or replace
urlString) for use by the later fallback open logic, while still skipping
CommandClickFileOpenRouter.deferredOpenFileInCmux when shouldRouteInCmux is
false; reference cmuxResolveTerminalOpenURLFilePath,
CommandClickFileOpenRouter.shouldRouteInCmux, resolvedPath, and the
performOnMain closure to locate the code to update.
🪄 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: 7bb62230-b0b7-40e1-8c31-85684bd99924

📥 Commits

Reviewing files that changed from the base of the PR and between 3b1fe70 and 23a31cf.

📒 Files selected for processing (9)
  • Resources/Localizable.xcstrings
  • Sources/AppDelegate.swift
  • Sources/GhosttyTerminalView.swift
  • Sources/cmuxApp.swift
  • cmuxTests/TerminalAndGhosttyTests.swift
  • cmuxTests/WindowAndDragTests.swift
  • cmuxUITests/TerminalCmdClickUITests.swift
  • skills/cmux-settings/references/all-keys.md
  • web/data/cmux.schema.json

Comment thread Sources/AppDelegate.swift
@lawrencecchen
lawrencecchen dismissed coderabbitai[bot]’s stale review May 28, 2026 05:08

Addressed in fa27877 by preserving the normalized fallback path and adding the settings helper.

@lawrencecchen
lawrencecchen merged commit ae0cc9e into main May 28, 2026
18 of 19 checks passed
@lawrencecchen
lawrencecchen deleted the task-markdown-path-linkification branch May 28, 2026 05:08

This branch was successfully deployed

1 active deployment
Preview – cmux — cd1f05db Deployed May 28, 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.

1 participant