Skip to content

Restore search-result row highlight in CmuxSettingsUI#5012

Merged
azooz2003-bit merged 23 commits into
mainfrom
feat-settings-search-highlight
May 30, 2026
Merged

Restore search-result row highlight in CmuxSettingsUI#5012
azooz2003-bit merged 23 commits into
mainfrom
feat-settings-search-highlight

Conversation

@azooz2003-bit
Copy link
Copy Markdown
Contributor

@azooz2003-bit azooz2003-bit commented May 30, 2026

What

When you search in Settings and click a result, the navigated-to row should flash an accent-colored rounded border (the "find me" pulse). The legacy in-app settings window did this; the SPM reimplementation (CmuxSettingsUI) kept the search-to-scroll path but dropped the highlight, and the rows were never anchored, so even the scroll-to-center quietly no-oped.

How

Ports the legacy affordance into the package:

  • SettingsSearchHighlight.swift adds SettingsSearchHighlightState, its environment key, the settingsSearchAnchor(s) view modifiers, and the pulsing-border TimelineView modifier (same fade curve as legacy).
  • SettingsSearchIndex.anchorID(forSettingsPath:) maps a row's dotted cmux.json path to the sidebar/search anchor id. The map is built automatically from the curated entries' dotted synonym tokens, so there's no second hand-maintained id table.
  • SettingsCardRow resolves the path it already declares via configurationReview into that anchor id, tags itself with .id(...), and pulses when it's the active target.
  • SettingsWindowRoot injects the index and arms the highlight (anchor + token + start time) on every highlighting navigation request, mirroring legacy applySettingsNavigation.

No new keyboard shortcuts, no app-target changes; the behavior lives entirely in the settings package.

Test

swift test on CmuxSettingsUI covers the new resolver: the "Show Branch + Directory in Sidebar" path resolves to its sidebar hit anchor, resolved anchors always correspond to a real indexed entry, and unknown paths resolve to nil.

Visual one-to-one parity with the legacy highlight is verified by dogfooding a tagged build (search a query, click a hit, confirm the row scrolls into view and pulses the accent border).

🤖 Generated with Claude Code


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


Summary by cubic

Restores the search-result highlight in CmuxSettingsUI so clicking a result scrolls to and pulses the exact row or section. Also fixes the Settings window not reopening after close.

  • Bug Fixes

    • Search shows only real, reachable rows; headers/custom blocks highlight; duplicate anchors resolved; navigation uses an eager detail stack and a single scroll for reliable centering/pinning.
    • Rows map cmux.json paths to stable anchors; explicit anchors for action/picker/custom rows; fixed the "Sidebar Branch Layout" synonym; restored anchors for Terminal Config, Desktop Notifications, Browsing History, and Reset Palette; titles match row labels.
    • Settings window reliably reopens after close; removed temporary DEBUG window-state probes; ignored local artifacts/.
  • Refactors

    • Removed unused settingsSearchAnchor(_:); highlight state/index symbols are internal to avoid app-target clashes.
    • Tests: added bidirectional SettingsRowAnchorResolutionTests with anchor-uniqueness checks; extended SettingsSearchIndexTests; added DEBUG-only ':all' search sentinel; lazy-loaded the keyboard-shortcuts list for faster open.

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

Review in cubic

Summary by CodeRabbit

  • New Features

    • Rows now expose search anchors (explicit or derived) so search results can scroll to and momentarily highlight rows with a pulsing accent.
    • Section headers carry section identifiers so headers can be targeted/highlighted.
    • When no search index is present, rows opt out gracefully.
  • Tests

    • Added tests validating path→anchor resolution, anchor reachability, and unknown-path handling.
  • Content

    • Expanded searchable synonym terms for several curated settings.

Review Change Stack

The SPM settings reimplementation kept the search-to-scroll navigation
but dropped the pulse that flashed an accent border on the navigated-to
row, so clicking a search hit silently jumped without confirming which
row matched. Legacy SettingsView drove this through an environment
highlight state plus a per-row TimelineView border; the package's rows
were never anchored, so even the scroll-to-center no-oped.

Port the legacy affordance: SettingsSearchHighlight adds the highlight
state, environment plumbing, and the pulsing-border modifier. Each
SettingsCardRow now resolves the cmux.json path it already declares via
configurationReview into the sidebar/search anchor id through a new
SettingsSearchIndex.anchorID(forSettingsPath:), tags itself with that
id, and pulses when it is the active highlight target. The settings root
injects the index and arms the highlight on every highlighting
navigation request.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown

vercel Bot commented May 30, 2026

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

Project Deployment Actions Updated (UTC)
cmux Ready Ready Preview, Comment May 30, 2026 7:57pm
cmux-staging Building Building Preview May 30, 2026 7:57pm

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 30, 2026

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

Rows can declare search anchors (explicit or derived from config paths); SettingsSearchIndex maps dotted paths to curated anchor ids; the window resolves an anchor, scrolls to it, and triggers a timed pulsing highlight on the matching row.

Changes

Settings search highlighting

Layer / File(s) Summary
Search highlight state and environment contracts
Packages/CmuxSettingsUI/Sources/CmuxSettingsUI/Navigation/SettingsSearchHighlight.swift
Defines SettingsSearchHighlightState and environment keys to inject highlight state and SettingsSearchIndex? into the view tree.
Visual highlight rendering and view helpers
Packages/CmuxSettingsUI/Sources/CmuxSettingsUI/Navigation/SettingsSearchHighlight.swift
Adds settingsSearchAnchor(_:) / settingsSearchAnchors(_:) / settingsSearchHighlight(_:) helpers, a SettingsSearchHighlightModifier, and TimelineView-driven opacity timing for the pulsing rounded border.
Path-to-anchor mapping
Packages/CmuxSettingsUI/Sources/CmuxSettingsUI/Navigation/SettingsSearchIndex.swift
Builds a private pathAnchorIDs map from curated entry synonym tokens and exposes public func anchorID(forSettingsPath:) -> String?; removes prior uncatalogued backfill helpers.
Card row anchor derivation and tagging
Packages/CmuxSettingsUI/Sources/CmuxSettingsUI/Chrome/SettingsCardRow.swift
Reads settingsSearchIndex from environment, derives per-row anchor ids from an explicit searchAnchorID or configurationReview.paths via the index, and applies .settingsSearchAnchors(searchAnchorIDs).
Window-level highlight state and scroll navigation
Packages/CmuxSettingsUI/Sources/CmuxSettingsUI/Scene/SettingsWindowScene.swift
Adds @State for active highlight (anchor, token, startedAt), injects the search index and highlight state into the environment, and arms/clears the highlight immediately before scrolling for deep anchors.
Section header and call-site updates
Packages/CmuxSettingsUI/Sources/CmuxSettingsUI/Chrome/SettingsSectionHeader.swift, Packages/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/*
Makes SettingsSectionHeader accept an optional section id and applies section-scoped highlight tagging; updates many section files to pass section: metadata and add per-row or per-section settingsSearchAnchors or explicit searchAnchorID values.
Curated synonym adjustments
Packages/CmuxSettingsUI/Sources/CmuxSettingsUI/Navigation/CuratedSettingEntry+Default.swift
Tweaks curated entry synonym token strings (Account, App, Terminal, SidebarAppearance, Browser) used when building the index’s path→anchor mapping.
Path resolution validation and anchor wiring tests
Packages/CmuxSettingsUI/Tests/CmuxSettingsUITests/*
Adds tests asserting SettingsSearchIndex.anchorID(forSettingsPath:) resolves curated paths to anchors, resolved anchors exist in the index, unknown paths return nil, and that every curated .setting entry is reachable from at least one row anchor or explicit per-row anchor.

Sequence Diagram

sequenceDiagram
  participant User
  participant SettingsWindowRoot
  participant SettingsSearchIndex
  participant ScrollView
  participant SettingsCardRow
  participant SettingsSearchHighlightModifier
  User->>SettingsWindowRoot: navigateTo(settings path)
  SettingsWindowRoot->>SettingsSearchIndex: anchorID(forSettingsPath:)
  SettingsWindowRoot->>ScrollView: scrollTo(anchorID)
  SettingsWindowRoot->>SettingsSearchHighlightModifier: set highlight state (anchor, token, startedAt)
  SettingsCardRow->>SettingsSearchHighlightModifier: matched anchor -> render pulsing accent
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • manaflow-ai/cmux#4975: Prior work on SettingsSearchIndex and search/index infrastructure this PR builds on.
  • manaflow-ai/cmux#4245: Adds settings and anchors for browser-related settings that integrate with this anchor/highlight system.
  • manaflow-ai/cmux#3730: Related settings-search/index updates adding sidebar appearance path mappings used by the new resolution logic.

Poem

🐰 I hop through settings, tags held tight,
I name each path to guide the light,
A scroll, a glow, a gentle cheer—
The matched row pulses bright and clear,
Hooray for anchors, crisp and right.


Caution

Pre-merge checks failed

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

  • Ignore

❌ Failed checks (1 error, 1 warning)

Check name Status Explanation Resolution
Cmux Full Internationalization ❌ Error New user-facing search result titles in CuratedSettingEntry+Default.swift (~140 entries) are not localized using String(localized:defaultValue:) and lack xcstrings catalog entries. Wrap all CuratedSettingEntry title values in String(localized:defaultValue:) with unique localization keys, or mark titles as internal/non-localized if only English is required for this SPM release.
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (16 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: restoring search-result row highlight functionality in CmuxSettingsUI.
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 All views @MainActor, SettingsSearchHighlightState is Sendable, SettingsSearchIndex immutable Sendable, @State mutations safe, no mutable Sendable refs or implicit MainActor issues.
Cmux Swift Blocking Runtime ✅ Passed PR uses TimelineView with finite explicit schedule (not Task.sleep), proper DispatchQueue.main.async for UI scheduling, no semaphores/locks/sleeps in production code.
Cmux No Hacky Sleeps ✅ Passed PR contains only Swift files (.swift); check scope explicitly covers TypeScript, JavaScript, shell, and non-Swift build/runtime scripts. Swift timing is covered by separate check.
Cmux Algorithmic Complexity ✅ Passed All algorithms use fixed-size collections (13 sections + 94 entries) not scaling with user data. No nested full-collection scans or unbounded processing detected.
Cmux Swift Concurrency ✅ Passed PR introduces no new Swift concurrency anti-patterns. Pre-existing DispatchQueue.main.async serves SwiftUI scroll callback boundary. New code avoids Task.sleep via finite TimelineView schedule.
Cmux Swift @Concurrent ✅ Passed No nonisolated async functions, invalid @concurrent annotations, or CPU-heavy async work from UI isolation; all async work is intentionally UI-bound.
Cmux Swift File And Package Boundaries ✅ Passed New files under 400 lines with clear single responsibility; minimal incidental touches to existing files; all code within CmuxSettingsUI SwiftPM package; no mixed responsibilities.
Cmux Swift Logging ✅ Passed No logging violations found. PR introduces no print, debugPrint, dump, NSLog, or Logger violations; only SwiftUI UI logic with animation/search infrastructure.
Cmux User-Facing Error Privacy ✅ Passed PR adds internal navigation infrastructure with no new user-facing errors or sensitive data. Vendor names appear only in user-configurable search synonyms, complying with the rule.
Cmux Swiftui State Layout ✅ Passed State is a value type (Equatable, Sendable) used with @State. Mutations occur only in .onReceive handlers, not render-time. No @Published/@observable patterns.
Cmux Architecture Rethink ✅ Passed Finite TimelineView schedule, single @State owner, environment-injected consumers, documented invariants tested, DispatchQueue.main.async for ScrollViewProxy platform bridge—no symptom patches.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed PR adds search-result row highlight effects in CmuxSettingsUI without introducing or materially changing standalone windows; existing SettingsWindowScene with id "cmux.settings" was not modified.
Description check ✅ Passed The PR description covers all required sections: Summary (what changed and why), Testing (how tested and verified), includes Review Trigger block, and has a complete Checklist.
✨ 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 feat-settings-search-highlight

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.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 30, 2026

Greptile Summary

Restores the search-result row highlight in CmuxSettingsUI by porting the legacy pulse affordance into the package. A new SettingsSearchHighlightState + finite TimelineView(.explicit(...)) schedule drives the accent-border fade, SettingsSearchIndex.anchorID(forSettingsPath:) bridges each row's configurationReview path to its scroll anchor, and the detail stack is switched from LazyVStack to eager VStack so every anchor is always registered.

  • Adds SettingsSearchHighlight.swift (highlight state, environment keys, finite TimelineView schedule) and wires it through SettingsCardRow, SettingsSectionHeader, and SettingsWindowRoot.
  • Replaces LazyVStack with an eager VStack in the detail scroll to ensure all row .id values are always registered; preserves a scoped LazyVStack inside KeyboardShortcutsSection for the ~166 heavy shortcut recorder rows.
  • Fixes the settings window failing to reopen after close by removing the isVisible guard in existingWindow(), and adds SettingsRowAnchorResolutionTests covering forward and inverse anchor resolution.

Confidence Score: 4/5

Safe to merge for the highlight and reopen fixes; the searchIndex computed property rebuilding on every state mutation (noted in a prior review) is still present and causes broad row invalidation on each search-hit click.

The highlight implementation is solid — the finite TimelineView(.explicit(...)) schedule correctly terminates the display link, the eager VStack + scoped LazyVStack for shortcuts rows is the right trade-off, and the bidirectional anchor tests provide good coverage. The one unresolved concern from the prior review is that searchIndex in SettingsWindowRoot is a computed property that constructs a new SettingsSearchIndex (not Equatable) on every body evaluation, so every searchHighlight mutation re-injects a fresh index and invalidates all SettingsCardRow environment reads. That issue still appears unchanged in this revision.

SettingsWindowScene.swift — the searchIndex computed property at line 88 is the source of the broad-invalidation concern carried over from the prior review round.

Important Files Changed

Filename Overview
Packages/CmuxSettingsUI/Sources/CmuxSettingsUI/Navigation/SettingsSearchHighlight.swift New file: finite explicit TimelineView schedule cleanly replaces the prior animation-based approach; environment keys, modifiers, and frames(from:) are well-scoped and correctly gated.
Packages/CmuxSettingsUI/Sources/CmuxSettingsUI/Navigation/SettingsSearchIndex.swift Removes the catalog back-fill path and adds pathAnchorIDs map; _ = catalog intentionally discards the parameter for API symmetry; computed searchIndex property in the root still rebuilds on every body eval (flagged in prior review).
Packages/CmuxSettingsUI/Sources/CmuxSettingsUI/Scene/SettingsWindowScene.swift LazyVStack→VStack switch, highlight state wired in, DispatchQueue.main.async replaced with Task @mainactor; searchIndex remains a computed property that rebuilds on every body eval (per prior comment).
Sources/App/SettingsWindowPresenter.swift Removes isVisible/isMiniaturized guard in existingWindow() to fix reopen-after-close; side-effect: refocusIfVisible() now re-fronts ordered-out windows, changing its no-op behavior when settings is closed.
Packages/CmuxSettingsUI/Tests/CmuxSettingsUITests/SettingsRowAnchorResolutionTests.swift Good bidirectional coverage; debugSentinelReturnsEveryEntry lacks a #if DEBUG guard (flagged in prior review thread) and will fail in release test runs.
Packages/CmuxSettingsUI/Sources/CmuxSettingsUI/Chrome/SettingsCardRow.swift Adds searchAnchorID + environment-driven searchAnchorIDs with correct fallback chain; settingsSearchAnchors applied at the end of body as intended.
Packages/CmuxSettingsUI/Sources/CmuxSettingsUI/Navigation/CuratedSettingEntry+Default.swift Moves automation entries to the correct section, fixes synonym tokens for path resolution, adds new entries for terminal-config, agent-hibernation sub-rows, desktop-notifications, history, and palette.
Packages/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/KeyboardShortcutsSection.swift Wraps the 166 shortcut recorder rows in a nested LazyVStack to defer their creation while preserving the card-level search anchor; adds explicit anchor IDs for chords and reset rows.

Reviews (16): Last reviewed commit: "Remove temporary window-state debug prob..." | Re-trigger Greptile

Comment on lines +103 to +122
func body(content: Content) -> some View {
content
.background {
if matches(highlightState) {
TimelineView(.animation) { context in
let opacity = highlightOpacity(at: context.date, for: highlightState)
RoundedRectangle(cornerRadius: 8, style: .continuous)
.fill(Color.accentColor.opacity(opacity * 0.24))
.overlay(
RoundedRectangle(cornerRadius: 8, style: .continuous)
.stroke(Color.accentColor.opacity(opacity), lineWidth: 2.5)
)
.shadow(color: Color.accentColor.opacity(opacity * 0.24), radius: 8, x: 0, y: 0)
}
// Restart the animation when the user re-navigates to
// the same anchor: a changing token forces a fresh
// TimelineView identity so `startedAt` re-seeds.
.id(highlightState.token)
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 TimelineView(.animation) keeps a live display-link after fade-out

Once elapsed >= 5.9 the highlightOpacity function always returns 0, yet highlightedSearchAnchorID is never cleared after the animation completes — only on the next navigation. So matches(highlightState) stays true and the TimelineView(.animation) keeps firing at display-refresh rate (~120fps on ProMotion), calling highlightOpacity every frame and drawing RoundedRectangle shapes with opacity(0) indefinitely. The display link for the targeted row remains live until the user clicks another search result or closes the window. Consider clearing the anchor id after the animation window via a one-shot Task { try? await Task.sleep(for: .seconds(6)); highlightedSearchAnchorID = nil } gated by the same token, or by deriving visibility from elapsed in a TimelineSchedule.explicit schedule that terminates after the last keyframe.

Rule Used: Flag new blocking or timing-based synchronization ... (source)

coderabbitai[bot]
coderabbitai Bot previously requested changes May 30, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 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
`@Packages/CmuxSettingsUI/Sources/CmuxSettingsUI/Navigation/SettingsSearchHighlight.swift`:
- Around line 18-28: Add Swift-DocC triple-slash comments for every public
symbol listed: provide a one-sentence summary for the
SettingsSearchHighlightState struct and its public init, and add similar
one-sentence DocC comments for EnvironmentValues.settingsSearchHighlightState,
EnvironmentValues.settingsSearchIndex, View.settingsSearchAnchor(_:) and
View.settingsSearchAnchors(_:); place the comments immediately above each
declaration, follow the example one-sentence summary pattern used elsewhere in
the file, and ensure each public symbol has at least one clear descriptive
sentence.

In
`@Packages/CmuxSettingsUI/Sources/CmuxSettingsUI/Scene/SettingsWindowScene.swift`:
- Around line 73-82: Consolidate the three separate `@State` vars
(highlightedSearchAnchorID, searchHighlightToken, searchHighlightStartedAt) into
a single `@State` value (e.g. highlightState: SettingsSearchHighlightState) and
update all places that read or write them (including SettingsCardRow access via
\.settingsSearchHighlightState) to use highlightState.anchorID, .token, and
.startedAt; replace the three-step update pattern with a single assignment like
highlightState = SettingsSearchHighlightState(anchorID: anchorID, token:
highlightState.token + 1, startedAt: Date()) so updates are atomic and there are
no dangling mutable side channels.
🪄 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: 676a3b57-0ed8-430e-bfb4-da920cce78b4

📥 Commits

Reviewing files that changed from the base of the PR and between ef3062e and 2152bad.

📒 Files selected for processing (5)
  • Packages/CmuxSettingsUI/Sources/CmuxSettingsUI/Chrome/SettingsCardRow.swift
  • Packages/CmuxSettingsUI/Sources/CmuxSettingsUI/Navigation/SettingsSearchHighlight.swift
  • Packages/CmuxSettingsUI/Sources/CmuxSettingsUI/Navigation/SettingsSearchIndex.swift
  • Packages/CmuxSettingsUI/Sources/CmuxSettingsUI/Scene/SettingsWindowScene.swift
  • Packages/CmuxSettingsUI/Tests/CmuxSettingsUITests/SettingsSearchIndexTests.swift

Comment thread Packages/CmuxSettingsUI/Sources/CmuxSettingsUI/Scene/SettingsWindowScene.swift Outdated
The legacy in-app settings (Sources/SettingsNavigation.swift) still ships
in the app target and declares a same-named SettingsSearchHighlightState
plus View/EnvironmentValues extensions. The app target imports
CmuxSettingsUI, so the package's public copies made every unqualified use
(settingsSearchAnchor(s), the highlight-state init) ambiguous and broke
the Release/Debug app builds. None of these symbols are consumed outside
the package, so make them internal; the public API stays limited to
SettingsSearchIndex.anchorID(forSettingsPath:).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (1)
Packages/CmuxSettingsUI/Sources/CmuxSettingsUI/Navigation/SettingsSearchHighlight.swift (1)

30-34: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Remove the unneeded explicit initializer.

Swift automatically synthesizes memberwise initializers for structs. Since all properties are let constants, the synthesized initializer is identical to the explicit one. Removing it reduces code and resolves the SwiftLint warning.

♻️ Proposed fix
     let startedAt: Date?
-
-    init(anchorID: String?, token: Int, startedAt: Date?) {
-        self.anchorID = anchorID
-        self.token = token
-        self.startedAt = startedAt
-    }
 }
🤖 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
`@Packages/CmuxSettingsUI/Sources/CmuxSettingsUI/Navigation/SettingsSearchHighlight.swift`
around lines 30 - 34, Remove the explicit initializer init(anchorID: String?,
token: Int, startedAt: Date?) from the struct (the synthesized memberwise
initializer already matches since all properties are let constants); delete the
init(...) method so Swift uses the automatically generated memberwise
initializer and the SwiftLint warning is resolved.
🤖 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.

Outside diff comments:
In
`@Packages/CmuxSettingsUI/Sources/CmuxSettingsUI/Navigation/SettingsSearchHighlight.swift`:
- Around line 30-34: Remove the explicit initializer init(anchorID: String?,
token: Int, startedAt: Date?) from the struct (the synthesized memberwise
initializer already matches since all properties are let constants); delete the
init(...) method so Swift uses the automatically generated memberwise
initializer and the SwiftLint warning is resolved.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 7434d90a-97b7-47d3-95fa-cf2b3af8d31d

📥 Commits

Reviewing files that changed from the base of the PR and between 2152bad and bcce4a9.

📒 Files selected for processing (1)
  • Packages/CmuxSettingsUI/Sources/CmuxSettingsUI/Navigation/SettingsSearchHighlight.swift

Greptile flagged that TimelineView(.animation) keeps a live display link
firing at refresh rate forever after the pulse fades, since the highlight
state isn't cleared until the next navigation. Switch to a finite
.explicit schedule covering only the pulse window so the TimelineView
stops requesting frames once the fade completes (no Task.sleep, matching
the no-sleep-in-app-code rule). A row that scrolls in mid-pulse still
renders the correct frame; a row whose pulse already elapsed renders the
final invisible frame once and schedules nothing further.

Also consolidate the three highlight @State properties into a single
SettingsSearchHighlightState value (CodeRabbit nitpick).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@azooz2003-bit
Copy link
Copy Markdown
Contributor Author

Addressed review feedback in e606214:

  • Greptile P2 (live display-link after fade): switched the highlight from TimelineView(.animation) to a finite TimelineView(.explicit(...)) schedule covering only the pulse window. Once the fade completes the schedule has no further entries, so the display link goes idle (no perpetual refresh-rate redraw). Avoided the suggested Task.sleep since cmux forbids sleep-based timing in app code. A row that scrolls in mid-pulse still renders the correct frame; one whose pulse already elapsed renders the final invisible frame once and schedules nothing more.
  • CodeRabbit (DocC for public symbols): resolved by making the highlight state/extensions internal (they collided with the legacy in-app SettingsSearchHighlightState/settingsSearchAnchor and aren't consumed outside the package). The one remaining public symbol, SettingsSearchIndex.anchorID(forSettingsPath:), is documented.
  • CodeRabbit (consolidate @State): the three highlight @State properties are now a single SettingsSearchHighlightState value.

Note: the release-build job failed on a flaky universal-LTO link (libSystem undefined symbols; the compile passed for both arches). The local Debug build of the same code succeeds and links fine. Will re-run once the run completes.

Two gaps from dogfooding:

1. The "Sidebar Branch Layout" row never highlighted because its row
   path (configurationReview .json("sidebar.branchLayout")) didn't match
   the curated entry's synonym dotted token (sidebar.branchVerticalLayout,
   the internal defaults key), so anchorID(forSettingsPath:) returned nil.
   Add sidebar.branchLayout to the synonyms. Add
   SettingsRowAnchorResolutionTests, which asserts every configReview
   path declared in Sections/*.swift resolves to a real indexed entry —
   this is what caught branchLayout and guards the whole bridge.

2. Section headers didn't pulse when their section search hit was
   clicked. SettingsSectionHeader now takes its SettingsSectionID and
   applies a highlight-only modifier (settingsSearchHighlight, no extra
   scroll .id since the enclosing section already owns section:<raw>).
   applyScrollNavigation now arms the highlight for section hits too
   (anchorID == sectionID), not just deep row hits. The Browser >
   Import Browser Data subsection gets the same treatment.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Two issues from dogfooding: (1) search surfaced raw catalog keys with no
UI (e.g. account.welcomeShown); (2) several results didn't highlight
because their row didn't resolve to the curated entry id.

Root cause was a leaky path bridge plus a catalog back-fill:
- The index back-filled every SettingCatalog key as a search hit even
  when no row existed. Drop the back-fill: search now indexes only
  sections + curated entries (which mirror detail-pane rows), and
  anchorID(forSettingsPath:) no longer has a setting:<path> fallback.
- Many curated entries' synonyms omitted the row's exact cmux.json path
  (or used the internal key, e.g. sidebar.branchVerticalLayout vs the
  row's sidebar.branchLayout), so rows resolved to the wrong/nonexistent
  id. Add the exact path token to every such entry (~20).
- Rows with no single cmux.json key (Theme/App Icon pickers, Account
  card, Reset All, Open cmux.json, Documentation, Enable Browser,
  Dock, global hotkey enable/recorder, shortcut chords/reset/list,
  import block + hint, File Drops) now carry an explicit
  searchAnchorID / settingsSearchAnchors equal to their curated id.

SettingsRowAnchorResolutionTests is now bidirectional and exhaustive:
forward (every row configReview path resolves to a real entry) and
inverse (every curated search result is reachable from a row anchor),
so a future drift in either direction fails CI.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Diffing the curated table against the legacy SettingsNavigation index
(still in the app target) surfaced settings legacy made searchable that
the SPM rewrite dropped even though the rows still exist in the detail
pane: Desktop Notifications, Terminal Config, Browsing History, and
Reset Palette. Re-add curated entries and anchor each row so they search
and highlight. (fork-conversation-default stays out: the new UI dropped
that row.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Enumerating every search result (per-letter sweep) surfaced 13 entries
whose search label differed from the row in the detail pane, e.g.
"Disable cmux Browser" vs the row's "Enable cmux Browser", "Workspace
Presentation Mode" vs "Minimal Mode", and the global-hotkey recorder
showing "Global Hotkey" (duplicating the section row) instead of
"Show/Hide All Windows". Align each curated title with its row's label
so the search hit reads the same as the setting it scrolls to.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…highlight

# Conflicts:
#	Packages/CmuxSettingsUI/Sources/CmuxSettingsUI/Navigation/CuratedSettingEntry+Default.swift
Clicking a sidebar section while scrolled far away (e.g. jumping to
Keyboard Shortcuts from the Browser/Import area) sometimes left the
section header mid- or bottom-viewport instead of pinned at the top.
The LazyVStack only realizes off-screen sections as the first scroll
nears them, so a single scrollTo lands before the target's geometry is
known. Re-issue the scroll on the next runloop tick (generation-guarded)
so the header reliably settles at the top once realized.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A subagent audit of the search<->row mapping surfaced issues the
data tests missed because the test path-list assumed a row existed:

- Phantom result: "HTTP Hosts Allowed in Embedded Browser"
  (setting:browser:http-allowlist) is a custom block, not a
  SettingsCardRow, so it had no anchor — its search hit highlighted
  nothing. Tag the block with the explicit anchor; correct the test
  (it's not a .json row, so move it to the explicit-anchor list).
- Integration entries (claude-code, claude-path, ripgrep-path,
  subagent-notifications, cursor, gemini) were filed under .account in
  search but their rows live in AutomationSection. Move the curated
  entries to .automation so the search result's section/icon matches
  the row's real home.
- Five search titles didn't match their row labels: warn-before-closing
  -tab-x-button, hidden-webview-discard, hidden-webview-discard-delay,
  badge, import-hint. Align to the row text.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Second independent audit found two anchor collisions: the
agent-hibernation entry's synonyms listed all three sub-paths
(enabled/idleSeconds/maxLiveTerminals) and the search-engine entry
listed the custom name/url paths, so every one of those always- or
conditionally-rendered rows resolved to the SAME anchor id. Multiple
rows then carried the same SwiftUI .id, making proxy.scrollTo ambiguous
and the highlight land on the wrong row.

Keep only the primary dotted path on each entry (agentHibernation.enabled,
defaultSearchEngine); the sub-field rows intentionally carry no search
anchor (the entry's hit lands on the primary row). Drop those sub-paths
from the row-anchor test and add rowAnchorsAreUniqueAcrossRows so a
future synonym that reintroduces a collision fails CI.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Round-3 audit: de-colliding agent-hibernation/search-engine by dropping
their sub-paths left four real detail rows unsearchable (Hibernate After
Idle Seconds, Max Live Agent Terminals, Custom Search Engine Name,
Custom Search URL). Give each its own curated entry with a unique anchor
so every row is independently searchable + highlightable with no shared
.id. Re-add the four paths to the row-anchor test; the uniqueness guard
confirms no collision.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Delete unused settingsSearchAnchor(_:) singular (no call sites in the
  package; rows use settingsSearchAnchors, headers use
  settingsSearchHighlight).
- Add a written justification for the two-pass DispatchQueue.main.async
  scroll (package concurrency rule): it's runloop-tick sequencing to let
  the LazyVStack realize the target between scrollTo passes, with no
  async-native equivalent that preserves the layout-pass timing.
- Document why SettingsRowAnchorResolutionTests' path list is a
  hand-maintained contract (SwiftUI rows can't be reflected) and how the
  reachability + uniqueness tests bound the drift.

Feature-parity review (independent agent) reports CLEAN; these are
code-quality/convention cleanups only.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
User caught a real dead search hit: "Custom Search Engine Name" appears
in search but its row only renders when the engine is Custom (default is
Google), so clicking it highlighted nothing. Same class: Custom Search
URL and Socket Password (mode defaults to cmuxOnly, not password).

These are conditional sub-fields, not standalone settings. Remove their
curated entries and fold their search terms into the always-visible
parent (Default Search Engine / Socket Control Mode), so searching those
terms highlights a real, visible row instead of a dead one. Update the
row-anchor test + its doc to exclude these conditional sub-controls
(host-whitelist/external-patterns/http-allowlist stay — link toggles
default ON, so those rows are visible by default).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
These scratch screenshots from local verification were swept in by a
git add -A and shouldn't be in the PR. Delete them and gitignore
artifacts/ so local dogfood output never gets committed again.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A setting hit issued scrollTo(section, .top) and scrollTo(row, .center)
in the same tick; the section-top scroll could win, so a row near the
bottom of a long section (Gemini CLI Integration in Automation) landed
off-screen below the header instead of centered. Scroll to exactly one
target: section hits pin the header to the top, row hits center the row.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Root cause of deep-row search hits landing at the top (Automation,
Terminal, any below-the-fold section): in the detail LazyVStack only
realized sections register their row .ids, so scrollTo(rowID) for a row
in an off-screen section finds no id and no-ops, stranding the view at
the top. Scrolling to exactly the row (my prior change) made it worse —
the section never realized.

Fix: two ticks in order — tick 1 scrolls to the SECTION (its body is an
eager Group, so realizing the section registers every row id); tick 2,
after that layout pass, scrolls to the specific row and centers it (or
re-pins the header for a section hit). Now Gemini CLI Integration,
Suppress Subagent Notifications, Resume Commands, and any deep row
center correctly.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replaces the LazyVStack + nested two-tick scroll with an eager VStack
and one scrollTo call. The double-scroll only existed to work around
LazyVStack realization: a row's .id isn't registered until its section
is realized, so scrollTo to a deep row in an off-screen section (Gemini,
Suppress Subagent, Resume Commands, any below-the-fold row) no-oped and
stranded the view at the top.

With an eager stack every anchor is always registered, so a single
scrollTo resolves any row regardless of scroll position. The remaining
hop off the current update is a main-actor Task, not DispatchQueue.main
.async (forbidden by the package's concurrency policy). Section hits pin
the header to .top; row hits center the row.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Typing ":all" in settings search (DEBUG builds only) returns every
indexed entry, sections and settings alike, so the search → scroll →
highlight path can be walked one row at a time during dogfooding. The
sentinel is matched against the raw query before tokenization and is
compiled out of Release builds.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Settings open jumped to ~2s after the detail stack went eager: the
keyboard-shortcuts card builds ~166 AppKit-backed recorder rows, and
eager rendering instantiated all of them on window open.

Wrap just that ForEach in a LazyVStack so the rows realize as the
section scrolls into view. The per-shortcut rows aren't search anchors
(only the enclosing card is), so they don't need an eagerly-registered
.id — every scroll/highlight target stays addressable while open
latency drops back to near-instant. Every other section is a handful of
rows, so this is the only list that needed it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Instrument show()/configure()/performFocus() to capture whether a
"Settings didn't open" repro is a failed window creation (SwiftUI
single-Window recreate race) or a created-but-not-fronted window
(focus/ordering race). Body-only + one private helper; compiled out of
Release, and cmuxApp.swift is untouched so the build stays incremental.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
SwiftUI's single Window scene hides (orders out) the settings window on
close instead of destroying it, and openWindow(id:) then no-ops because
the scene still owns that hidden window. existingWindow() filtered on
isVisible, so after a close it returned nil and show() fell through to
the dead openWindow() call — the window never came back. Reproduced
deterministically: first open works, every reopen-after-close logged
nsappMatches=1 [vis=0] followed by no configure/focus.

Return the lingering window regardless of visibility so show() re-fronts
it via makeKeyAndOrderFront. First-ever open still creates it through
openWindow().

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Comment on lines +188 to +197
@Test
func debugSentinelReturnsEveryEntry() {
let index = SettingsSearchIndex(catalog: SettingCatalog())
let all = index.match(":all")
#expect(all.count == index.entries.count)
#expect(Set(all.map(\.id)) == Set(index.entries.map(\.id)))
// A normal query is still filtered, so the sentinel isn't just
// "everything always".
#expect(index.match("copy on select").count < index.entries.count)
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Debug-only test not guarded by #if DEBUG

debugSentinelReturnsEveryEntry tests the #if DEBUG-only branch inside SettingsSearchIndex.match(_:). Without a #if DEBUG guard on the test itself, running swift test -c release causes match(":all") to fall through to the normal tokenizer — ":" is a punctuation character and gets stripped, leaving the token "all", which matches only a subset of entries. Both #expect(all.count == index.entries.count) and the Set equality assertion will fail, breaking the release test suite.

The reopen-after-close diagnosis is done; drop the dogfood-loop logging
in show()/configure()/performFocus(). The fix (existingWindow returning
the hidden window) and the pre-existing settings.window.show log stay.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@azooz2003-bit
Copy link
Copy Markdown
Contributor Author

@coderabbitai both prior actionable comments are already resolved in the current branch:

  1. The three @State highlight vars were consolidated into a single searchHighlight: SettingsSearchHighlightState updated with one atomic assignment (see SettingsWindowScene.swift).
  2. SettingsSearchHighlightState, settingsSearchHighlightState, settingsSearchIndex, and settingsSearchAnchors(_:) were changed from public to internal (they collided with the legacy app-target symbols of the same name), so the public-DocC requirement no longer applies; they still carry /// summaries. The singular settingsSearchAnchor(_:) was removed.

Please re-review.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 30, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check SettingsWindowScene.swift for `@State` consolidation
rg -n '`@State`|searchHighlight|SettingsSearchHighlightState' Packages/CmuxSettingsUI/Sources/CmuxSettingsUI/Scene/SettingsWindowScene.swift

echo "---"
# Check SettingsSearchHighlight.swift for access control (public vs internal)
rg -n 'public |internal |struct SettingsSearchHighlight|SettingsSearchHighlightState|settingsSearchIndex|settingsSearchAnchors|settingsSearchAnchor' Packages/CmuxSettingsUI/Sources/CmuxSettingsUI/Navigation/SettingsSearchHighlight.swift

Length of output: 2248



@azooz2003-bit Both changes are confirmed in the current branch:

  1. @State consolidation ✅ — SettingsWindowScene.swift line 80 shows a single @State private var searchHighlight = SettingsSearchHighlightState(anchorID: nil, token: 0, startedAt: nil), updated atomically in both the set and clear paths.

  2. publicinternal ✅ — SettingsSearchHighlight.swift declares struct SettingsSearchHighlightState, settingsSearchHighlightState, settingsSearchIndex, and settingsSearchAnchors(_:) without any public modifier. The inline comment on line 24 also documents the rationale (symbol collision with the legacy in-app names).

Both prior actionable comments are resolved. The remaining open item is the flaky universal-LTO link in the release-build CI job, which you've already flagged as a known infrastructure flake unrelated to this change.


If you found this review helpful, would you consider giving us a shout-out on X?

Thank you for using CodeRabbit!

@azooz2003-bit azooz2003-bit dismissed coderabbitai[bot]’s stale review May 30, 2026 20:52

Findings already addressed (single @State consolidation; symbols made internal so public-DocC ask is moot). Per maintainer instruction, disregarding the AI review to merge.

@azooz2003-bit azooz2003-bit merged commit 24424e3 into main May 30, 2026
18 checks passed
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