Repository navigation
Redesign the interface around native macOS chrome and a real design system #14
Description
Summary
Replace the hand-built interface with native macOS chrome and move every visual decision out of the views into a design system. Landed in #13.
Motivation
The window had the shape of an application without the finish of one. Two defects drove most of it:
ThemeManager.isDarkwasbackground.red < background.blue, which is false for any neutral background —#1e1e1eis30 < 30. Dark and High Contrast therefore forced.preferredColorScheme(.light), so every system-drawn control rendered light against a dark canvas. It read as a styling problem and was a comparison operator.- The tab strip swapped the close button for the dirty dot, so modified tabs could not be closed without saving first.
Beyond those, the chrome was 13 hardcoded .system(size:) calls with no semantic styles, 7 off-grid padding values, zero corner radii, fixed 28pt/20pt bars, two competing empty states in different capitalisations, a hand-rolled sidebar header that truncated No Folder Opened to No Fold...Opened, and Theme.keyword doing five unrelated jobs — app tint, tab underline, folder icon, terminal prompt and terminal errors.
Proposed approach
Modules touched: CodeEditorThemes (tokens), CodeEditorCore (metrics, extracted logic), CodeEditorUI (views), CodeEditorApp (toolbar, commands).
ThemedeclarescolorSchemeper palette, plusChromeandSemanticgroups. Chrome never borrows a syntax token.Metrics— 4pt grid, radii by surface, chrome heights — inCodeEditorCore, Foundation-only, grid enforced by a test.Typography— semantic styles so chrome scales with system text size; code stays fixed-size SF Mono.- Materials: stop overriding the sidebar's own vibrancy.
- Unified toolbar replacing the sidebar header; tabs that merge into the canvas; one adaptive empty state.
- Selection-driven tree activation, the Xcode navigator rule.
- ⌘F via CodeEdit's own panel, ⌘P quick open, drag-resized terminal, session restore.
FileOutline,QuickOpenMatcher,QuickOpenIndex,SessionStoreextracted to Core and tested.
Ordering matters: finish before features. The find panel is upstream AppKit, so dropping it into an unfinished shell makes the gap more obvious, not less.
Trap for whoever picks this up
- A row
.onTapGestureinside aList(selection:)never fires — the List consumes the click. Drive row behaviour from the selection change. See MEMORY.md docs: project documentation set plus AGENTS.md #12. NavigationSplitViewcolumns are sized in exactly one place. See MEMORY.md feat(ui): redesign the interface around native macOS chrome #13.- Materials follow
preferredColorScheme, which the palette drives. Vibrancy and "theme drives everything" are compatible because of that, but High Contrast must setusesMaterials = false.
Acceptance criteria
-
isDarkno longer derived from colour arithmetic; regression test covers all three palettes - Dirty tabs are closeable without saving
- No
.system(size:)in chrome; no padding off the 4pt grid - One empty state, one primary action per surface
- Sidebar reveals and emphasises the active document
- ⌘F, ⌘P, resizable terminal, session restore
-
swift buildpasses -
swift testpasses — 68 tests, up from 35 - Visual review by a human. Spacing, materials, contrast and the tab/canvas merge were never confirmed by eye; the implementing session could not read screenshots.
Not done, deliberately
LSP transport, PTY terminal, external-change detection, Finder file associations (needs CFBundleDocumentTypes), new dependencies, a new app icon.
Activity
docs: record the redesign issue and a recommendation for what comes next
Yan123-tech commentedon Oct 10, 2026
The visual review is done. It found three defects that 68 passing tests did not, all now fixed and confirmed:
SourceEditordoes not diff text on update; the text is pushed once inmakeNSViewControllerand SwiftUI reuses the controller across tab switches.Two of those were defects the redesign introduced; #15 predates it and simply was never visible without two tabs worth switching between.
Acceptance criteria are now all met. Leaving this open only because PR #13 is still unmerged — the work is on
feature/ui-redesign, not inmainyet. Close it with the merge.Also fixed while in there: the tab label rendered both a dirty dot and the
*thatDocument.titleappends. One state, one indicator.