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.isDark was background.red < background.blue, which is false for any neutral background — #1e1e1e is 30 < 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).
Theme declares colorScheme per palette, plus Chrome and Semantic groups. Chrome never borrows a syntax token.
Metrics — 4pt grid, radii by surface, chrome heights — in CodeEditorCore, 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, SessionStore extracted 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
Acceptance criteria
Not done, deliberately
LSP transport, PTY terminal, external-change detection, Finder file associations (needs CFBundleDocumentTypes), new dependencies, a new app icon.
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.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 truncatedNo Folder OpenedtoNo Fold...Opened, andTheme.keyworddoing 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.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
.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.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.system(size:)in chrome; no padding off the 4pt gridswift buildpassesswift testpasses — 68 tests, up from 35Not done, deliberately
LSP transport, PTY terminal, external-change detection, Finder file associations (needs
CFBundleDocumentTypes), new dependencies, a new app icon.