git:20261015.b95492a to git:20261022.ce1953a
20 added, 21 removed. Audit A to A.
# floating-clock
Always-on-top floating desktop clock for macOS. Objective-C implementation (~470 KB binary, ~13 MB physical footprint) using NSPanel. Right-click for 30 color themes, 15 font sizes (10–64 pt), and live market-session state across 14 major global stock exchanges. Overlay rails above the clock for audio I/O, network route, display brightness and text-to-speech speed.
**Idle CPU:** ~0.7% with all rails off, ~2.9% with the audio and network rails on (M3 Max, 2026-09-20). Measure a CPU-time delta: `ps %cpu` is a lifetime average and reads ~0.0%. Method and per-rail figures: [docs/brightness-rail.md](./docs/brightness-rail.md) § Cost.
**Hub:** [CLAUDE.md](../../CLAUDE.md) | **Sibling:** [plugins/CLAUDE.md](../CLAUDE.md)
## Build
From the plugin directory:
```bash
cd plugins/floating-clock
make all # build + bundle + sign
make run # build + bundle + sign + open app
make install # install to /Applications
make test # build + run data-layer unit tests
make check # build + test (pre-release validation)
make clean # remove build/ artifacts
make help # list all targets
```
- Tests live in `tests/*.m`, linked into one `build/test_session` binary: `main()` in `tests/test_session.m` registers 143 tests with `RUN_TEST`, including those defined in the sibling fixtures (`test_holidays`, `test_halfdays`, `test_levers`, `test_styling_levers`, `test_local_features`, `test_network`, `test_overlay_width`, `test_brightness`, `test_tts_rate`, `test_level_meter`). The tests link every source file except the AppKit-bound ones listed in `TEST_EXCLUDED` in the `Makefile`. Coverage includes `computeSessionState` (session boundaries, weekend skip, lunch state, PRE-MARKET/AFTER-HOURS promotion, progress math, 24 h state-invariant sweeps), the TZ-helper layer (DST branching for BST/CEST/EDT/AEDT, UTC-offset formatting including Kolkata's UTC+5:30, fullTzLabel composition), cityCode / flag emoji mapping for all 14 exchanges, the holiday and half-day calendars, starter-profile key-coverage invariants, the progressive countdown format (sub-day `T-HH:MM:SS` vs ≥24h `T-Nd Hh MMm`), `FCFormatLandingTime` cross-day/cross-weekday cases, `FCParseFontWeight` id→NSFontWeight mapping with fallback, the `FCResolveSegmentWeight` fallback chain (segment key → global FontWeight → Medium), and the `FCResolveSegmentOpacity` fallback chain with clamping (segment key → CanvasOpacity → theme alpha).
+ Tests live in `tests/*.m`, linked into one `build/test_session` binary: `main()` in `tests/test_session.m` registers every test with `RUN_TEST`, including those defined in the sibling fixtures (every other `tests/test_*.m`). The tests link every source file except the AppKit-bound ones listed in `TEST_EXCLUDED` in the `Makefile`. Coverage includes `computeSessionState` (session boundaries, weekend skip, lunch state, PRE-MARKET/AFTER-HOURS promotion, progress math, 24 h state-invariant sweeps), the TZ-helper layer (DST branching for BST/CEST/EDT/AEDT, UTC-offset formatting including Kolkata's UTC+5:30, fullTzLabel composition), cityCode / flag emoji mapping for all 14 exchanges, the holiday and half-day calendars, starter-profile key-coverage invariants, the progressive countdown format (sub-day `T-HH:MM:SS` vs ≥24h `T-Nd Hh MMm`), `FCFormatLandingTime` cross-day/cross-weekday cases, `FCParseFontWeight` id→NSFontWeight mapping with fallback, the `FCResolveSegmentWeight` fallback chain (segment key → global FontWeight → Medium), and the `FCResolveSegmentOpacity` fallback chain with clamping (segment key → CanvasOpacity → theme alpha).
Binary at `build/floating-clock` (the installed signed build is ~470 KB), app bundle at `build/FloatingClock.app`. App bundle includes `Contents/Resources/Icon.icns` (generated at build time from `Sources/gen-icon.m` via Core Graphics — no external image dependencies). Spotlight/Launchpad/Finder index the app with this icon after first install.
Third-party code: `Sources/vendor/RMBlurredView/` vendors [RMBlurredView](https://github.com/raffael/RMBlurredView) (Raffael Hannemann, 2013, MIT) for the frosted-glass segment backdrops. ~155 LoC including the header, only public APIs (CIFilter + CALayer.backgroundFilters), one local pragma delta from upstream.
## Slash Commands
| Command | Purpose |
| --------------------------- | -------------------------------------------------------------------------------- |
| `/floating-clock:install` | Build + copy to `/Applications/` + launch |
| `/floating-clock:launch` | Open the installed (or local) app |
| `/floating-clock:quench` | Terminate the running clock |
| `/floating-clock:diagnose` | One-page health report (binary info, signing, process, active profile, tests) |
| `/floating-clock:uninstall` | Quit, remove from `/Applications/`, clear NSUserDefaults (confirmation required) |
## Design
- **NSPanel (not NSWindow)**: Borderless, non-activating, always floating
- **All Spaces**: `collectionBehavior = NSWindowCollectionBehaviorCanJoinAllSpaces | NSWindowCollectionBehaviorStationary`
- **Timer**: `dispatch_source_t` aligned to second boundary, 1-second interval
- **No Dock icon**: `LSUIElement=YES` in Info.plist
- **Font resolution** (in priority order):
1. User override via NSUserDefaults key `FontName` (PostScript name)
2. iTerm2 default profile's `Normal Font` (extracted from `com.googlecode.iterm2.plist`)
3. System monospaced fallback: SF Mono (macOS 10.15+) or Menlo (older)
4. Size selectable from 15 options (10 / 12 / 14 / 16 / 18 / 20 / 22 / 24 / 28 / 32 / 36 / 42 / 48 / 56 / 64 pt) grouped as Small / Medium / Large / Huge in the context menu
- **Color themes**: 30 preset bundles (each sets foreground, background, alpha atomically), defined in `kThemes[]` in `Sources/data/ThemeCatalog.m`: Terminal, Amber CRT, Green Phosphor, Solarized Dark, Dracula, Nord, Gruvbox, Rose Pine, High Contrast, Soft Glass, Synthwave, Monokai, Gotham, Ayu Mirage, Catppuccin, Tokyo Night, Kanagawa, Paper White, Sepia, Midnight Blue, Oceanic Deep, Cherry Blossom, Espresso, Lavender Dream, Mint Dark, Forest, Volcanic, Carnival (yellow on green, pairs with B3), Aurora (cyan-green on deep indigo) and Concrete (neutral gray on charcoal). Menu items show 14×14 color swatches drawn inline via Core Graphics.
- **Market sessions** (when a non-local market is selected):
- 14 major exchanges grouped by region (Americas / Europe / Africa / Asia / Oceania) — NYSE, TSX, B3, LSE, Euronext, XETRA, SIX, TSE, HKEX, SSE, KRX, NSE, ASX, JSE
- Time displayed in that exchange's local time via IANA `NSTimeZone` (DST-correct across hemispheres)
- Second line shows state glyph + market code + progress bar + countdown:
- `●` green: OPEN (regular session)
- `◑` violet: LUNCH (TSE / HKEX / SSE only)
- `◐` amber: PRE-MARKET
- `◒` rose: AFTER-HOURS
- `○` gray: CLOSED (overnight, weekend) — shows `opens in Xh Ym` or `opens EEE HH:mm` for gaps >99h
- Progress bar uses Unicode 1/8-width blocks (`█▉▊▋▌▍▎▏░`) for sub-cell smoothness
- Countdown format: `2h17m` (≥1h), `47m` (<1h), `5m32s` (<2m)
- Window auto-resizes to a 2-line layout with center-anchor; falls back to 1-line when Local Time is selected
- **Default position** (first launch, no saved state): bottom-center of main screen's `visibleFrame` (respects menu bar and Dock)
- **Multi-monitor position persistence**:
- Saves both window frame and screen ID on every move (`windowDidMove:`)
- On launch: restores only if saved screen still connected AND frame intersects that screen
- If saved screen disconnected: falls back to bottom-center of main screen
- At runtime: monitors `NSApplicationDidChangeScreenParametersNotification`; if clock's screen unplugged, relocates to bottom-center of main screen with animation
- **Defensive parsing**: All plist dictionary lookups verify `isKindOfClass:` before use — malformed iTerm2 plist cannot crash the clock
- **Self-generated icon**: `gen-icon` helper draws a 1024×1024 clock glyph (dark rounded square + white face + 10:10 hands) using only Core Graphics. `iconutil` bundles into ICNS at build time. Zero external image assets.
## Touchpoints
Everything this plugin touches on your system. Nothing outside this table.
- | Kind | Detail |
- | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
- | **Reads (filesystem)** | `~/Library/Preferences/com.googlecode.iterm2.plist` — read-only, font lookup only. If missing/malformed: silently falls back to SF Mono. |
- | **Writes (filesystem)** | `~/Library/Preferences/com.cc-skills.floating-clock.plist` — written via NSUserDefaults. See Runtime Preferences below for keys. |
- | **Install path** | `/Applications/FloatingClock.app` — placed by `/floating-clock:install` or `make install`. Uninstall removes this directory only. |
- | **Build artifacts** | `plugins/floating-clock/build/` — gitignored. Contains `floating-clock` binary, `gen-icon` helper, PNG iconset, `Icon.icns`, and `FloatingClock.app`. `make clean` removes. |
- | **Linked frameworks** | `Cocoa`, `Foundation`, `AppKit`, `CoreFoundation`, `libobjc`, `libSystem` (main binary), plus `QuartzCore`, `CoreLocation`, `CoreAudio`, `AVFoundation`, `IOBluetooth`, `CoreText`, `SystemConfiguration`, `CoreWLAN` and `Metal` for the overlay indicators, and `Carbon` for the hide-toggle hotkey (2026-10-10) (`Metal` 2026-09-19: the brightness rail presents a 1×1 EDR frame). Plus `ImageIO`, `UniformTypeIdentifiers`, `CoreServices` (build-time `gen-icon` helper only). One PRIVATE framework is `dlopen`'d and never linked — `DisplayServices`, so a missing symbol degrades the brightness rail instead of breaking the build. All system, not third-party. |
- | **Signing** | Ad-hoc code signature via `codesign --force --deep --sign -`. No Developer ID cert required. No notarization. Gatekeeper allows ad-hoc apps on first run with standard right-click → Open bypass if needed. |
- | **Entitlements** | None. Unsandboxed. No hardened runtime flags. |
- | **Network** | No network CALLS — the binary never opens a socket, and nothing it displays comes from a remote service. It does READ local network configuration and per-interface statistics in-process for the network bar: `SCDynamicStore` (primary interface, services holding IPv4), `getifaddrs(3)` (addresses, byte counters), and CoreWLAN (RSSI, noise, PHY rate). CoreWLAN's `-ssid` is deliberately never called: it requires Location authorization, and the network NAME is the one field here that identifies a place. |
- | **Subprocesses** | Exactly one, and only on demand — `/usr/sbin/networksetup`, used by the network bar to list network services and to reorder them when you pick a different route. Never spawned from the 1 Hz tick (that would wreck the idle-CPU budget); the service list is cached and refetched only on a menu open, a switch, or a newly-seen primary interface, the last of which is rate-limited by a negative cache so an interface `networksetup` never lists (a VPN's `utunN`) cannot provoke a fetch every tick. Measured idle with a resolvable primary: 0.3 % CPU, zero child processes; the unresolvable path is pinned by unit test rather than by that measurement. |
- | **System mutations** | **Three**, all only on explicit user action, all reversible. (1) **Network service order** via `networksetup -ordernetworkservices` — moves the default route for every app; reversible by picking the previous service. (2) **Display brightness and gamma** — the brightness rail writes the system-wide macOS brightness value, and above 100% multiplies the built-in display's gamma table. The gamma override reverts automatically on process death, including SIGKILL (verified 2026-09-20). Guards, degradation and attack-test results: [docs/brightness-rail.md](./docs/brightness-rail.md). |
- | **Launchd** | None. Not registered as a LaunchAgent or LaunchDaemon. No autostart at login — launch manually via Spotlight, Launchpad, Finder, or `/floating-clock:launch`. |
- | **Dock / menu bar** | Hidden from both. `LSUIElement=YES` in Info.plist makes it an accessory app — no Dock tile, no application menu bar. The only visible UI is the floating clock window itself. |
- | **System permissions** | None at runtime. Accessibility access is NOT required (nothing uses the AX API). The context menu works via standard NSMenu, which needs no permission grant. |
- | **Keyboard monitors** | One local (in-process) `NSEvent` monitor for ⌘Q to route to `NSApp terminate:`. Scope: only this app's events — does not see other apps' keystrokes. No global event taps. One Carbon global hotkey, ⌃⌥⇧⌘H by default (`ToggleVisibilityHotKey`), for the whole-clock hide toggle: the system delivers only that exact chord, so no other keystroke is visible and no permission is needed. |
- | **IPC** | Observes three distributed notifications, `<bundle id>.visibility.toggle`, `.show` and `.hide` (the prefix is `CFBundleIdentifier`) (any local process may post them; the worst a post can do is hide or show the clock). Posts an `…explicit-audio-selection` notification when you pick an audio device. `floating-clock --toggle\|--show\|--hide` posts the first three and exits. |
- | **Clock source** | `dispatch_source_t` on the main queue, timer aligned to second boundaries via `[NSDate timeIntervalSince1970]` fractional remainder. No IPC with any external time service. |
-
- The third system mutation (2026-10-08): clicking the MIC MUTED banner clears the default input's CoreAudio mute flag. It acts only when that flag is set, and whatever set the flag can set it again.
+ | Kind | Detail |
+ | ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+ | **Reads (filesystem)** | `~/Library/Preferences/com.googlecode.iterm2.plist` — read-only, font lookup only. If missing/malformed: silently falls back to SF Mono. |
+ | **Writes (filesystem)** | `~/Library/Preferences/com.cc-skills.floating-clock.plist` — written via NSUserDefaults. See Runtime Preferences below for keys. |
+ | **Install path** | `/Applications/FloatingClock.app` — placed by `/floating-clock:install` or `make install`. Uninstall removes this directory only. |
+ | **Build artifacts** | `plugins/floating-clock/build/` — gitignored. Contains `floating-clock` binary, `gen-icon` helper, PNG iconset, `Icon.icns`, and `FloatingClock.app`. `make clean` removes. |
+ | **Linked frameworks** | `Cocoa`, `Foundation`, `AppKit`, `CoreFoundation`, `libobjc`, `libSystem` (main binary), plus `QuartzCore`, `CoreLocation`, `CoreAudio`, `AVFoundation`, `IOBluetooth`, `CoreText`, `SystemConfiguration`, `CoreWLAN`, `IOKit` and `Metal` for the overlay indicators, and `Carbon` for the hide-toggle hotkey (2026-10-10) (`Metal` 2026-09-19: the brightness rail presents a 1×1 EDR frame). Plus `ImageIO`, `UniformTypeIdentifiers`, `CoreServices` (build-time `gen-icon` helper only). One PRIVATE framework is `dlopen`'d and never linked — `DisplayServices`, so a missing symbol degrades the brightness rail instead of breaking the build. All system, not third-party. |
+ | **Signing** | `make sign` signs with the persistent self-signed identity **"FloatingClock Local Signing"** (`SIGN_IDENTITY` in the `Makefile`), so TCC grants survive rebuilds; it falls back to ad-hoc (`--sign -`) only when that cert is absent from the login keychain. No Developer ID, no notarization. Cert recreation and the identity-change gotcha: [docs/signing-and-tcc.md](./docs/signing-and-tcc.md). |
+ | **Entitlements** | None. Unsandboxed. No hardened runtime flags. |
+ | **Network** | No network CALLS — the binary never opens a socket, and nothing it displays comes from a remote service. It does READ local network configuration and per-interface statistics in-process for the network bar: `SCDynamicStore` (primary interface, services holding IPv4), `getifaddrs(3)` (addresses, byte counters), and CoreWLAN (RSSI, noise, PHY rate). CoreWLAN's `-ssid` is deliberately never called: it requires Location authorization, and the network NAME is the one field here that identifies a place. |
+ | **Subprocesses** | Exactly one, and only on demand — `/usr/sbin/networksetup`, used by the network bar to list network services and to reorder them when you pick a different route. Never spawned from the 1 Hz tick (that would wreck the idle-CPU budget); the service list is cached and refetched only on a menu open, a switch, or a newly-seen primary interface, the last of which is rate-limited by a negative cache so an interface `networksetup` never lists (a VPN's `utunN`) cannot provoke a fetch every tick. Measured idle with a resolvable primary: 0.3 % CPU, zero child processes; the unresolvable path is pinned by unit test rather than by that measurement. |
+ | **System mutations** | **Three**, all only on explicit user action, all reversible. (1) **Network service order** via `networksetup -ordernetworkservices` — moves the default route for every app; reversible by picking the previous service. (2) **Display brightness and gamma** — the brightness rail writes the system-wide macOS brightness value, and above 100% multiplies the built-in display's gamma table. The gamma override reverts automatically on process death, including SIGKILL (verified 2026-09-20). Guards, degradation and attack-test results: [docs/brightness-rail.md](./docs/brightness-rail.md). (3) **Input mute flag** (2026-10-08): clicking the MIC MUTED banner clears the default input's CoreAudio mute flag, only when it is set; whatever set it can set it again. |
+ | **Launchd** | None. Not registered as a LaunchAgent or LaunchDaemon. No autostart at login — launch manually via Spotlight, Launchpad, Finder, or `/floating-clock:launch`. |
+ | **Dock / menu bar** | Hidden from both. `LSUIElement=YES` in Info.plist makes it an accessory app — no Dock tile, no application menu bar. The only visible UI is the floating clock window itself. |
+ | **System permissions** | Four TCC grants, each asked once per signing identity (usage strings in `Info.plist`): **Location** (`LocationProvider`, sunrise/sunset glyph, city-level), **Bluetooth** (`BluetoothPairedAudioDeviceConnector`, connect a paired audio device), **Microphone** (`MicMuteIndicator`, signal level for mute detection, never recorded) and **System Audio Recording Only** (`OutputLevelTap` process tap, output level meter, runs only while something plays). Accessibility is NOT required (nothing uses the AX API). |
+ | **Keyboard monitors** | One local (in-process) `NSEvent` monitor for ⌘Q to route to `NSApp terminate:`. Scope: only this app's events — does not see other apps' keystrokes. No global event taps. One Carbon global hotkey, ⌃⌥⇧⌘H by default (`ToggleVisibilityHotKey`), for the whole-clock hide toggle: the system delivers only that exact chord, so no other keystroke is visible and no permission is needed. |
+ | **IPC** | Observes three distributed notifications, `<bundle id>.visibility.toggle`, `.show` and `.hide` (the prefix is `CFBundleIdentifier`) (any local process may post them; the worst a post can do is hide or show the clock). Posts an `…explicit-audio-selection` notification when you pick an audio device. `floating-clock --toggle\|--show\|--hide` posts the first three and exits. |
+ | **Clock source** | `dispatch_source_t` on the main queue, timer aligned to second boundaries via `[NSDate timeIntervalSince1970]` fractional remainder. No IPC with any external time service. |
## Runtime Preferences
All settings persist in `~/Library/Preferences/com.cc-skills.floating-clock.plist` (NSUserDefaults). Inspect or reset via the `defaults` CLI:
```bash
defaults read com.cc-skills.floating-clock # show all
defaults delete com.cc-skills.floating-clock # reset everything (next launch -> defaults)
```
**Full key table (60+ keys): [docs/runtime-preferences.md](./docs/runtime-preferences.md)** — display modes, fonts, themes, per-segment overrides, profiles, urgency levers, indicator bars. Update the spoke, not this hub, when adding keys.
## Implementation
- **Entry point**: `Sources/clock.m` (~380 LoC)
- **Icon helper**: `Sources/gen-icon.m` (~170 LoC, build-time only)
Source is organized hierarchically by area (the main directories; `core/` also holds the overlay indicators, location provider and brightness code):
```
Sources/
clock.m entry + registerDefaults + panel init
core/
FloatingClockPanel.{h,m} NSPanel subclass (interface)
FloatingClockPanel+Runtime.{h,m} tick pipeline, timers, positioning
FloatingClockPanel+Layout.{h,m} 3-segment + legacy layout maths
DateFormatPrefix.{h,m} 9-preset DateFormat → UTS#35 pattern
SkyGlyph.{h,m} 5-phase hour-of-day → emoji dispatcher
SegmentGap.{h,m} 7-preset SegmentGap → points
DensityPad.{h,m} 6-preset Density → inner-row padding
CornerRadius.{h,m} 8-preset CornerStyle → layer radius
ShadowSpec.{h,m} 7-preset ShadowStyle → spec struct
SessionSignalWindow.{h,m} 5-preset SessionSignalWindow → minutes, gates PRE-MARKET + AFTER-HOURS promotions
ClipboardHeader.{h,m} FCComposeClipboardSnapshot — self-documenting UTC-stamped header for Copy cluster
+ AudioHALSnapshotReader.{h,m} CoreAudio HAL reads/writes on a private serial queue, immutable snapshots to main (cc-skills#37)
segments/
FloatingClockSegmentViews.{h,m} Local/Active/Next/ClockContentView subclasses
content/
ActiveSegmentContentBuilder.{h,m} live-markets rendering
NextSegmentContentBuilder.{h,m} next-to-open rendering
SegmentHeaderRenderer.{h,m} shared title/legend/hrule helper
UrgencyColors.{h,m} shared urgency palette + thresholds, continuous gradient + 1Hz pulse
UrgencyHorizon.{h,m} 6-preset UrgencyHorizon → seconds, runtime gradient horizon
UrgencyFlash.{h,m} 4-preset UrgencyFlash → dim-alpha, runtime 1Hz pulse intensity
WeekProgressBar.{h,m} FCWeekFraction + FCBuildWeekProgressBar — pure-offline week-progress bar on LOCAL
LandingTimeFormatter.{h,m} dual-zone time w/ weekday disambiguation
data/
ThemeCatalog.{h,m} 30 theme presets + CG swatches
MarketCatalog.{h,m} 14-exchange registry (+ local) + IANA helpers
HolidayCalendar.{h,m} per-exchange holiday fixtures
HalfDayCalendar.{h,m} per-exchange early-close fixtures
MarketSessionCalculator.{h,m} computeSessionState, countdown fmts, progress-bar 10-glyph dispatch
rendering/
FontResolver.{h,m} iTerm2 → system monospaced cascade + FontWeight + LetterSpacing + LineSpacing + CurrentTimeFormat
SegmentOpacityResolver.{h,m} 3-tier canvas-opacity fallback
AttributedStringLayoutMeasurer.{h,m} NSLayoutManager multi-line height
VerticallyCenteredTextFieldCell.{h,m} cell that centers attributed text
menu/
FloatingClockPanel+MenuBuilder.{h,m} full preferences menu + Profile submenu + Quick Styles integration
FloatingClockPanel+SegmentMenus.{h,m} LOCAL / ACTIVE / NEXT scoped menus
FloatingClockPanel+MenuHelpers.{h,m} shared NSMenu helpers
actions/
FloatingClockPanel+ActionHandlers.{h,m} every menu-item action target (40+ setters + applyQuickStyle + resetVisualStyle)
preferences/
FloatingClockPanel+ProfileManagement.{h,m} save/load/switch/delete
FloatingClockStarterProfiles.{h,m} 8 bundled starters + profileManagedKeys
FloatingClockQuickStyles.{h,m} 17 Quick Style bundled moods
vendor/
RMBlurredView/ vendored frosted-glass library (MIT)
gen-icon.m build-time-only icon renderer
```
Design notes:
- `@autoreleasepool` for memory hygiene
- No external dependencies beyond system frameworks (plus the vendored RMBlurredView)
- No SwiftUI, no Swift runtime tax
- Automatic Reference Counting (ARC)
- `resolveClockFont()`: 4-tier font resolution
- `defaultFrame()`: bottom-center of primary display (uses `[NSScreen screens].firstObject`, not `mainScreen`)
- `screensChanged:`: runtime observer for monitor hot-unplug
- `buildMenu` / `refreshMenuChecks:` / `applyDisplaySettings`: NSMenu-driven preferences
- `groupedSubmenuTitled:` + `setChecksInMenu:` — recursive helpers for hierarchical menus (font sizes, regions)
- `kThemes[]` — static C array of 30 theme structs (id, display, fg_rgb, bg_rgb, alpha)
- `swatchForTheme()` — inline CoreGraphics drawing of menu item color swatches
- `kMarkets[]` — static C array of 15 market structs, `local` plus the 14 exchanges (id, display, code, iana, session hours, lunch times)
- `computeSessionState()` — state + progress + countdown via `NSCalendar` in the exchange's IANA TZ
- `buildProgressBar()` — Unicode 1/8-width block bar with color-split filled/unfilled portions via `NSAttributedString`
- `ClockContentView`: custom `NSView` subclass whose `menuForEvent:` returns the context menu on right-click
## Canonical UI Names
Every user-visible UI element has a stable canonical short name so feedback can be precise (e.g. "the label in the bottom-right of ACTIVE called [COUNTDOWN] is 2pt too small"). Toggle `Show Debug Labels` in the context menu (or `defaults write com.cc-skills.floating-clock ShowDebugLabels -bool YES`) to render these as tiny corner overlays on the running app. Hovering any named NSView shows its full NSToolTip.
| Nameidg | Surface class | Purpose / hover tooltip |
| ---------- | ---------------------------- | ---------------------------------------------------------------------------------------------------------------- |
| `[LOCAL]` | `LocalSegmentView` | LOCAL — top segment, current user-local time |
| `[ACTIVE]` | `ActiveSegmentView` | ACTIVE — bottom-left segment, currently-open markets with progress bars |
| `[NEXT]` | `NextSegmentView` | NEXT — bottom-right segment, upcoming-open markets with landing countdowns |
| `[TIME]` | `LocalSegmentView.timeLabel` | TIME — user-local time text inside [LOCAL] (NSToolTip only; no corner overlay since the label fills the segment) |
Sub-element names for elements that live inside attributed-string content (e.g. `[PROGRESSBAR]`, `[COUNTDOWN]`, `[HEADER-LEGEND]`, `[SKYGLYPH]`, `[DATE]`, `[MARKETCODE]`) can't use NSToolTip directly because `NSAttributedString` text runs aren't distinct `NSView`s. The names stay as a paper reference so the user can call them out verbally ("the [COUNTDOWN] column right-aligns oddly when the market has lunch") and we find them in code by grep.
## Whole-clock hide toggle (⌃⌥⇧⌘H) — [docs/visibility-toggle.md](./docs/visibility-toggle.md)
One switch hides the clock and every rail welded to it, and brings them all back in place (2026-10-10). Entry points: the global hotkey ⌃⌥⇧⌘H (Carbon, no permission needed; re-bind or disable with `ToggleVisibilityHotKey`), the context-menu item **Hide Clock**, `floating-clock --toggle|--show|--hide`, or the matching distributed notifications. Not persisted: a relaunch always shows the clock. The spoke records how the chord was chosen (config survey, then a press-and-diff behavioural check, since a Carbon hotkey conflict double-fires silently rather than failing) and the live test suite in `scripts/hotkey-check/`. Code: `Sources/core/{ClockVisibilityToggle,HotKeySpec}.{h,m}`; tests: `tests/test_hotkey_spec.m`.
## Brightness rail (0–140%) — [docs/brightness-rail.md](./docs/brightness-rail.md)
Second from the top of the indicator stack, just under the TTS speed rail (2026-09-19). One continuous control: 0–100% drives the ordinary macOS brightness value via a `dlopen`'d DisplayServices SPI; above 100% it unlocks EDR headroom the slider cannot reach, using a 1×1 Metal EDR trigger plus a gamma ramp scaled above 1.0. Gamma reverts on process death including SIGKILL. Collapses to a plain 0–100 slider on any Mac without extra headroom. Defaults: `BrightnessBarEnabled`, `BrightnessBarStep`. Mechanism, safety guards, attack-test results, licensing and cost: the spoke above.
## TTS speed rail (1×–3×)
Top of the indicator stack (2026-10-08), off by default (`TTSBarEnabled`). Sets how fast a text-to-speech reader speaks, live and mid-sentence. The rail writes one number, `%.2f\n`, to `TTSRateFile` (default `~/.config/floating-clock/tts-rate`); the file is both the contract and the persisted setting. A reader that polls the file can apply the rate through a pitch-preserving time-stretch (on macOS, `AUNewTimePitch`), which keeps words intelligible at speeds where re-synthesizing faster garbles them. Controls: drag the track to one of seven evenly spaced detents (1, 1.25, 1.5, 1.75, 2, 2.5, 3); −/+ and the top/bottom half of the number step by 0.25; scroll steps by 0.05; the 🔊 icon goes back to 1×; right-click opens the presets. 1× is the floor. The 1 Hz tick costs one `stat(2)` and re-reads the file only when its mtime moves, so a hand edit shows up within a second. The rail never starts, stops or signals a reader. Code: `Sources/core/TTSRate{Helpers,BarZoneView,StatusIndicator}.{h,m}`; tests: `tests/test_tts_rate.m`.
## Overlay indicators (audio bar · mic-mute · VPN) — [docs/overlay-indicators.md](./docs/overlay-indicators.md)
Audio I/O bar (`AudioStatusIndicator`, default ON): manual IN/OUT device + volume control above the clock; right-click pull-out menus with Bluetooth connect/takeover (`AudioDeviceSelectionMenuController` + `BluetoothPairedAudioDeviceConnector`); coreaudiod hijack guard keeps IN/OUT independent. All overlay panels are drag-WELDED to the clock as child windows (`ClockChildWindowAttachment`). The VPN/state-file banner stays generic and secret-free. Defaults: `AudioBarEnabled`, `AudioBarStep`, `VPNIndicator*`. Full saga, defaults tables, and diagnostics: the spoke above.
## Solar canvas + segment styling — [docs/solar-canvas-and-styling.md](./docs/solar-canvas-and-styling.md)
Compact-mode background rides an OKLab twilight ramp over the LIVE solar elevation at the user's CoreLocation coordinates (`CanvasColorMode`: solar-vivid DEFAULT / solar-atmospheric / theme — `SolarSkyColorRamp` + `FCSolarElevationDegrees`). Hairline segment border lever (`BorderStyle`: hairline DEFAULT / frame / none — `SegmentBorderSpec`, `FCApplyBorderToLayer`). Compact text legibility via Core Text round-join outline (`SolarOutlinedTextRenderingView`).
## Build signing + TCC — [docs/signing-and-tcc.md](./docs/signing-and-tcc.md)
The Makefile signs with the persistent "FloatingClock Local Signing" cert so TCC grants (Bluetooth / mic / location) survive rebuilds. Identity changes reset ALL grants — one re-Allow each. Cert recreation recipe in the spoke.
## Known limitations
- **Holiday awareness: 14/14 exchanges (2026 fixtures); half-days: 8/14.** `Sources/data/HolidayCalendar.{h,m}` + `HalfDayCalendar.{h,m}`, wired into `computeSessionState` with correct back-to-back chaining (weekend+holiday clusters). Live caveats: 6 exchanges' half-days deferred, SSE make-up Saturdays (补班) not modelled, lunar dates fixture-locked best-effort. Per-exchange detail: [docs/holiday-coverage.md](./docs/holiday-coverage.md).
- **No extended after-hours trading window modelled**. Each exchange's full extended session (US equities 16:00–20:00 ET, various 1–2 h windows elsewhere) is not modelled. What is modelled: a uniform window after regular close promotes CLOSED → AFTER-HOURS, symmetric to the PRE-MARKET window before open. Its length is the `SessionSignalWindow` preference (`off` / `5min` / `15min` default / `30min` / `60min`, `Sources/core/SessionSignalWindow.{h,m}`). Per-market extended-session modelling is not implemented, because it would need per-exchange duration data.
## Future Enhancements — [docs/roadmap.md](./docs/roadmap.md)
Near-term + Tier-3 backlog and the shipped-in-v4 log live in the spoke.