diff --git a/CHANGELOG.md b/CHANGELOG.md index a364aca..662091d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,17 @@ and this project follows semantic versioning for tagged releases. ## Unreleased +### Added + +- Gave each macOS menu profile a colour and symbol. New profiles get a + stable colour from their name, with `default` in graphite, and show their + initial; **Appearance…** in a profile's actions chooses one of eight + colours and twelve symbols, applied at once. The palette leaves out red, + orange and yellow, which mean low quota. +- Added **Quota in menu bar**, an opt-in gear-menu toggle that shows the + lowest remaining Codex quota beside the menu-bar icon and keeps usage + current with the same five-minute background read as alerts. + ### Changed - Redesigned the project website as a dark ASCII page drawn live with WebGL: diff --git a/README.md b/README.md index ad81139..31d2b6d 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@
+
Actual AppKit view with sample quota values. Light and dark appearance follow macOS.
@@ -127,9 +127,13 @@ profile's ⋯ menu lists every window. Hover for details or press **⌘R** to
refresh.
The menu bar icon gains a badge when a profile runs low. Turn on
-**Low-quota alerts** in the gear menu for a notification when a profile
-crosses into low or critical. Alerts are off by default, and the app never
-switches profiles for you.
+**Quota in menu bar** in the gear menu to see the lowest remaining
+percentage beside the icon, or **Low-quota alerts** for a notification when a
+profile crosses into low or critical. Both are off by default, and the app
+never switches profiles for you.
+
+Give each profile its own colour and symbol with **Appearance…** in its ⋯
+menu, so work and personal are easy to tell apart.
For an unavailable reading, use **Sign in to Codex CLI…** in that profile's
actions menu, then refresh. Quota follows the profile's **CLI sign-in**.
diff --git a/docs/index.html b/docs/index.html
index b7be7f1..967e077 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -639,7 +639,7 @@
+
01
02
03
diff --git a/docs/llms.txt b/docs/llms.txt
index 0efa53e..49f3fa4 100644
--- a/docs/llms.txt
+++ b/docs/llms.txt
@@ -225,7 +225,10 @@ Finder launches without a separately installed Codex CLI on PATH.
Profile rows show the tightest Codex CLI window's remaining percentage,
reported duration and a ring; a window at 25% or less is highlighted and its
reset countdown replaces the row description. The profile's actions menu lists
-every window. Hover for reset times. Usage is read
+every window. Hover for reset times. Each profile has a colour and symbol,
+automatic until chosen with Appearance… in its actions. An opt-in, default-off
+Quota in menu bar toggle shows the lowest remaining percentage beside the
+menu-bar icon and reads usage about every five minutes while on. Usage is read
through the official app-server method account/rateLimits/read, cached in
memory for 60 seconds and refreshed with Command-R; a refresh keeps the
previous reading visible. Missing usage does not disable launches. The
diff --git a/docs/menu-dark.png b/docs/menu-dark.png
index 29f720f..56b2917 100644
Binary files a/docs/menu-dark.png and b/docs/menu-dark.png differ
diff --git a/macos/CodexProfilesMenu/DESIGN.md b/macos/CodexProfilesMenu/DESIGN.md
index 4dc1875..eb100bd 100644
--- a/macos/CodexProfilesMenu/DESIGN.md
+++ b/macos/CodexProfilesMenu/DESIGN.md
@@ -10,13 +10,14 @@ Workspaces whose profile is missing follow the groups. A hairline divides
groups; rows within a group have no rules, because the nesting already shows
what belongs together. Nesting and search replace a profile filter.
-The 360-point popover uses semantic macOS colours, system type, monochrome
-SF Symbols and the material supplied by NSPopover. The content view has no
+The 360-point popover uses semantic macOS colours, system type, SF Symbols
+and the material supplied by NSPopover. Colour is reserved for two jobs:
+profile identity and quota emphasis. The content view has no
additional effect layer. One type scale sets profile names in 13-point
semibold, workspace names in 13-point regular and details in 11-point
secondary text; weight, not size, carries the hierarchy. A profile row is 46
-points high: a 26-point monochrome monogram tile, the name above a detail
-line, and its quota. A workspace row is a single 30-point line starting at
+points high: a 26-point tile in the profile's colour holding its initial or
+chosen symbol in white, the name above a detail line, and its quota. A workspace row is a single 30-point line starting at
its profile's title column: a pin or folder symbol, the project name and its
path on one baseline. A search result listed without its profile's row starts
under the profile tiles and names its profile instead. A workspace that
@@ -50,8 +51,26 @@ reset times, freshness and the Codex CLI sign-in scope. Countdowns stay in
English to match the surrounding text, while clock times follow the user's
locale and 12/24-hour setting. Workspace rows do not repeat account quota.
Only explicitly adding a workspace asks for a folder. App-level setup,
-sign-in, the Return destination, Open at Login, Low-quota alerts, About and
-Quit live in the gear menu.
+sign-in, the Return destination, Open at Login, Low-quota alerts, Quota in
+menu bar, About and Quit live in the gear menu.
+
+Each profile has a colour and a symbol. Until the person chooses, `default`
+is graphite and every other profile takes one of seven colours from an FNV-1a
+hash of its name, so the colour stays the same on every launch and does not
+shift when profiles are added; the symbol is the profile's initial. The
+palette of blue, indigo, purple, pink, teal, green, brown and graphite leaves
+out red, orange and yellow so they keep meaning low quota, and white symbols
+keep 4.5:1 contrast on every colour. Appearance… in a profile's actions
+replaces the list with an editor, in place rather than in a second popover so
+dismissal and keyboard focus stay with the menu: a 40-point preview with the
+profile's name and its CODEX_HOME path from the documented mapping, eight
+round colour swatches, and twelve symbols, the initial and eleven SF Symbols.
+A choice applies at once and updates the editor in place. Reset restores the
+automatic appearance; Done, Return or Escape returns to the list. The chosen
+swatch gains a ring and every choice reports its state to accessibility
+clients, so a choice never depends on colour alone. A search result listed
+without its profile's row shows a small square of the profile's colour beside
+its name. The status item stays a monochrome template image.
Apple's [menu-bar guidance](https://developer.apple.com/design/human-interface-guidelines/the-menu-bar#Menu-bar-extras)
prefers a menu unless the functionality calls for richer controls. Search,
@@ -101,7 +120,8 @@ removal and relocation update affected rows before reloading, so a failed
refresh cannot restore an old launch identity. Superseded or removed rows
cannot launch. The CLI remains the only writer of profile and binding state;
the GUI persists only its pins,
-recents, destination preference and Low-quota alerts choice in UserDefaults. Profile-only launches start
+recents, destination preference, profile colours and symbols, and its
+Low-quota alerts and menu-bar figure choices in UserDefaults. Profile-only launches start
in the home directory, avoiding accidental project-binding inheritance. Explicit
workspace launches pass their folder to the CLI and preserve its guard rules.
@@ -143,6 +163,16 @@ at the earliest reset, waiting at most a day so an implausible reset time
cannot overflow it. Remaining quota only falls within a window, so a
reading stays a true upper bound until then.
+Quota in menu bar, an opt-in gear-menu toggle that is off by default, adds
+the lowest remaining share across initialized profiles beside the icon in
+monospaced menu-bar text, such as 7%. Unlike the badge it also covers healthy
+windows, and the tooltip and accessibility label then name that profile and
+window. The lowest figure can only rise when its own window resets, so the
+same one-shot timer re-evaluates it then. While the figure is on, usage is
+read about every five minutes through the same refreshUsage path as alerts,
+so it stays current; it needs no notification permission. Background reads
+stop only when both alerts and the figure are off.
+
Low-quota alerts is an opt-in gear-menu toggle, off by default so the app
never shows an unrequested permission prompt. Authorization is requested only
when the toggle is turned on; denial keeps it off and explains System
@@ -165,10 +195,15 @@ a threshold cannot repeat. A later critical notification replaces
the low one for the same window. The body names the reset time and mentions
another profile only when it has at least 50% of the same window left, no
low window of its own and a reading under 15 minutes old. Clicking a
-notification opens the popover. While alerts are on, a five-minute loop with
-timer tolerance calls the same refreshUsage path, so the cache, two readers,
-deadlines and owned-process cleanup are unchanged; turning alerts off or
-Quit stops it, and no reader starts without initialized profiles. The app
+notification opens the popover. While alerts or the menu-bar figure are on,
+a five-minute loop with timer tolerance calls the same refreshUsage path, so
+the cache, two readers, deadlines and owned-process cleanup are unchanged;
+turning both off or Quit stops it, and no reader starts without initialized
+profiles. Each pass lists profiles before reading usage, so profiles created
+or removed outside the app join or leave the checks while the menu stays
+closed. After the first profile load these listings are quiet, leaving the
+status message the person may be reading alone; until then, as after a login
+launch, a pass loads profiles as opening the menu would. The app
never switches profiles or accounts automatically: alerts show the numbers
and the person chooses. The decision logic is plain Foundation behind a
notifier protocol, so tests never touch UNUserNotificationCenter.
@@ -182,7 +217,8 @@ values. These are layout previews;
they do not demonstrate live translucency, system permission prompts or
upstream app behaviour. Previews render at 2x whatever display draws them. The
AppKit interaction runner checks profile-first setup, nesting and alignment,
-selection, search, dismissal, Command-Return, the Return destination and
+automatic and chosen profile appearance, the appearance editor and its
+persistence, the menu-bar figure toggle, selection, search, dismissal, Command-Return, the Return destination and
launch arguments against an isolated executable. It switches the same open view light → dark → light, checks
system-colour and accessibility notifications, and verifies opaque selection
policies and accessibility state. These checks do not change global OS
@@ -195,7 +231,8 @@ updates, refresh dimming, quota colours in both appearances, the usage lines
in profile actions, rows being changed, right-click actions, menu enablement after AppKit
validation, one-off destinations and Open at Login through a fake login item. An isolated real CLI integration verifies profile
selection and strict workspace-guard handling with a fake app-server.
-Alert checks cover indicator state and its bounded timer, crossing and
+Alert checks cover indicator state and its bounded timer, the lowest
+figure and its tooltip, background reads that follow the figure, crossing and
de-duplication across jitter, early resets and missing reset times, the
settled baseline after enabling, headroom choice, preference persistence,
permission denial, undecided and unanswered prompts, background reads that
diff --git a/macos/CodexProfilesMenu/Previews/RenderPreview.swift b/macos/CodexProfilesMenu/Previews/RenderPreview.swift
index e9c0083..ab37ef2 100644
--- a/macos/CodexProfilesMenu/Previews/RenderPreview.swift
+++ b/macos/CodexProfilesMenu/Previews/RenderPreview.swift
@@ -6,7 +6,7 @@ import Foundation
struct RenderPreview {
static func main() {
guard CommandLine.arguments.count >= 2 else {
- FileHandle.standardError.write(Data("Usage: RenderPreview
+
See the [design notes](CodexProfilesMenu/DESIGN.md) for Apple guidance and
interaction behaviour. Re-render the previews after changing the view:
@@ -179,6 +195,7 @@ interaction behaviour. Re-render the previews after changing the view:
```sh
scripts/macos/render-menu-preview.sh macos/CodexProfilesMenu/Previews/menu-light.png
scripts/macos/render-menu-preview.sh macos/CodexProfilesMenu/Previews/menu-dark.png --dark
+scripts/macos/render-menu-preview.sh macos/CodexProfilesMenu/Previews/menu-appearance.png --appearance
```
## Build locally
diff --git a/test/macos/menu-app-test.sh b/test/macos/menu-app-test.sh
index 8eba1f0..7f382d3 100755
--- a/test/macos/menu-app-test.sh
+++ b/test/macos/menu-app-test.sh
@@ -110,6 +110,7 @@ swiftc \
-o "$TMP_DIR/RenderPreview"
"$TMP_DIR/RenderPreview" "$TMP_DIR/menu-light.png"
"$TMP_DIR/RenderPreview" "$TMP_DIR/menu-dark.png" --dark
+"$TMP_DIR/RenderPreview" "$TMP_DIR/menu-appearance.png" --appearance
printf 'Native light and dark preview backdrop checks passed.\n'
MENU_APP_BUILD_DIR="$TMP_DIR/app-build" "$BUILD_SCRIPT"