Skip to content

feat: add note surface type with cmux note CLI - #4332

Closed
austinywang wants to merge 293 commits into
mainfrom
issue-4331-note-surface-type
Closed

austinywang wants to merge 293 commits into
mainfrom
issue-4331-note-surface-type

Conversation

@austinywang

@austinywang austinywang commented May 19, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Adds note as a project-scoped markdown surface with durable metadata in .cmux/notes/index.json. Notes can attach to a workspace, a specific surface, or a terminal surface, and the existing Markdown panel is used as a writing-first note editor while preserving live file watching and preview mode.

Closes #4331.

What landed

Layer Change
Storage CmuxNoteStore owns indexed note records, stable note IDs, body paths, attachment anchors, legacy .cmux/notes/<slug>.md compatibility, read/write/append/delete, and list/path metadata
CLI / agents `cmux note new
Socket RPC note.create/open/list/path/read/write/append/delete, with --attach / attach modes for `none
UI actions Default surface tab bar includes a centered note button; command palette includes note actions for current surface and workspace
Editing UX Notes open in edit mode by default; re-opening an already-editing note can focus the text editor; editable note text views receive plain and Shift-arrow keys
Rich preview Markdown preview keeps GFM tables, Mermaid, and Vega/Vega-Lite graph blocks, and now bundles MathJax for inline/display LaTeX math without runtime CDN access
Session restore Workspace and panel note anchors plus note body paths persist, so attached notes and moved-project restore keep stable identity
Domain split Note support is split into NoteSupport.swift, CmuxNoteModels.swift, and CmuxNoteStore.swift; all new Swift files are wired into cmux.xcodeproj
Licenses Bundled MathJax asset is included in THIRD_PARTY_LICENSES.md

Testing

  • CI tests
  • CI release-build
  • CI remote-daemon-tests
  • CI tests-build-and-lag
  • CI ui-regressions
  • CI activation-session
  • CI workflow-guard-tests
  • CI web-typecheck
  • CI web-db-migrations
  • git diff --check
  • jq empty Resources/Localizable.xcstrings
  • Local builds/tests not run by Codex per workspace instruction

Demo Video

Not applicable. This is a feature/refactor PR and did not need the cloud-mac visual repro path.

Review Trigger

CodeRabbit, Cursor Bugbot, and Greptile reviewed the PR. Actionable review threads have been fixed or replied to.

Checklist

HQ Debug Build Command

CMUX_SKIP_ZIG_BUILD=1 ./scripts/reload.sh --tag issue-4331-note-surface-type --launch

Note

Medium Risk
New automation surface (CLI + socket note CRUD) and control-socket routing affect agents and concurrency; beta-gated UI limits blast radius but note file writes and attachment routing still touch workspace state.

Overview
Adds a full cmux note command surface that talks to new v2 socket methods (note.create, open, list, path, read, write, append, delete) for project-scoped markdown under .cmux/notes/, including attach modes, routing flags, and human/JSON output.

CLI structure is refactored: markdown open, workspace list/create/env/close/select/rename, and right-sidebar availability move into dedicated extension files; note is registered in command suggestions and top-level dispatch.

Right sidebar gains an opt-in Notes beta mode: a new defaults key, Settings toggle, dynamic notes in right-sidebar mode lists/help (CLI reads app UserDefaults suites so PATH-launched cmux matches the app), and shortcuts switchRightSidebarToNotes / newNote.

Control socket routes all note.* methods on the worker thread to avoid main-actor deadlocks. Terminals can receive CMUX_WORKSPACE_NOTES_DIR via spawn policy when a workspace notes root exists.

Also adds a Pane Tab Bar Settings section (docs + global/project cmux.json pointers), new drag UTTypes for notes/session trees, and a small RovoDev helper inlining refactor unrelated to notes.

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

@vercel

vercel Bot commented May 19, 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 Jul 15, 2026 1:31pm
cmux-staging Building Building Preview, Comment Jul 15, 2026 1:31pm

@coderabbitai

coderabbitai Bot commented May 19, 2026 •

Copy link
Copy Markdown

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a project-scoped note surface persisted at .cmux/notes/<slug>.md, including NoteSupport helpers, workspace APIs for opening/creating notes, v2 socket RPCs (note.create/open/list/path/delete), a cmux note CLI dispatcher, session slug persistence/restore, localization, tests, and Xcode project entries.

Changes

Project-Scoped Markdown Notes Surface

Layer / File(s) Summary
Note storage & slug helpers
Sources/NoteSupport.swift, Sources/CmuxConfig.swift
Adds NoteSupport with slug validation/generation, project-root discovery, notes-dir/path helpers, ensureNoteFile, listNotes, deleteNote, NoteListEntry, NoteError, and adds CmuxSurfaceType.note.
Workspace integration & session persistence
Sources/Workspace.swift, Sources/SessionPersistence.swift
Snapshots record noteSlug; restore re-resolves slugs against workspace .cmux/notes/ root. Adds noteProjectRoot() and newNoteSurface(inPane:slug:createIfMissing:focus:). Threads deterministic layoutPath/surfaceSeed into custom layout handling for stable slugs.
V2 RPC handlers
Sources/TerminalController+Notes.swift, Sources/TerminalController.swift, Sources/TerminalController+MoveTabToNewWorkspace.swift
Registers note.* in capabilities and implements v2NoteCreate, v2NoteOpen (shared v2NoteOpenSplit with create/reuse logic), v2NoteList, v2NotePath, v2NoteDelete. Adds note.create/note.open to explicit-focus handling.
CLI dispatcher & localization
CLI/cmux.swift, Resources/Localizable.xcstrings
Adds cmux note top-level case and runNoteCommand to parse shared flags, validate args, route verbs to v2 methods, and format human/JSON output. Adds cli.note.* localized strings and updates global CLI overview and usage.
Build entries & tests
cmux.xcodeproj/project.pbxproj, cmuxTests/CmuxConfigTests.swift
Adds NoteSupport.swift and TerminalController+Notes.swift to Xcode project and build phase; adds NoteSupportTests for slug parsing, fallback slug determinism, and idempotent deletion.

Sequence Diagram(s)

sequenceDiagram
  participant CLI
  participant Client
  participant TerminalController
  participant Workspace
  participant NoteSupport
  participant Filesystem

  CLI->>Client: cmux note new/open/list/path/rm
  Client->>TerminalController: v2 RPC (note.*)
  TerminalController->>NoteSupport: validateSlug / autoSlug
  TerminalController->>Workspace: noteProjectRoot() / newNoteSurface(...)
  Workspace->>NoteSupport: resolve or ensure note path
  NoteSupport->>Filesystem: create/list/delete note files
  Workspace->>TerminalController: MarkdownPanel (created or reused)
  TerminalController->>Client: structured response (slug, path, project_root, reused/exists)
  Client->>CLI: prints human or JSON output
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 A tiny note in .cmux hides,
A slug named true where project pride resides,
From CLI to socket, through workspace doors,
Markdown panes open, content restores,
Hop, scribble, save — the rabbit cheers and soars!


Important

Pre-merge checks failed

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

❌ Failed checks (6 errors, 1 warning)

Check name Status Explanation Resolution
Cmux Swift Actor Isolation ❌ Error PR introduces synchronous file I/O on MainActor threads in Workspace.newNoteSurface and TerminalController+Notes v2 RPC methods without background dispatch, violating Swift 6 actor isolation. Move NoteSupport.ensureNoteFile/noteFileExists calls off MainActor to background tasks with proper completion handlers; defer sessionRestoreNoteProjectRoot capture until after currentDirectory update.
Cmux Swift Blocking Runtime ❌ Error Blocking file I/O on @MainActor paths: Workspace.newNoteSurface lines 11179/11188, TerminalController+Notes.v2NoteOpenSplit lines 147/172 call ensureNoteFile/noteFileExists synchronously. Dispatch file I/O to background queue with callbacks, then hop back to @MainActor for UI updates only in both Workspace.newNoteSurface and v2NoteOpenSplit.
Cmux Swift Concurrency ❌ Error Workspace.newNoteSurface() performs blocking file I/O on main thread via ensureNoteFile/noteFileExists calls, violating the concurrency modernization requirement to move such operations off-thread. Move file I/O operations in newNoteSurface off the main thread using detached Task or nonisolated helper before hopping back to main for panel creation.
Cmux Swift @Concurrent ❌ Error File I/O-heavy NoteSupport functions called from @MainActor v2NoteOpenSplit at lines 147, 172 without background dispatch, violating UI isolation rules. Move noteFileExists/ensureNoteFile calls to DispatchQueue.global() or refactor to perform I/O off-thread with results returned to main thread.
Cmux Swift File And Package Boundaries ❌ Error Budget file includes stale values. Workspace.swift and TerminalController.swift exceed budgets by 1135/1386 lines. PR adds 173 total lines without updating budget values to match. Update .github/swift-file-length-budget.tsv to 15110 for Workspace.swift and 18703 for TerminalController.swift to reflect actual post-PR sizes.
Cmux Architecture Rethink ❌ Error MainActor file I/O in TerminalController+Notes.swift and session-restore ordering bug in Workspace.swift break architectural invariants for timing and state ownership. Move noteFileExists/ensureNoteFile to Task.detached; move sessionRestoreNoteProjectRoot cache after currentDirectory update so slug resolution uses restored working directory.
Docstring Coverage ⚠️ Warning Docstring coverage is 51.61% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (10 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The PR fully implements all primary objectives from #4331: new CmuxSurfaceType.note, NoteSupport domain helpers, workspace factory methods, v2 socket methods, CLI verbs, session restore support, and focus discipline.
Out of Scope Changes check ✅ Passed All code changes are directly scoped to implementing the note surface primitive; no unrelated refactoring or features beyond #4331 objectives are present.
Cmux No Hacky Sleeps ✅ Passed Rule explicitly covers only TypeScript, JavaScript, and shell scripts. PR modifies only Swift files, Xcode config, and localization strings; rule is not applicable.
Cmux Swift Logging ✅ Passed PR uses Apple's unified Logger for error logging with privacy protection; CLI output uses print() correctly; no prohibited logging violations found.
Cmux User-Facing Error Privacy ✅ Passed User-facing errors use proper localization with no vendor names, raw upstream errors, credentials, tokens, or sensitive implementation details exposed.
Cmux Full Internationalization ✅ Passed All 40 note-related user-facing strings use String(localized:defaultValue:) with complete translations for all 19 supported locales in Localizable.xcstrings.
Cmux Swiftui State Layout ✅ Passed PR introduces no new SwiftUI state patterns violating swiftui-state-layout.md: no @Published/@State/@Observable/@bindable. All code is CLI/RPC/data structures.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed PR adds note surface reusing existing MarkdownPanel pane type. No NSWindow, NSPanel, NSWindowController, or WindowGroup creation; only CLI dispatching, RPC handlers, utilities, and workspace methods.
Title check ✅ Passed The title accurately summarizes the main addition: a new note surface and cmux note CLI.
Description check ✅ Passed The PR description covers the required sections with summary, testing, demo note, review trigger, and checklist.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch issue-4331-note-surface-type

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.

Comment thread Sources/TerminalController.swift Outdated
Comment thread Sources/Workspace.swift Outdated
coderabbitai[bot]
coderabbitai Bot previously requested changes May 19, 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: 8

🤖 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 `@CLI/cmux.swift`:
- Around line 4009-4026: In the "rm"/"delete" subcommand block (the case
handling in CLI/cmux.swift) add the same unknown-flag validation used elsewhere:
after computing let rest = Array(trailingArgs.dropFirst()) and before checking
for extraArg, detect if rest.first starts with "-" and throw a CLIError like
"cmux note rm: unknown flag '\(rest.first!)'"; keep the existing extra-argument
check and then proceed to buildRoutingParams(), set params["slug"] = slugArg and
call client.sendV2("note.delete", ...).
- Around line 3991-4007: The "path" (and similarly "rm") case is missing the
unknown-flag validation that "new", "open", and "list" perform, causing poor
error messages; update the "path" case to run the same unknown-flag check (the
same validation used by the "new"/"open"/"list" branches) before you compute
rest/extraArg from trailingArgs so unknown flags like `--unknown` produce the
consistent "unknown flag" error, then keep the existing extra-argument check and
the rest of the logic (references: the "path" case, trailingArgs, extraArg, and
the "rm" case).

In `@Sources/CmuxConfig.swift`:
- Around line 3253-3426: Extract the NoteSupport enum (including NoteListEntry
and NoteError) into its own Swift source file: create a new file containing
NoteSupport, NoteListEntry, NoteError and all methods (validateSlug, autoSlug,
projectRoot(forCwd:), notesDirectory(forProjectRoot:),
notePath(forSlug:projectRoot:), slug(forNotePath:),
ensureNoteFile(slug:projectRoot:), listNotes(forProjectRoot:),
deleteNote(slug:projectRoot:)). Ensure the new file imports Foundation and that
the types have the appropriate access level (make them internal or public to
match current usage outside the original file), remove the original enum from
CmuxConfig.swift, and add the new source to the same target so all existing
references to NoteSupport continue to compile. Also run a build to fix any
access-level issues and update tests/imports if this is used by other targets.
- Around line 3336-3346: slug(forNotePath:) currently returns the raw slug when
validateSlug(slug) throws, violating the contract; change the final return to
only return the validated slug (String) and otherwise return nil by replacing
"return (try? validateSlug(slug)) ?? slug" with logic that attempts
validateSlug(slug) and returns nil on failure (e.g., use do/try/catch or if-let
with try? to return the validated value or nil). Ensure you reference the local
variable slug and the validateSlug(_:) call in the updated code.
- Around line 3351-3362: The ensureNoteFile(slug:projectRoot:) currently ignores
FileManager.createFile(...)’s Bool result and returns the path even if creation
failed; update the function to check the Bool returned by
FileManager.default.createFile(atPath: path, contents: Data(), attributes: nil)
and if it returns false throw an appropriate error (or propagate a thrown error)
so callers like note.create/note.open don’t assume the file exists; keep the
existing behavior for directory creation (notesDirectory and createDirectory)
and reference the path, dir, and slug variables when adding the failure check
and throw.

In `@Sources/TerminalController.swift`:
- Around line 9444-9462: The success response built in the new-panel branch (the
dictionary passed to result = .ok([...]) that contains keys like "window_id",
"window_ref", "pane_id", "surface_id", "slug", "path", etc.) must include an
explicit "reused": false entry to match the reused-panel branch which returns
"reused": true; update the dictionary in the code that constructs result =
.ok(...) (references: result, v2OrNull, v2Ref, windowId, targetPaneUUID,
panel.id, slug, notePath, projectRoot) to add "reused": false so the response
schema is consistent and callers can reliably distinguish reused vs new panels.
- Around line 9554-9558: The response currently interpolates the raw error into
the API message (result = .err with code "io_error" and message "Failed to
delete note: \(error)"), which may leak internal details; change the message to
a generic string like "Failed to delete note" and move the error details into
the data payload (e.g., add "error" or "error_description":
error.localizedDescription alongside the existing ["slug": slug]) so diagnostics
remain available without exposing internal error text in the top-level message.
- Around line 9453-9456: The code inconsistently uses optional chaining for
sourceSurfaceId (and sourcePaneUUID) even though a prior guard (guard let
sourceSurfaceId) made them non-optional; update the entries using v2OrNull and
v2Ref so they use the non-optional properties: replace
sourceSurfaceId?.uuidString with sourceSurfaceId.uuidString and
sourcePaneUUID?.uuidString with sourcePaneUUID.uuidString, and ensure
v2Ref(kind: .surface, uuid: sourceSurfaceId) and v2Ref(kind: .pane, uuid:
sourcePaneUUID) continue to receive the non-optional UUIDs (symbols:
sourceSurfaceId, sourcePaneUUID, v2OrNull, v2Ref).
🪄 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: 60b62169-5d59-407e-b20c-34d6308c59f3

📥 Commits

Reviewing files that changed from the base of the PR and between f98f8c2 and e45b614.

📒 Files selected for processing (6)
  • CLI/cmux.swift
  • Sources/CmuxConfig.swift
  • Sources/SessionPersistence.swift
  • Sources/TerminalController+MoveTabToNewWorkspace.swift
  • Sources/TerminalController.swift
  • Sources/Workspace.swift

Comment thread CLI/cmux.swift
Comment thread CLI/cmux.swift
Comment thread Sources/CmuxConfig.swift Outdated
Comment thread Sources/CmuxConfig.swift Outdated
Comment thread Sources/CmuxConfig.swift Outdated
Comment thread Sources/TerminalController.swift Outdated
Comment thread Sources/TerminalController.swift Outdated
Comment thread Sources/TerminalController.swift Outdated
Comment thread Sources/Workspace.swift Outdated
@greptile-apps

greptile-apps Bot commented May 19, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR introduces note as a first-class project-scoped surface type — cmux note new|open|list|here|path|read|write|append|rm, socket RPCs (note.create/open/list/…), a Notes sidebar tree, durable index storage under .cmux/notes/index.json, MathJax-bundled Markdown preview, and full session/workspace restore support. Multiple previous review rounds addressed error-message hygiene, main-thread filesystem I/O, index/file ordering correctness, concurrency serialization, and localization gaps; the fixes are reflected in the current HEAD.

  • CmuxNoteStore correctly serializes all mutations through storageQueue and writes the index before unlinking in delete; CmuxNoteModels/NoteSupport/CmuxNoteStore are split under the 800-line ceiling as requested.
  • NotesTreeStore.swift (1 183 lines) and NotesTreeStorage.swift (925 lines) are new files that exceed the 800-line ceiling and mix distinct concerns (UI state, file watching, session orchestration, process inspection, filesystem CRUD); a follow-up split similar to the backend domain split would bring them in line with the file-boundary rule.
  • The cli.note.usage/cli.note.globalUsage catalog entries now include all 8 verbs; all note keys carry en+ja entries matching the documented localization floor.

Confidence Score: 5/5

Safe to merge; no correctness defects remain in the changed paths after multiple rounds of fixes.

Multiple previous review rounds addressed concurrency, ordering, I/O-on-main, and localization; the fixes are all reflected in the current HEAD. The only remaining finding is a style concern: two large new files exceed the project's file-size ceiling and bundle more than one concern. These do not affect correctness or security — they are a maintainability signal for a follow-up split, not a reason to hold the merge.

Sources/NotesTreeStore.swift and Sources/NotesTreeStorage.swift — both exceed 800 lines and mix concerns that would benefit from a follow-up split.

Important Files Changed

Filename Overview
Sources/NotesTreeStore.swift 1 183-line ObservableObject mixing UI state, session refresh, file watching, and all mutation operations — over the 800-line ceiling with multiple distinct concerns.
Sources/NotesTreeStorage.swift 925-line file mixing Codable marker types, process inspection (paneProcesses), and filesystem operations — over the 800-line ceiling.
Sources/CmuxNoteStore.swift New 785-line store for indexed note CRUD. Index-write-before-delete ordering correctly applied in delete; serialization via storageQueue is correct.
Sources/CmuxNoteModels.swift Pure Codable models, result types, and the context resolver. Clean separation from the store.
Sources/NoteSupport.swift Slimmed to 352 lines of slug-validation and filesystem helpers after the split. Symlink containment check in absoluteBodyPath is thorough.
Sources/TerminalController+Notes.swift 852-line extension containing all note RPC handlers. Single-responsibility extension pattern consistent with other TerminalController extensions, but barely over the 800-line threshold.
Sources/Workspace.swift Notes additions use noteProjectRootOffMain and Task.detached correctly; session restore no longer probes the filesystem on the main actor.
CLI/cmux.swift Adds all 8 note verbs with correct localization via String(localized:defaultValue:) and matching catalog entries.
Resources/Localizable.xcstrings All note keys now carry en+ja entries; cli.note.usage and cli.note.globalUsage include all 8 verbs. Additional locales are en+ja-only per the developer's policy decision.
web/app/[locale]/docs/custom-commands/page.tsx New Surface tab bar section uses t() throughout with en+ja translations matching the repo's documented web localization floor.

Sequence Diagram

sequenceDiagram
    participant CLI as cmux note CLI
    participant Socket as ControlSocket RPC
    participant TC as TerminalController+Notes
    participant Store as CmuxNoteStore
    participant FS as Filesystem (.cmux/notes/)
    participant UI as MarkdownPanel / NotesTreeStore

    CLI->>Socket: note.create / note.open / note.write
    Socket->>TC: v2NoteCreate / v2NoteOpenSplit / v2NoteWrite
    TC->>TC: v2MainSync (resolve workspace/surface)
    TC-->>Store: createOrOpenAsync / writeContent (off-main)
    Store->>Store: withStoreLockAsync (storageQueue.async)
    Store->>FS: loadIndex → mutate → writeIndex (atomic)
    Store->>FS: ensureBodyFile / write content
    Store-->>TC: CmuxNoteStoreResult
    TC->>TC: v2MainSync (open MarkdownPanel, focus)
    TC-->>Socket: JSON result
    Socket-->>CLI: slug / path / output

    UI->>Store: CmuxNoteStore.list (via NotesTreeStorage)
    Store->>Store: withStoreLock (storageQueue.sync)
    Store->>FS: loadIndex + sort by mtime
    Store-->>UI: [CmuxNoteRecord]
    UI->>UI: reload() → buildChildren → NotesTreeNode tree
Loading

Reviews (75): Last reviewed commit: "Merge origin/main into notes surface PR" | Re-trigger Greptile

Comment thread Sources/TerminalController.swift Outdated
Comment thread Sources/TerminalController.swift Outdated
Comment thread Sources/CmuxConfig.swift Outdated
coderabbitai[bot]
coderabbitai Bot previously requested changes May 19, 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: 2

🤖 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/Workspace.swift`:
- Around line 1210-1215: The helper noteSlugForConfigSurface currently falls
back to NoteSupport.autoSlug() which produces a new random slug each launch;
change it so that when surface.name is missing or invalid you either (A) derive
a deterministic slug from a stable layout seed/state (e.g. use a stable
identifier from the CmuxSurfaceDefinition such as surface.id or a layoutSeed
value) and pass that through NoteSupport.validateSlug, or (B) surface validation
fails and you propagate/reject the malformed config so the caller can handle it;
update noteSlugForConfigSurface to use NoteSupport.validateSlug on the
deterministic fallback (or throw/return an error) instead of calling
NoteSupport.autoSlug().
- Around line 865-875: The saved snapshot's noteSlug must be validated before
using it to construct a filesystem path: in the block that reads
snapshotMarkdown.noteSlug, first verify the slug matches the allowed pattern
(e.g. /^[a-z0-9-]+$/) and only then call NoteSupport.notePath(forSlug:
projectRoot:) (with projectRoot from noteProjectRoot()); if validation fails or
the resulting path doesn't exist, set restorePath to snapshotMarkdown.filePath
instead; keep the existing FileManager.default.fileExists(atPath:) check but
perform it only after the slug validation.
🪄 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: f0b454f0-c4dc-42e3-acd0-c08676496f32

📥 Commits

Reviewing files that changed from the base of the PR and between e45b614 and 7a62569.

📒 Files selected for processing (2)
  • Sources/TerminalController.swift
  • Sources/Workspace.swift

Comment thread Sources/Workspace.swift Outdated
Comment thread Sources/Workspace.swift Outdated

@cubic-dev-ai cubic-dev-ai 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.

3 issues found across 6 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="Sources/TerminalController.swift">

<violation number="1" location="Sources/TerminalController.swift:9385">
P2: Invalid `direction` values are silently coerced to `.right` instead of returning `invalid_params`.</violation>
</file>

<file name="Sources/CmuxConfig.swift">

<violation number="1" location="Sources/CmuxConfig.swift:3360">
P2: `ensureNoteFile` can report success even when file creation fails because `createFile`'s `Bool` result is ignored.</violation>

<violation number="2" location="Sources/CmuxConfig.swift:3411">
P2: `deleteNote` is not fully idempotent under races: a file can disappear after `fileExists` and still throw on `removeItem`.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread Sources/TerminalController.swift Outdated
Comment thread Sources/CmuxConfig.swift Outdated
Comment thread Sources/CmuxConfig.swift Outdated

@cubic-dev-ai cubic-dev-ai 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.

1 issue found across 2 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread Sources/Workspace.swift Outdated
Comment thread CLI/cmux.swift Outdated
Comment thread Sources/Workspace.swift Outdated
Comment thread CLI/cmux.swift Outdated
Comment thread Sources/TerminalController+Notes.swift
coderabbitai[bot]
coderabbitai Bot previously requested changes May 19, 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: 5

🤖 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 `@Resources/Localizable.xcstrings`:
- Around line 113745-113746: The two localization entries cli.note.globalUsage
and cli.note.usage currently contain verbatim English for many high-confidence
locales (de, es, fr, it, ko, nb, pt-BR, ru, uk, zh-Hans, zh-Hant); remove these
copied-English string values and replace them with proper localized strings (or
an explicit non-production placeholder status) for each listed locale so they no
longer present English in production; update the corresponding stringUnit state
metadata for each locale (e.g., set to "needs-translation" or the correct
translated state) and ensure only authentic translations or approved
placeholders exist for cli.note.globalUsage and cli.note.usage rather than
copied English.

In `@Sources/NoteSupport.swift`:
- Around line 116-123: ensureNoteFile currently treats any existing path
(including directories) as a valid note file; update ensureNoteFile, listNotes,
and deleteNote to guard that paths are regular files. Specifically: in
ensureNoteFile, when FileManager.default.fileExists(atPath:) is true, verify the
path is a regular file (e.g., use FileManager.default.attributesOfItem(atPath:)
or URL.resourceValues(forKeys:[.isRegularFileKey/.isDirectoryKey])) and throw a
CocoaError if it’s a directory; when creating the file, proceed only if the path
is absent or a regular file; in listNotes, filter out entries where
isRegularFile is false so directories aren’t returned; in deleteNote, check the
target is a regular file before calling removeItem(atPath:) and throw if it’s a
directory. Apply these checks inside the functions named ensureNoteFile,
listNotes, and deleteNote.

In `@Sources/TerminalController`+Notes.swift:
- Line 14: Replace all uses of String(describing: error) in the Notes-related
error responses with error.localizedDescription to avoid leaking Swift type
names; specifically update the return .err(...) expressions that currently pass
String(describing: error) (the occurrences flagged in the Notes controller) to
instead pass error.localizedDescription so the API returns a user-friendly
message—ensure you change every instance mentioned (the three other occurrences
found alongside the one shown) so they match the existing handlers that already
use error.localizedDescription.

In `@Sources/Workspace.swift`:
- Around line 11136-11138: The noteProjectRoot() helper currently calls
NoteSupport.projectRoot(forCwd: currentDirectory) which can pick up the stale
snapshot value; change it to resolve from the live project location at restore
time instead of the saved snapshot. Update noteProjectRoot() to call
NoteSupport.projectRoot(forCwd: liveProjectDirectory) where liveProjectDirectory
is derived from the runtime workspace/project root (e.g., the workspace's
current project directory or process CWD) and ensure restoreSessionSnapshot(_:)
does not overwrite that live value before note panels are recreated (if
restoreSessionSnapshot assigns currentDirectory from the snapshot, move that
assignment or defer rebuilding note panels until after you recompute note roots
from the live location). Use the symbols noteProjectRoot(),
NoteSupport.projectRoot(forCwd:), and restoreSessionSnapshot(_:) to locate and
implement the change.
- Around line 11145-11164: The helper newNoteSurface currently calls
NoteSupport.notePath(forSlug:projectRoot:) with the raw slug when
createIfMissing is false; ensure slug validation is performed inside this
boundary by validating the slug against the allowed pattern (e.g.
/^[a-z0-9-]+$/) before computing the read-only path, and return nil (or an
appropriate error) for invalid slugs. Update newNoteSurface to validate slug at
the top of the else branch (the path that uses NoteSupport.notePath) so callers
no longer must remember to validate, and keep the existing use of
NoteSupport.ensureNoteFile(...) in the createIfMissing=true branch unchanged.
🪄 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: 592e1d0e-9186-4131-9f6a-99cc2e4ff4ba

📥 Commits

Reviewing files that changed from the base of the PR and between 7a62569 and 1cf61ec.

📒 Files selected for processing (10)
  • CLI/cmux.swift
  • Resources/Localizable.xcstrings
  • Sources/CmuxConfig.swift
  • Sources/NoteSupport.swift
  • Sources/SessionPersistence.swift
  • Sources/TerminalController+Notes.swift
  • Sources/TerminalController.swift
  • Sources/Workspace.swift
  • cmux.xcodeproj/project.pbxproj
  • cmuxTests/CmuxConfigTests.swift

Comment thread Resources/Localizable.xcstrings Outdated
Comment thread Sources/NoteSupport.swift Outdated
Comment thread Sources/TerminalController+Notes.swift Outdated
Comment thread Sources/Workspace.swift Outdated
Comment thread Sources/Workspace.swift Outdated
coderabbitai[bot]
coderabbitai Bot previously requested changes May 19, 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: 7

🤖 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/NoteSupport.swift`:
- Around line 17-30: Replace the hardcoded validation messages in NoteError and
the slug-checking code with localized strings using
String(localized:defaultValue:) (or your app's localization API) and add
matching keys to the app's string catalogs; specifically, change the messages
thrown in NoteError (e.g., "slug is empty", "slug is longer than %d characters",
"slug may contain only lowercase letters, digits, and hyphens", "slug must not
start with a hyphen") to use localization keys (e.g., "note.slug_empty",
"note.slug_too_long", "note.slug_invalid_chars",
"note.slug_cannot_start_hyphen") and supply defaultValue for each, add those
keys to every supported locale catalog, and update the other occurrence
mentioned (the block around lines 233-240) the same way so all user-facing
validation and NoteError.localizedDescription strings are localized.
- Around line 201-217: In deleteNote(slug:projectRoot:) replace the
FileManager.removeItem(atPath:) call with an atomic Darwin.unlink(2) call:
obtain a C path for the computed path (e.g. via withCString or
fileSystemRepresentation), call unlink, and map results so that unlink returning
0 -> return true, errno == ENOENT -> return false, errno == EISDIR (directory)
-> throw NoteError.notRegularFile, and any other errno -> rethrow/convert to an
error; keep the existing validateSlug(notePath:) and isRegularFile checks but
remove reliance on removeItem’s semantics to avoid the TOCTOU race.

In `@Sources/TerminalController`+Notes.swift:
- Around line 22-33: The RPC responses in v2NoteOpen (and related handlers)
return hardcoded English messages; replace the inline message: string literals
in the V2CallResult.err calls with localized strings using
String(localized:defaultValue:) (or your app's localization helper) and create
matching keys in the Localizable.xcstrings catalog (e.g., keys for
"invalid_params.missing_slug" and the validation error fallback). Update
v2NoteOpen, the error branch around NoteSupport.validateSlug, and all other v2*
handlers called by v2NoteOpenSplit/V2CallResult to use localized keys instead of
literal text, passing error.localizedDescription only where appropriate as a
parameterized defaultValue or variable in the localized string so the API body
is locale-aware.
- Around line 8-18: The create-note path currently treats an explicitly empty
providedSlug as omitted and calls NoteSupport.autoSlug(), but we should reject
explicit empty slugs like other endpoints do: change the logic around
v2String(params, "slug")/providedSlug so that if providedSlug is present but
empty you return .err(code: "invalid_params", message: "slug must not be empty",
data: nil) instead of falling through to NoteSupport.autoSlug(); keep using
NoteSupport.validateSlug when providedSlug is non-empty and preserve
auto-generation only when the slug key is truly absent (nil).
- Around line 62-70: The API currently returns raw filesystem errors by
inserting error.localizedDescription into the top-level "message" and
"error_description" fields when building the response (see the result =
.err(...) blocks in TerminalController+Notes.swift); change these to stable,
generic user-facing messages (e.g., "I/O error while accessing the note" or
"Unable to read/write note") and remove any direct inclusion of
localizedDescription from the response body, and instead send the full error to
internal logging (use the controller's logger or a similar internal log call) so
callers get a consistent, non-platform-specific message; apply the same change
to all occurrences where result = .err(...) populates "message" and
"error_description" (including the other ranges mentioned).

In `@Sources/Workspace.swift`:
- Around line 545-548: Only populate noteSlug when the markdown file is inside
this workspace's note root: compute the slug with NoteSupport.slug(forNotePath:)
only if markdownPanel.filePath is located under this workspace's resolved note
root (e.g. check path.hasPrefix(self.resolvedNoteRoot) or use a
project-root-aware containment helper), otherwise pass nil into
SessionMarkdownPanelSnapshot. Update the code that constructs markdownSnapshot
in the Workspace (the SessionMarkdownPanelSnapshot creation) to perform that
containment test and conditionally assign the slug.
- Around line 11139-11140: The noteProjectRoot() helper currently calls
NoteSupport.projectRoot(forCwd: currentDirectory) which uses the local
filesystem but currentDirectory can be a remote/SSH path; change
noteProjectRoot() to first detect remote workspaces (e.g., check
workspace.isRemote or an equivalent flag on the current Workspace/Context) and
if remote either return an explicit local note root (map to a configured
localNotesRoot via NoteSupport or a LocalNotesDirectory constant) or fail fast
by throwing/returning nil so note create/open/restore are rejected for remote
workspaces; apply the same remote-check and mapping/early-reject change to the
other helper calls that use currentDirectory for notes (the usages around the
note create/open/restore code paths).
🪄 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: d67ce4ab-c48b-47ef-ae6c-ce8c960ce6ae

📥 Commits

Reviewing files that changed from the base of the PR and between 1cf61ec and 866206c.

📒 Files selected for processing (4)
  • Resources/Localizable.xcstrings
  • Sources/NoteSupport.swift
  • Sources/TerminalController+Notes.swift
  • Sources/Workspace.swift

Comment thread Sources/NoteSupport.swift Outdated
Comment thread Sources/NoteSupport.swift Outdated
Comment thread Sources/TerminalController+Notes.swift Outdated
Comment thread Sources/TerminalController+Notes.swift Outdated
Comment thread Sources/TerminalController+Notes.swift Outdated
Comment thread Sources/Workspace.swift
Comment thread Sources/Workspace.swift Outdated
Comment thread Resources/Localizable.xcstrings Outdated
coderabbitai[bot]
coderabbitai Bot previously requested changes May 19, 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: 2

Caution

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

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

264-280: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Compute the restore note root after applying snapshot.currentDirectory.

sessionRestoreNoteProjectRoot is cached before currentDirectory is updated from the snapshot, so note restore can resolve slugs against the stale pre-restore cwd and fall back to the old absolute path after a project move.

💡 Minimal fix
-        sessionRestoreNoteProjectRoot = noteProjectRoot()
-        defer { sessionRestoreNoteProjectRoot = nil }
-
         let restoredRemoteConfiguration = snapshot.remote?.workspaceConfiguration()
         if let restoredRemoteConfiguration {
             configureRemoteConnection(
                 restoredRemoteConfiguration,
                 autoConnect: !SessionRestorePolicy.isRunningUnderAutomatedTests()
@@
         let normalizedCurrentDirectory = snapshot.currentDirectory.trimmingCharacters(in: .whitespacesAndNewlines)
         if !normalizedCurrentDirectory.isEmpty {
             currentDirectory = normalizedCurrentDirectory
         }
+
+        sessionRestoreNoteProjectRoot = noteProjectRoot()
+        defer { sessionRestoreNoteProjectRoot = nil }
🤖 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/Workspace.swift` around lines 264 - 280,
sessionRestoreNoteProjectRoot is being captured before applying
snapshot.currentDirectory, causing note restore to use a stale working
directory; move the assignment sessionRestoreNoteProjectRoot = noteProjectRoot()
(and its defer cleanup) to after you update currentDirectory from
snapshot.currentDirectory (i.e. after computing normalizedCurrentDirectory and
assigning currentDirectory) so noteProjectRoot() sees the restored cwd; keep the
existing defer { sessionRestoreNoteProjectRoot = nil } and leave
configureRemoteConnection / disconnectRemoteConnection logic as-is.
🤖 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 `@Resources/Localizable.xcstrings`:
- Around line 113747-113768: The new localization keys (e.g.,
"note.error.slugEmpty", "note.error.slugTooLong", "note.error.slugInvalidChars",
"note.error.slugStartsWithHyphen", "note.error.invalidSlug", and all
"rpc.note.error.*" keys added) only include en and ja; update
Resources/Localizable.xcstrings by adding translated stringUnit entries for
every locale already present in this catalog (ar, bs, da, de, es, fr, it, ko,
nb, pl, pt-BR, ru, th, tr, uk, zh-Hans, zh-Hant, etc.) for each of those keys so
that each key has matching localized values across all supported locales per the
project guideline.

In `@Sources/Workspace.swift`:
- Around line 1331-1341: The code is treating any non-nil return from
newNoteSurface as a freshly placed surface and closing the placeholder even when
newNoteSurface returned an existing panel in another pane; to fix, change
newNoteSurface so that when reuseExisting is false it calls
newMarkdownSurface(...) instead of openOrFocusMarkdownSurface(...), ensuring the
workspace-wide reuse path is not used for layout materialization, and update the
corresponding duplicate branches (the block using
noteSlugForConfigSurface/newNoteSurface and the similar block at the other
location) so they only close the placeholder panel (closePanel(panelId, force:
true)) when the returned panel actually belongs to the target pane, preserving
title setting (setPanelCustomTitle) and focus assignment (focusPanelId)
behavior.

---

Outside diff comments:
In `@Sources/Workspace.swift`:
- Around line 264-280: sessionRestoreNoteProjectRoot is being captured before
applying snapshot.currentDirectory, causing note restore to use a stale working
directory; move the assignment sessionRestoreNoteProjectRoot = noteProjectRoot()
(and its defer cleanup) to after you update currentDirectory from
snapshot.currentDirectory (i.e. after computing normalizedCurrentDirectory and
assigning currentDirectory) so noteProjectRoot() sees the restored cwd; keep the
existing defer { sessionRestoreNoteProjectRoot = nil } and leave
configureRemoteConnection / disconnectRemoteConnection logic as-is.
🪄 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: 0c7f7431-fc51-4ef9-9694-4ca946983c90

📥 Commits

Reviewing files that changed from the base of the PR and between 866206c and f0ca6cc.

📒 Files selected for processing (5)
  • Resources/Localizable.xcstrings
  • Sources/NoteSupport.swift
  • Sources/TerminalController+Notes.swift
  • Sources/Workspace.swift
  • cmux.xcodeproj/project.pbxproj

Comment thread Resources/Localizable.xcstrings Outdated
Comment thread Sources/Workspace.swift Outdated
Comment thread CLI/cmux.swift
Comment thread Sources/TerminalController+Notes.swift Outdated
Comment thread Sources/TerminalController+Notes.swift

Copy link
Copy Markdown
Contributor

@coderabbitai review

@coderabbitai

coderabbitai Bot commented May 19, 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.

coderabbitai[bot]
coderabbitai Bot previously requested changes May 19, 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: 2

🤖 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/Workspace.swift`:
- Around line 11154-11199: newNoteSurface is running NoteSupport.ensureNoteFile
/ noteFileExists / notePath synchronously on the `@MainActor` and needs to perform
that filesystem I/O off the main thread: move the code that calls
NoteSupport.ensureNoteFile, NoteSupport.noteFileExists and NoteSupport.notePath
into an async background task (e.g. Task.detached or a background queue),
capture the resulting filePath or error there, then hop back to the main actor
to call openOrFocusMarkdownSurface or newMarkdownSurface; preserve the existing
slug validation, error logging messages and the reuseExisting branch logic, but
only do UI work (openOrFocusMarkdownSurface / newMarkdownSurface) on the main
actor after awaiting the background result.
- Around line 264-265: sessionRestoreNoteProjectRoot is being captured before
applying snapshot.currentDirectory so noteProjectRoot() resolves against the old
cwd; to fix, move the assignment sessionRestoreNoteProjectRoot =
noteProjectRoot() to after the code that applies snapshot.currentDirectory (the
block that sets snapshot.currentDirectory on the workspace), keeping the
existing defer { sessionRestoreNoteProjectRoot = nil } to clear it; in short,
call noteProjectRoot() only after snapshot.currentDirectory has been applied so
sessionRestoreNoteProjectRoot caches the post-restore root.
🪄 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: d77148cb-3321-4d8f-ab9d-2045d6334bf9

📥 Commits

Reviewing files that changed from the base of the PR and between 866206c and 9369a6c.

📒 Files selected for processing (6)
  • CLI/cmux.swift
  • Resources/Localizable.xcstrings
  • Sources/NoteSupport.swift
  • Sources/TerminalController+Notes.swift
  • Sources/Workspace.swift
  • cmux.xcodeproj/project.pbxproj

Comment thread Sources/Workspace.swift Outdated
Comment thread Sources/Workspace.swift Outdated
Comment thread Sources/Workspace.swift Outdated

@cubic-dev-ai cubic-dev-ai 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.

1 issue found across 1 file (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread Sources/Workspace.swift Outdated
@cursor

cursor Bot commented Jul 14, 2026

Copy link
Copy Markdown

Bugbot is paused — on-demand spend limit reached

Bugbot uses usage-based billing for this team and has hit its on-demand spend limit.

A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue.

austinywang and others added 4 commits July 13, 2026 22:25
Two note-surface bugs:

- The pane focus flash animated through detached asyncAfter closures;
  any interruption between a fade-in segment and its fade-out left the
  accent ring stuck faintly visible — the "random vertical line"
  parked on note pane edges. The pattern now runs in one cancellable
  task whose every exit path lands on opacity 0.

- Renaming a note round-tripped through the index write before any
  title surface updated, so the header, tab, and derived workspace
  title flashed old -> new. The rename now applies optimistically:
  synchronously to the renaming panel, via the shared retitle
  notification to siblings and the tree, and the store result
  reconciles (or rolls back to the previous title on failure).
  Regression test asserts the retitle is visible synchronously.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
waitForPendingReloadForTesting awaited only reloadTask, racing the
coalesce scheduler under load — intermittent stale-tree failures
across the observation suite (a different victim each run). Await the
coalesce task first.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The branch (via main) carries cmux-side #7997 for the tab bar relayout
feedback loop but not bonsplit-side #180 — running half of a two-sided
layout fix leaves stale split geometry, which draws the split divider
at an old boundary inside a visually-single pane: the stationary
vertical line cutting through note headers.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Replace the accreted note header (custom NSTextField, sizing-text
overlay, underline rectangles, focus-responder overrides) with a
from-scratch structure that mirrors the plain markdown viewer:

- a slim trailing-actions strip (find, preview toggle, typography,
  overflow) with no divider, like Notion's top-right page controls
- a large chromeless page title at the top of the content column,
  aligned to the preview's max-width column, with a faint Untitled
  placeholder; click to edit in place (plain SwiftUI TextField +
  FocusState), Enter/click-away commits, Escape cancels

The four NoteTitle* files are deleted outright; renames keep the
optimistic one-repaint path and external retitles still flow in.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@cursor

cursor Bot commented Jul 14, 2026

Copy link
Copy Markdown

Bugbot is paused — on-demand spend limit reached

Bugbot uses usage-based billing for this team and has hit its on-demand spend limit.

A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue.

austinywang and others added 23 commits July 14, 2026 18:33
A note is just a markdown file in a managed local directory, so the
note-only chrome goes away entirely (the Notion-style page header
included): every markdown surface now shares the same structure —
file path header, one compact trailing control set (Save for plain
files, find, source/preview toggle, typography, overflow menu), and a
new formatting toolbar over the source editor (H1/H2/H3, bold/italic/
strikethrough, bullet/numbered/task lists, quote, link) inserting
markdown at the selection with undo support; headings toggle instead
of stacking. Formatting edits are pure functions pinned by unit
tests; toolbar labels localized in English and Japanese. Dead
copy-toolbar and confirmation-flash code removed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ce-type

# Conflicts:
#	.github/swift-file-length-budget.tsv
#	Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/BetaFeaturesSection.swift
#	Packages/macOS/CmuxSettingsUI/Tests/CmuxSettingsUITests/SettingsRowAnchorResolutionTests.swift
#	Resources/Localizable.xcstrings
#	Sources/Panels/FilePreviewTextEditor.swift
#	Sources/RestorableAgentSession.swift
#	Sources/RightSidebarPanelView.swift
#	Sources/TerminalController.swift
#	cmux.xcodeproj/project.pbxproj
#	vendor/bonsplit
The merge union pushed it to 507; the theme application moves to its
own extension file so the PR's newly-tracked-file gate stays green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The blind hunk-union of this merge's pbxproj conflict duplicated
definitions main had moved (and my dedupe pass then orphaned a
multi-line body). Reconstruct deterministically: main's pbxproj as
the base with every branch-added entry re-inserted verbatim at its
anchored position. Project loads; all five targets and the test
wiring lint verify.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Main's a630590 exports ghostty_renderer_event_e, required by the
merged CmuxTerminalCore profiling code; the merge had kept the
branch's older pointer once again.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The pbxproj rebuild re-inserted branch-side entries whose files main
removed (background-mount policy, its tests, the old beta settings
view); purge references whose file exists nowhere in the tree.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The rebuild sourced insertions from the pre-merge project snapshot,
which predates the theme-extension split. Audit confirms no other
tracked Swift source is unwired.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
OpenStep plist strings containing '+' must be quoted; the unquoted
path made Xcode reject the whole project as damaged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Take main's FilePreviewTextEditor (session-owned view construction)
instead of a hand-merged hybrid: the accent caret ports into its
theme application, the current-line highlight applies at
attachTextView, and the markdown call site matches the seven-argument
API. The interim theme-extension split is unnecessary against main's
467-line file and is removed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
onPointerDown, highlightsCurrentLine, and currentLineHighlightColor
back the branch's CurrentLineHighlight extension; main's class body
replaced ours without them. Theme application colors the highlight
again.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The stationary vertical line cutting through pane headers is the tab
right-border separator painting below the bar when relayout proposes
an oversized item; the separator (and its container) are now
hard-capped to the tab height. Submodule branch
fix-tab-separator-height-clamp, pushed to the fork.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ce-type

# Conflicts:
#	.github/swift-file-length-budget.tsv
#	Sources/KeyboardShortcutSettings.swift
The merge reset bonsplit to main's tip, dropping the separator-clamp
commit (which descends from that tip); re-pin it. Ghostty advances to
main's b4b6d69.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The between-tab right-border separator painted below the bar into the
pane header under oversized relayout proposals — the stationary faint
gray vertical line. The target visual has no between-tab separators;
delete the drawing outright (bonsplit b0778ea).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
SwiftUI Divider takes the proposed height; under the split-relayout
disease (#8139 family) 1px chrome in this pane has painted far
outside its row. A hard 1x14 rectangle plus a clipped row cannot.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The 1x14 group-divider ticks stack visually with the horizontal row
dividers and read as a stray vertical line cutting through the note
header. Whitespace separates the groups instead.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ce-type

# Conflicts:
#	Packages/macOS/CmuxSettingsUI/Tests/CmuxSettingsUITests/SettingsRowAnchorResolutionTests.swift
#	Sources/TerminalController+Capabilities.swift
#	cmux.xcodeproj/project.pbxproj
#	vendor/bonsplit
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Notes and markdown both render main's untouched viewer chrome: the
file path header with the original trailing controls (typography,
mode, copy, external open). All branch chrome — compact control set,
find button, formatting toolbar row — is unwired, leaving a clean
baseline to direct changes from. The only difference from main's file
is the cancellable focus-flash task, kept because the asyncAfter
pattern left the flash ring stuck visible.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The branch had overridden the panel icon to doc.text so notes read as
plain documents; with notes now rendering through the untouched
markdown viewer, the tab and path-header icon match markdown files
(doc.richtext), same as main.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

stale-revisit Closed after 30+ days without activity; preserved for possible revisit or reopening.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: add note surface type (project-scoped markdown notes)

3 participants