Skip to content

Releases: binlecode/co-awareness

v2.2.0

Choose a tag to compare

@binlecode binlecode released this 28 Sep 00:51

Added

  • Unified telemetry source list: The top metrics section and the collapsible "Other Sources" list are merged into a single flat source list at the top of the menu (CPU, Memory, DRAM Bandwidth, GPU, Network, Disk, Fan, Battery, Temperature, ANE). Each available source displays its live readout directly. Clicking any row immediately switches the active telemetry monitor driving the status bar item.
  • Trace Chart and Live Value at the top of Presets: The Presets ▸ submenu now features Trace Chart and Live Value at the top above the GIF presets. Selecting Trace Chart or Live Value switches the status bar representation directly, while selecting any GIF switches to that animated runner. Mutually exclusive selection checkmarks indicate the active representation.
  • Single status bar display architecture (--display): The status bar representation is configured via --display <gif|trace|value|chart> or CO_AWARENESS_DISPLAY, unifying all visual forms into one slot with zero jitter.

v2.1.0 — the trace chart menu-bar label

Choose a tag to compare

@binlecode binlecode released this 27 Sep 02:16

Added

  • --label chart: the last 60 seconds, on the menu bar. The label slot could show a number or
    a fixed tag, but a single reading can't tell you whether a spike is just starting or already over,
    and the dropdown's sparkline only showed that after a click. A new label mode, Trace Chart
    (--label chart, CO_AWARENESS_LABEL=chart, or Settings ▸ Menu Bar Label), draws the active
    source's load history as a 45 pt bar sparkline in the adjacent slot. It uses the same
    green/amber/red thresholds as the dropdown (inverted for battery) and redraws on the same 2 s tick.
    The slot keeps a fixed width, so nothing beside it moves; a Keep Awake countdown sits next to the
    chart, closest to the icon, in the Keep Awake tint. Remembered across relaunches like the other modes.
    chart is now a reserved keyword, so a custom label reading literally "chart" can't be expressed.

release: v1.23.1 — the menu-bar countdown pauses when the icon is hidden

Choose a tag to compare

@binlecode binlecode released this 12 Sep 03:16

Fixed

  • A timed Keep Awake kept a 1 Hz redraw running while the icon was hidden. The menu-bar countdown
    measured and relaid out its slot every second even with the status item behind the notch, in menu-bar
    overflow, on another Space, or with the display asleep — the one case the app's pause-when-hidden
    rule exists to cover, and an 8-hour window held it for eight hours. The countdown now reads the same
    occlusion verdict the animation does and stops with it, redrawing on the way back so the slot never
    shows the second it went dark on. A countdown in an open menu is unaffected: the menu is its own
    window, visible whatever the status item is doing.

release: v1.23.0 — Keep Awake counts down on the menu bar itself

Choose a tag to compare

@binlecode binlecode released this 12 Sep 03:16

Added

  • Keep Awake can now wait for a process instead of a clock. --keep-awake-pid <pid> (or
    MENUBAR_LOAD_RUNNER_KEEP_AWAKE_PID) holds the Mac awake until that process exits, then releases
    on its own — the shape an unattended terminal job actually has, where picking 2h or 4h is a
    guess in both directions and the guess that ends early is the one that costs the job. From the menu,
    Keep Awake ▸ Until a process exits… takes a pid or a name, matching the newest process of
    that name you are running, so binding to a job already in flight doesn't start with a pgrep. The
    hold names its subject wherever it is shown (Keep Awake: claude (41293)), obeys the battery band
    and the 5% floor like any other window, and is deliberately never resumed after a reboot — pids are
    recycled, so a restored one could bind to something unrelated. It survives an in-app restart, which
    doesn't reboot the Mac. Release is event-driven (a kqueue exit watch), with a 2s liveness re-check
    underneath it as a fallback.
  • A timed Keep Awake now counts down on the menu bar itself, not just inside the menu. How long
    is left was previously a fact you had to open the menu to learn, which is the wrong shape for the
    thing it answers — you glance at a menu bar, you navigate a menu. With the label off, the slot
    reveals the countdown on its own (29:58) in the Keep Awake tint, wearing the paused tone when a
    condition has the hold suspended, and collapses back to nothing when the window ends or you disarm
    it. With --label value or a custom label, the countdown sits alongside the reading rather than
    replacing it (CPU 45% 29:58), always on the side nearest the icon. The slot reserves width for
    the full 88:88 / 88:88:88 template, so a ticking countdown shifts neighboring items by 0 pt —
    the same no-jitter promise the telemetry labels already make. An indefinite or process-bound hold
    shows nothing, having no clock to show. The 1 Hz ticker behind it runs only while there is a live
    countdown to draw or the menu is open, and stops otherwise.

Fixed

  • A countdown that ran out while Keep Awake was paused stayed frozen at 00:01 on the menu bar.
    If the window elapsed while a low battery had the hold suspended, the bar kept showing one second
    remaining indefinitely — and disagreed with the menu, which correctly showed the window as over.
    The readout was reusing the seconds-remaining value that the caffeinate respawn needs floored at
    1s; it now reads the deadline directly, like the in-menu row always did. The 1 Hz ticker that the
    stuck value kept alive now stops with it.
  • An externally killed caffeinate left a process-bound hold half-armed. Keep Awake correctly
    went off, but the process binding survived it — so restarting the app from the menu silently
    re-armed a hold the user had not asked for, and re-enabling from a tint row re-attached to the old
    process. The binding is now released with the intent, and the restart path forwards a bound pid
    only while Keep Awake is actually on.

release: v1.22.0 — freeze animation + honor Reduce Motion (R17)

Choose a tag to compare

@binlecode binlecode released this 27 Aug 20:02

Added

  • The animation can now hold still — and does so on its own when macOS asks. The app has always
    throttled itself under pressure and paused when hidden, but the one OS signal saying this user
    wants less motion — System Settings → Accessibility → Display → Reduce Motion — did nothing,
    and the only way to calm the icon was to quit. Now that setting freezes the animation live (no
    relaunch), and a manual Settings ▸ Freeze Animation toggle reaches the same machinery for
    screen-sharing or just a quieter menu bar. Frozen means frozen: the game loop stops entirely (zero
    redraws — cheaper than even the slowest animation) and the icon holds its current frame, no jump.
    Because the animation's speed is the readout, a frozen icon doesn't go silent: with the menu-bar
    label off, the label slot temporarily shows the live value instead (a custom label is respected,
    and your saved label choice is untouched — the menu says off (value while frozen) so nothing is
    invisible). The toggle survives a relaunch; the Reduce Motion half is re-read fresh each launch.
    While the OS setting holds, the menu row reads Freeze Animation — on via Reduce Motion and its
    checkmark keeps tracking your own toggle, which stays editable underneath. Menu-only, like the
    label position — no new CLI flag.
  • MENUBAR_LOAD_RUNNER_LOG_ANIMATION=1 (debug/test hook): prints the freeze gate, frame cursor,
    speed, and label-handoff state each 2s tick, so tests/qa.sh §3g can assert a frozen launch never
    animates — the sibling of the LOG_SLOTS/LOG_ASSERTIONS/LOG_AWAKE hooks, for the same
    no-TCC-grant reason.

release: v1.21.0 — build the update before the restart, not during it

Choose a tag to compare

@binlecode binlecode released this 27 Aug 20:02

Fixed

  • Restarting after a self-update left the menu bar empty for minutes. Reported as "after
    auto-upgrade, restart no longer works" — and the restart did in fact work, which is the whole
    problem: it just took 132 seconds during which nothing was on screen and nothing said why.
    Applying an update pulled the source only, leaving the compile to the launcher at restart, i.e.
    to the one window in which the app does not exist. The build now runs between the pull and the
    Restart offer, while the app is still up and animating, so the restart itself is about a second.
    The cost was bimodal, which is why this survived so long: against a warm clang module cache the
    deferred build was ~6.5s and looked fine, cold it was 32s, and under load on the day it was
    measured, 132s. The dropdown reports the new phase as Building vX.Y.Z… rather than sitting
    silent, and a build that fails is not fatal — the launcher still compiles at restart, and the
    alert says so instead of letting a long gap read as a failure.
  • A restart that never happened left no trace. The relaunch is handed to a detached shell that
    waits for the app's pid to disappear; its output went to /dev/null. When the 30s wait expired
    with the app still alive it ran the launcher anyway, the singleton guard declined, and that was
    the end of it — silently, in the one code path whose failure the app cannot report, because by
    then it has quit. Both the timeout and the launcher's reason now go to the log file.
  • The launcher compiled before checking the singleton guard. A duplicate launch paid for a full
    swiftc run only to be turned away, and during a long build — when no instance is up yet — it
    could start a second compile writing the same output as the one already in flight. The guard runs
    first now.
  • Rebuilding could corrupt a running instance. The build was written over the binary in place,
    which is unsafe for a process paging out of it; scripts/install-login-item.sh did exactly that
    on every reinstall. Builds now go to a temp and are renamed into place, so the live process keeps
    its own inode.

Added

  • --precompile on the launcher: build the binary if the source is newer, then exit without
    launching. Safe to run while an instance is live. This is the single place the build command
    lives — install.sh, scripts/install-login-item.sh, and the in-app updater all call it instead
    of carrying their own swiftc line, so the flags cannot drift out of agreement.

Internal

  • tests/qa.sh §6 covers --precompile building without launching, a live instance surviving a
    rebuild, and a rejected launch leaving the binary untouched; §2 now checks the launcher's own
    --help lists its own flags, which nothing did before.
  • tests/install-smoke.sh asserts that installing never starts the app, and cleans up any
    instance it finds under its sandbox rather than letting one outlive the directory it was
    installed into.

release: v1.20.1 — an idle die is a reading, not a missing one

Choose a tag to compare

@binlecode binlecode released this 27 Aug 20:02

Fixed

  • The temperature source could claim it had no reading while it was visibly driving the
    animation.
    Caught by hand in the release's own menu walk: the dashboard showed
    Temperature: warming up... on one row and Speed Multiplier (auto: Temperature): 1.74x two rows
    below it, after minutes of correct readings. The cause is a distinction 1.20.0 failed to make. A
    powered-down core cluster answers the sensor read with 0 rather than declining it, and those get
    filtered out so the menu doesn't print -4 °C; but on a tick where every cluster happens to be
    parked, the filter emptied the whole set and the reader reported that as missing data. It
    isn't — it is a reading of idle. Only a total read failure (the SMC going away mid-run) is
    missing data, and the two are now separate: an all-parked tick reports an idle load and the menu
    says Temperature: ≤30 °C · every core cluster parked rather than inventing a measurement.

    Rare — it did not recur in 45 sampled ticks or three targeted attempts to reproduce it — and
    cosmetic when it fired, since the animation simply held its last speed. Recorded in
    docs/ROADMAP.md § Verification debt, because the parked branch cannot be reached on demand: the
    only hook that would force it is one that changes a decision, which this repo's testing rules bar.

release: v1.20.0 — die temperature as an eighth load source

Choose a tag to compare

@binlecode binlecode released this 27 Aug 20:02

The eighth load source, and the first one that reads how hot the machine is rather than how busy.

Added

  • An eighth load source: die temperature (--load-source temperature, or pick Temperature
    from Other Sources in the menu). Where fan speed is the lagging thermal signal — fans trail
    the work that heated the machine by seconds — this is the leading one: the die responds in
    milliseconds. Read at the same unprivileged tier as every other source (no sudo, no
    entitlement), straight off the performance-core temperature sensors via the shared SMCClient
    that shipped in 1.19.4. The menu shows the hottest sensor, the spread across all of them, and how
    many answered; the menu-bar label shows TMP 72°. Machines where no sensor answers (typically
    VMs) disable the row and fall back to CPU, exactly like fan on a fanless Mac.

    Three things about it are deliberate and worth knowing. The animation follows the hottest
    sensor, not the average — throttling responds to the hottest die, and averaging a loaded core
    cluster against an idle one hides the event worth watching. The scale is absolute (30 °C idle
    → 100 °C flat out) rather than rescaled to your machine's recent range, so a given animation speed
    means the same temperature on every Mac and on every day; the cost is that a Mac which cools well
    never quite reaches the top of the range, which is the honest reading rather than a flattering
    one. And only ~12 cluster sensors are sampled per tick rather than all 102 the chip publishes —
    they carry the same maximum, and the full sweep would have cost about 1% of a core continuously,
    which is not a thing an indicator built to not add load gets to spend.

release: v1.19.4 — extract the SMC plumbing into a shared SMCClient

Choose a tag to compare

@binlecode binlecode released this 27 Aug 20:02

An internal release: the SMC plumbing moved out of the fan reader into a shared client. Nothing you
can see changed, and that is the whole acceptance criterion.

Fixed

  • The SMC connection is now shared, so a second sensor source can't open a second one. Reading
    fan RPM opens a connection to the System Management Controller, and that connection is never
    closed — the app holds it for its whole life and the kernel reclaims it at exit. That is fine for
    one. But the plumbing was private to the fan reader, so the next SMC-backed source (die
    temperature) would have had to duplicate it and open its own, leaving two permanently-open
    connections where one would do. The mechanism now lives in a single shared SMCClient, and the
    struct-layout safety check that disables the SMC path on an incompatible toolchain now gates
    every SMC source at once instead of only the reader holding its own copy of the check.

Unchanged, deliberately

  • Fan readings, the per-fan menu lines, availability on fanless Macs, and every CLI flag and
    environment variable. The fan label and menu rows were captured from the pre- and post-change
    builds back-to-back and are identical, at two hardware states (fans warm, and both fans stopped).