Skip to content

feat(macapp): window chrome, sidebar type, and the duplicate action glyph - #1072

Merged
dennisonbertram merged 10 commits into
mainfrom
feat/codex-chrome-r8b
Aug 11, 2026
Merged

feat(macapp): window chrome, sidebar type, and the duplicate action glyph#1072
dennisonbertram merged 10 commits into
mainfrom
feat/codex-chrome-r8b

Conversation

@dennisonbertram

Copy link
Copy Markdown
Owner

Round 8 of the Codex look-and-feel gauntlet. Closes #1071.

The full-width titlebar band is gone

A visible toolbarBackground paints one colour across the whole window, so the sidebar’s grey ran over the content pane and the window wore a #222222 band from y=0 to y=52 — the content pane did not exist at that height. Hidden now, with each column painting its own colour through the top safe area.

Verified by pixel probe on the running app, not by reading the diff — reading the diff is how round 7 called this closed while it was not:

Probe Before After Codex
(100,5)pt — sidebar side #222222 #222222 #222222
(700,5)pt — content side #222222 #181818 #181818

The nav pill no longer steals the selected state

selectedRowSurface was painted on both the active nav row and the active conversation — two competing "this one is selected" affordances 31.5pt apart. The reference reserves that fill for the conversation alone; nav selection now reads through ink weight.

Sidebar type sits at body size

The reference sets its sidebar very slightly larger than transcript body. Ours was 24% smaller, which read as a footnote rather than as the list the sidebar exists to be.

The section label goes the other way — larger but two rungs dimmer. foregroundQuaternary is already #747474, which is the reference’s label ink exactly. It had been smaller and brighter: the opposite of a label that recedes.

A real bug: two actions, one glyph

ChatView.swift:214 and :247 both used doc.on.doc, so copy-message and copy-conversation rendered identically and read as a duplicated button. A critic found this two rounds ago and the fix never landed. Now pinned by a test that fails if the two ever match again.

Not in this round, deliberately

The sixteen properties already measured as matching — composer max width (883.0 vs 882.5pt), body cap height, all four surface colours, bubble geometry, and the rest. Two earlier rounds were partly spent re-fixing things that were already correct, which is why the issue carries a do-not-touch list.

183 Swift tests pass; build and swift format lint --strict clean.

🤖 Generated with Claude Code

https://claude.ai/code/session_01UPFeSNp49415WenKDF7gy9

…lyph

Round 8 of the Codex look-and-feel gauntlet. Implements issue #1071.

**The full-width titlebar band is gone.** A visible toolbarBackground paints
one colour across the whole window, so the sidebar's grey ran over the content
pane and the window wore a #222222 band from y=0 to y=52 — the content pane did
not exist at that height. Hidden now, with each column painting its own colour
through the top safe area, which is what the reference does.

Verified by pixel probe on the running app rather than by reading the diff,
because reading the diff is how round 7 called this closed while it was not.
At y=5: x=100 → #222222, x=700 → #181818. Both previously returned #222222.

**The nav pill no longer steals the selected state.** selectedRowSurface was
painted on both the active nav row and the active conversation, putting two
competing "this one is selected" affordances in the sidebar 31.5pt apart. The
reference reserves that fill for the conversation alone; nav selection now
reads through ink weight instead.

**Sidebar type sits at body size**, as the reference's does — slightly larger
than transcript body rather than 24% smaller. The section label goes the other
way: larger but two rungs dimmer, since foregroundQuaternary is #747474, which
is the reference's label ink exactly. It had been smaller *and* brighter, the
opposite of a label that recedes.

**Copy-message and copy-conversation had the same glyph.** Both were
"doc.on.doc", so the action row showed one icon twice and read as a duplicated
button. A critic found this two rounds ago and the fix never landed, so it is
now pinned by a test that fails if the two ever match again.

Not in this round, and deliberately: the sixteen properties already measured as
matching. Two earlier rounds were partly spent re-fixing things that were
already correct.

Closes #1071.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UPFeSNp49415WenKDF7gy9
@cursor

cursor Bot commented Jul 31, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 877ce86913

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

// the way the rail does. Previously only the toolbar background
// covered this strip, in the *sidebar's* colour, so the pane began at
// y=52 and the window wore a full-width band above it.
.background(Theme.background.ignoresSafeArea(.container, edges: .top))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Extend the pane background for every section

This background extension exists only in ChatView, while AppShell now hides the toolbar background for the entire ProjectView. When the user switches to Activity, Sessions, Checkpoints, or Settings—or while startup/failure content is shown—those views do not ignore the top safe area, so the right-hand titlebar strip falls back to the window background instead of the pane surface. Apply the top-safe-area background around the shared content container rather than only Chat; the new module-wide token test currently passes without exercising any of these affected sections.

AGENTS.md reference: AGENTS.md:L31-L31

Useful? React with 👍 / 👎.

// "doc.on.doc", so the two actions rendered identically and the
// row read as a duplicated button. This one copies the whole
// transcript, which is a page rather than a snippet.
Image(systemName: copied ? "checkmark" : "text.document")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Use a symbol available on macOS 14

text.document was introduced after macOS 14, but macapp/Package.swift:7 still declares macOS 14 as supported. On that supported OS, Image(systemName:) cannot resolve this name, leaving the whole-conversation copy toolbar action without its intended visible glyph. Use an older equivalent such as doc.text, or gate the new symbol with an availability fallback.

Useful? React with 👍 / 👎.

Round 9 of the Codex gauntlet. Implements issue #1073.

**The ink ramp had four rungs where the reference has six**, and seven chrome
elements had collapsed into a single 220–224 band as a result: with nothing
between secondary and quaternary, everything rounded up to secondary. Added
#8B8B8B (subtle) and #616161 (placeholder), and moved tertiary from 163 to
150 — a critic measuring both apps found 163 (#A3A3A3) is a value the
reference never uses anywhere, and we were the only one with it.

Three inversions fixed and each verified by probe on the running app:

- folder icon 163 → **255** (the reference draws it white; it belongs to the
  title beside it)
- overflow menu 221 → **139** (it had been brighter than the title's own icon)
- nav label 255 → **222** (the reference reserves white for the active
  conversation, not for navigation)

The overflow needed `.tint` rather than `.foregroundStyle`: `.borderlessButton`
re-tints whatever label it is given, so styling the Image had no effect and
styling the Menu with foregroundStyle had none either. Both looked correct in
the source and neither changed a pixel, which is why each rung was measured
rather than assumed.

**Plan mode is a chip, not a checkbox.** The reference's composer uses
icon+label chips and has no checkbox anywhere; a square AppKit control in a
chat composer was the most out-of-family element on the screen. ComposerChip is
shared rather than styled inline, so the next composer toggle inherits it.

**Composer type and padding**: the field set no font at all, so its placeholder
rendered a step under the reference's. It takes the body rung now, the control
labels match it, and the vertical padding grows to suit — the padding had been
compressed to fit type that was too small.

Left alone deliberately: the 1px bright rim at the very top of the window.
It is a single raw pixel, correct by y=1pt, and I could not attribute it to
anything this app draws — reporting it as unexplained rather than guessing at
a cause.

Closes #1073.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UPFeSNp49415WenKDF7gy9
…chips

Round 10. Implements issue #1074, and corrects two claims round 9 got wrong.

**The composer border round 9 reported did not exist.** Its stroke was 43
against a 45 fill — darker than what it bordered, and therefore
indistinguishable from the antialiased edge of the rounded rect underneath. I
measured a colour transition and called it a border without checking which
direction it went. Theme.rule is 60, lighter than the fill, and the probe now
reads 24 → 60 → 45 down the composer edge, matching the reference's 32 → 60 →
45. The test pins the *direction*, not just the value.

**"The reference never uses #A3A3A3" was false.** It uses 1,198px of it,
mostly in the transcript. Near-absent from reference chrome, so removing it
there was defensible; asserting it as a fact was not.

**Round 8's nav change was an over-correction.** Removing the fill to stop it
competing with the active conversation left nothing indicating the current
section — the row scanned uniformly #222222. Restored, and the principle round
9 stated alongside it was also wrong: the reference does not reserve white for
conversations, spending it on the header title, account name and active
controls too.

The reference is a compartmented app and this had no rules at all. Three added,
each measured lighter than the surface it borders: main-pane header (#3C3C3C at
y=103), sidebar footer (#393939), composer (#3C3C3C).

Sidebar 220 → 310pt. It had been 63% of the reference's width, failing on both
the absolute and the proportional measure.

The two composer chips had a 48% icon-width spread and an 85% label-gap spread,
because Label sizes its symbol from font metrics and the two labels differ.
Both now build their icon and gap from tokens, so adjacent chips cannot drift.

Verified on the running app: composer rule 60-on-45, header rule at y=103,
selected row filled, sidebar divider at x=310.

Closes #1074.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UPFeSNp49415WenKDF7gy9
dennisonbertram and others added 7 commits July 31, 2026 21:06
…r header

Round 11. Implements part of issue #1075; two items are honestly not closed.

**Fixed two regressions round 10 introduced.**

Round 10 restored the nav fill and produced a sidebar with *two* identical
#333333 bands — the nav row and the conversation row, same fill, height and
inset, nothing distinguishing current from adjacent. The reference has exactly
one in its whole sidebar. The nav row is *current*, not *selected*; it carries
that in ink weight now and leaves the fill to the conversation list. Probe on
the running app: zero fills at the nav row's column, where there had been one
competing with the conversation's.

Round 10 also set the header title to primary and the overflow menu followed it
back to #FFFFFF, re-inverting a hierarchy round 9 had fixed. The tint is
reapplied on the Menu, where .borderlessButton actually reads it.

**Every rule in the app was twice the reference's weight** — composer border,
header rule, footer rule and column divider all measured 2 raw px against the
reference's 1, four for four. Spacing.hairline is 0.5 now, which is the single
pixel a 2x display draws. The column divider was also the one rule whose colour
was wrong, because it was a system Divider rather than a token: 47 where the
reference draws 67. Both verified on the running app — divider now 67 at 0.5pt.

**Corner radii were ~40% short on the two smallest surfaces.** An edge-inset
profile put the reference's user bubble at r≈18 against ours at 10, and its
selected row at r≈10 against ours at 6, on identical 36pt rows.

**The header is shorter but not right.** 103pt → 91.5pt against the
reference's 55pt. Two causes were found and one fixed: the header row was bound
to ConversationColumn, so the title truncated at the column's edge while 402pt
of its own row — 38% of it — sat empty to the right. The header spans the pane
now and only the transcript is column-bound.

The remaining 36pt is the titlebar band above it. Moving the title into the
toolbar was tried and reverted: `.navigation` placement puts it in the
*sidebar* pane and pushes the app name into the content pane, which is worse
than the problem. Closing this properly needs the window to adopt a hidden
titlebar so content starts at y=0, which is a larger change than this round.

Not attempted this round, and still open from #1075: transcript vertical
rhythm, the tool-activity label's second type size, and the absent accent
channel in the sidebar.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UPFeSNp49415WenKDF7gy9
…lbar

Tried .windowStyle(.hiddenTitleBar) to let the conversation header sit on the
traffic-light row, which is where the reference puts it.

It removed the app wordmark, which was the intended half. It also moved the
toolbar items into the *sidebar's* top-left corner beside the traffic lights,
where the reference has them at the top right of the content pane — and the
header rule stayed at 91.5pt regardless, so the actual gap did not close.

Reverted rather than left in: one intended improvement, one clear regression,
and none of the measurement that motivated it. The header remains 91.5pt
against the reference's 55pt, recorded as open.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UPFeSNp49415WenKDF7gy9
Round 12. The previous critic's summary was that the chrome is now materially
correct and what separates the two apps is a greyscale ladder pointed at the
wrong roles, type rungs running short, and rhythm. This addresses those.

**Ink roles.** Six of thirteen sampled roles sat on the wrong rung. Fixed the
three that are ours to fix here: sidebar nav label 255 → 222 (the reference
reserves white for content, not navigation); transcript action icons 116 → 139
(quaternary is the section-label rung and sat a step below); tool-activity
label 150 → 163, separated from body by colour rather than by scale.

**Type rungs.** The caption rung was the cause of two short roles rather than
their call sites: measured ascender-to-descender, the composer placeholder ran
12.0pt against the reference's 15.0 and the sidebar section header 10.0 against
14.5. Raised 14 → 16, which moves both.

The tool-activity label moves to body size. The reference sets it 0.5pt below
its own body ascender and separates it by colour; ours sat 2.5pt below,
de-scaled *and* dimmed where the reference only dims.

**Transcript rhythm was wrong in both directions.** The reference opens tight —
38.5pt from the header rule to the first bubble — then leaves 62pt before the
tool row. Ours opened at 65.5 and then crammed the tool row 19.5pt below the
bubble, 3.2x tight. Both now come from tokens rather than a shared `large`.

**Composer padding was doubled.** The send button sat 18.5pt from the right
edge and 20pt from the bottom against the reference's 9 and 9.5 — twice as far
in, inside a composer 14pt shorter overall.

**One correction to round 11.** The 0.5pt rule convention was over-applied. The
reference draws four rules at 0.5pt and the tool-activity separator at 1.0pt;
round 11 made all five half-weight. That fifth rule has its own token now.

Not addressed, and the largest remaining: the app has zero chromatic pixels
anywhere, where the reference spends 952 saturated samples on identity, status
and diff counts. Also open: no right rail, no sidebar header row, and a sidebar
carrying one nav item against the reference's five.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UPFeSNp49415WenKDF7gy9
…leanup

Round 13. Half of the change the last critic named as most important.

**The app's identity moved into the sidebar**, which is where the reference
puts it: a bright header row with a disclosure chevron and a search glyph.
Ours had been a recessed system window title measuring 89 against the
reference's 222 — 60% dimmer, in the wrong pane, and costing a whole chrome
band purely to repeat the app's name. The window title is empty now. Probed on
the running app: identity header 222, nav label 222, both exact.

**The action row was not a set.** Four icons measured 19.5 / 14.5 / 12.0 /
12.5pt — a 62% spread — because different SF Symbols draw to different heights
at one point size. Their ink was 139 / 116 / 116 / 116 because the two copy
buttons overrode the row's colour and the other two inherited the
section-label rung. One size and one ink are set on the row now, and the copy
buttons override only for their copied state.

**Chat rows carried two marks the reference's do not**: a bullet peaking at
255 — the brightest ink anywhere in the sidebar — and a leading speech-bubble
glyph, together creating a third indent level. Both removed; the reference's
chat rows are text-only and align with its project rows.

Nav icon 18 → 15pt: measured ink width had been 21.0 against the reference's
15.0. Footer's fifth icon matched to its four neighbours, having rendered 60%
brighter, which read as a state rather than as the same kind of control.

**Not closed, and it is the other half of that one change.** The conversation
title still sits on its own row, so the header rule stays at 91.5pt against the
reference's 55. Removing the wordmark band did not collapse it — the title's
row is separate from the window-control row, and the two structural approaches
tried so far both made things worse: `.navigation` toolbar placement puts the
title in the *sidebar* pane, and `.windowStyle(.hiddenTitleBar)` moves the
toolbar items into the sidebar's corner. Recorded as open rather than attempted
a third time blind.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UPFeSNp49415WenKDF7gy9
The change three previous attempts failed at, and the reason they failed.

The header rule sat at 91.5pt against the reference's 55: a 52pt system
titlebar stacked above a 40pt header band. Hiding the titlebar was tried twice
and reverted twice, because a window toolbar *requires* that band — hide it and
the toolbar's items are re-homed into the sidebar's corner, which is worse than
the problem.

The missing step was that the toolbar did not need to exist. Its two controls
belong to the conversation, so the header now owns them, there is no toolbar
left to displace, and the titlebar can go. One band, measured 67.5pt on the
running app.

The app's identity moved into the sidebar in the previous commit, which is what
made this possible: with the wordmark gone from the titlebar, nothing was left
up there worth a band.

**Still wrong, and measured rather than glossed:** the header's controls sit
beside the title instead of at the far right. The Spacer between them is not
expanding, so the row sizes to its content — the Group wrapper appears to be
constraining it. The height gain is real and independent of this; the alignment
is not fixed.

Also open: 67.5 is not 55. The remaining 12.5pt is the header band's own
padding, which is worth measuring against the reference's internal spacing
before changing, rather than trimmed to hit a number.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UPFeSNp49415WenKDF7gy9
A `.borderlessButton` menu expands to fill whatever width it is offered, so the
overflow menu was swallowing the Spacer and leaving the header's controls
clustered beside the title. `.fixedSize()` on the menu — the same fix the other
copy of this code already carried — lets the Spacer do its job.

The Group wrapper went with it; it was doing nothing once the header stopped
being bound to the transcript's column.

Verified on the running app: title at the left, controls at the right edge,
one header band.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UPFeSNp49415WenKDF7gy9
@dennisonbertram
dennisonbertram merged commit 6f5a5be into main Aug 11, 2026
4 of 5 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.

macapp round 8: window chrome, sidebar type, and the duplicate message-action glyph

1 participant