Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .ai/memory.md
Original file line number Diff line number Diff line change
Expand Up @@ -562,6 +562,20 @@
- **Trailing-newline-ness is a property of how a row ended, not of `isComplete`.** Conflating them regressed `BrewCommandServicePseudoTerminalTests`: a child that ends with `printf 'tty'` (no newline) must not gain one in `standardOutput`. `TerminalTranscript` tracks `endsWithNewline` separately so the returned transcript holds settled rows without inventing terminators.
- **Rejected: adopting SwiftTerm.** Its `Terminal` models a fixed grid + scrollback; the console models a variable-length transcript with stable row identities for SwiftUI diffing. Using the engine headless means diffing a grid to infer which rows changed — plausibly more code than this, and it fights `CONVENTIONS.md`'s "add packages sparingly". Revisit only if the app ever needs to run arbitrary interactive commands (a shell, a pager, anything using the alt screen), where hand-rolling stops being viable.

## 2026-08-30 — Colour tokens are held to WCAG AA by a calculated audit

- **The audit is a unit test over the asset catalogue, not a rendering check.** `Tests/BrewUIComponentsTests/Support/` parses `Media.xcassets` from source and computes WCAG 2.1 relative luminance / contrast ratios. It reads the JSON rather than resolving `NSColor(named:bundle:)` because **SwiftPM copies `.xcassets` into the test bundle uncompiled** — there is no `Assets.car`, so a runtime lookup silently returns `nil` under `swift test`. Do not "fix" the test by switching it to `NSColor`; it will pass vacuously.
- **The (foreground, background) pairs are hand-maintained, deliberately.** A full cross-product would demand success green stay legible on the error tint, which no screen renders, and buying that costs real chroma across the palette. When a view puts a token on a new surface, add the pair to `BrewColorTokenContrastTests.requirements`.
- **Translucent background tokens are measured over both `Surface` and `SurfaceElevated`.** The dark-mode `BrandTint` is 12% alpha, so what a selected row's text actually sits on depends on the panel behind it; the worse of the two has to pass. This is what forced the last few tenths on tertiary text and dark-mode error red.
- **Brand amber is split by role.** `color.brand.primary` stays the undiluted Homebrew amber and is **fill-only** — the pairing that must hold for it is dark `color.text.onBrand` knocked out of the fill. Amber drawn *on* an app surface uses the new `color.text.brand` (`Color.brewTextBrand`), which is darkened to `#98620F` in light mode because the brand amber is 2.4:1 on white. Reaching for `brewBrandPrimary` as a `foregroundStyle` is the mistake this split exists to prevent.
- **`CommandBlockView` deliberately does not use `Color.brewTerminal`.** Light-on-dark text under the system selection highlight is 1.01:1, and SwiftUI exposes no way to restyle that highlight. The command well is `brewSurface` (the sidebar's surface) with `brewTextPrimary`; the header and footer are `brewSurfaceRecessed`. Selection is therefore dark-on-light, which the system draws correctly, and moving the block back to the terminal surface reintroduces the defect. The console hit the same wall and solved it the other way, with an `NSTextView` (see 2026-08-31) — that route would allow the terminal look here at the cost of owning the text view.
- **Vivid colours survive as fills and glyphs, not as text.** After the AA pass the warning colour was too dark to read as a warning, so it split the same way the brand amber did: `color.status.warning` (#9B600D) is text-only, `color.status.warningBold` (#F0AD4E) is icons, dots and filled badges. The bold yellow is **deliberately** 1.95:1 as a bare glyph on white — under WCAG 1.4.11's 3:1 for non-text — because none of those glyphs carry meaning alone; each sits beside text saying the same thing. Do not "fix" it back.
- **A filled surface is audited by what is knocked out of it, not by the fill.** `BrandPrimary`, its hover/pressed states and `StatusWarningBold` are asserted against `TextOnBrand`.
- **The package-kind icon chip is an outline, not a fill.** The accent draws the glyph and a 1pt ring; the row shows through. A filled version was tried and rejected — it was the third arrangement of this chip, after a pale tint that did not separate from the row. Both parts are non-text graphics, so the chip is held to 3:1, not 4.5:1; it clears 4.5 anyway because the accent doubles as the kind badge's label colour.
- **There are two palettes, and only the high-contrast one is held to WCAG AA.** Colour sets carry High Contrast variants, which macOS swaps in on System Settings → Accessibility → Display → Increase contrast. The standard palette is the Homebrew palette as designed and several pairings sit below 4.5:1 on purpose — do not "fix" them. `BrewColorTokenContrastTests` asserts AA in the high-contrast appearances and, for the standard ones, only that high contrast never renders a pairing *worse*.
- **AppKit's fallback for a missing high-contrast variant is not observable with the system setting off.** A probe that resolves `NSColor(named:)` under `NSAppearance(named: .accessibilityHighContrastAqua)` returns the *standard* value, so it looks like a confirmed fallback order when it has confirmed nothing. Every colour set that varies by luminosity therefore states its high-contrast dark value outright, enforced by a test, and the reader only ever does exact-match-or-universal. Verify variants with `assetutil --info` on the compiled `Assets.car`, not with a runtime probe.
- **Borders are knowingly outside the audit.** `BorderDefault`/`Strong`/`Separator` sit at 1.1–1.7:1 against their surfaces. WCAG 1.4.11 wants 3:1 only for boundaries *required* to identify a control; these are decorative card edges, and raising them is a visible restyle rather than a conformance fix.

## 2026-08-31 — The console body is a text view, not a list of rows

- **What was wrong.** A `List` with one `Text` per line gives every line its own selection scope. Dragging across lines selected nothing, ⌘A had no document to select, the gaps between rows (row insets, and the empty area below the last line) weren't text at all so clicks there did nothing, and the pointer alternated between an arrow and an I-beam depending on which of those it was over. All four are the same defect: there was no document.
Expand Down
Loading
Loading