ExcelRewind — Mac Companion (apps/companion-mac)

Architecture documentation — generated for the ExcelRewind architecture doc suite. Source-verified against the repository at commit range ending bf96420 (2026-07-17). Not marketing copy: every name below is a real file, function, RPC, or endpoint in the repo.

1. Overview 2. Architecture 3. Data flow 4. Sequences 5. Interconnections 6. Key functions & files 7. Invariants & gotchas 8. Testing

1. Overview

The Mac Companion is a small, local, loopback-only Python HTTP server that runs alongside Microsoft Excel for Mac and gives the ExcelRewind Excel add-in (apps/addin-excel, an Office.js task pane hosted in WKWebView) a way to see and briefly control the user's other open workbooks — something Office.js's sandboxed JavaScript API cannot do on its own. It is the macOS twin of apps/companion-win (a Windows COM-based service) and is deliberately written so its wire protocol is byte-for-byte identical: same 5-port range, same endpoint paths, same JSON shapes, same Bearer-token auth. The add-in's apps/addin-excel/src/companion.ts client works unmodified against either OS.

Where it runs: as a background macOS process with no Dock icon (LSUIElement in the app's Info.plist) and a small menu-bar item ("ExcelRewind Companion" via rumps) as its only visible UI. It is designed not to launch Excel — the read paths (list_workbooks(), save_copy(), spotlight()'s entry check, _find_wb()) explicitly gate their AppleScript on _excel_or_none() (which shells out to pgrep -x "Microsoft Excel") and no-op if Excel isn't running. open_ref()/open_refs() and _active_wb_name() do not re-check before their own tell application "Microsoft Excel" — see the Invariants section for why this is a real gap in the guarantee, not just a documentation nuance. It talks to Excel exclusively through AppleScript (shelling out to osascript), because there is no macOS COM equivalent; this replaces the Windows companion's win32com/COM automation of Excel.

Build/deploy shape: it's a plain Python package (src/companion/, no native extension) run from source during development (python run_companion.py), and packaged for distribution with py2app (setup.py, invoked by build.sh) into a universal (arm64+Intel) .app bundle when built with the system Python 3. For end users it ships as a single double-clickable .pkg installer (installer/build-installer.shinstaller/dist/ExcelRewind-Installer.pkg), whose postinstall script (installer/scripts/postinstall) installs the app to /Applications, sideloads the add-in manifest into Excel's wef folder, trusts the companion's self-signed loopback TLS certificate in the System keychain, and registers a per-user launchd LaunchAgent so the companion auto-starts at login. The build is currently unsigned — code-signing/notarization with an Apple Developer ID is an owner-side task not yet done (README §"Notarization / signing").

Current scope is a deliberate, owner-decided cut: "spotlight/provenance first." Pairing, /status, /workspace, /file, /open, /open-many, the Provenance Spotlight (/spotlight, /spotlight/clear), heartbeat, and shutdown are all fully real and mac-verified against a live Excel 365 for Mac session (2026-07-14 through 2026-07-15). Tier-2 record-all-workbooks is deferred/record/start, /record/events, and /record/stop are graceful no-op stubs that return the exact response shapes the add-in expects, so the add-in never errors against a Mac companion; it simply never receives cross-workbook change events on this platform yet.

2. Architecture diagram

Internal components, grouped by responsibility. Solid arrows are direct calls; the HTTP boundary (external, to the add-in pane) is drawn at the top.

flowchart TB
    PANE["Excel add-in pane
(WKWebView, companion.ts)"] subgraph ENTRY["Entry / process lifecycle"] RUN["run_companion.py
(source + py2app entry point)"] MAIN["main.py: main()
single-instance guard, wiring, signal handling"] end subgraph HTTP["HTTP layer — server.py"] BIND["bind_server() / serve()
ThreadingHTTPServer, ports 38743-38747"] COMP["class Companion
(shared state: paired, last_seen, spotlight flags)"] HANDLER["make_handler() -> Handler
(BaseHTTPRequestHandler routes)"] WATCHDOG["_watchdog()
2s tick -> check_idle()"] end subgraph SEC["Security"] AUTH["auth.py: class Auth
token load/create, pair(), check()"] DIALOG["confirm_pairing_dialog()
osascript display dialog"] TLSMOD["tls.py
ensure_cert(), wrap_socket()"] end subgraph WORK["Worker thread — worker.py"] WORKER["class Worker
submit(fn) -> queue -> 8s timeout -> ExcelBusy"] end subgraph BACKEND["Excel backend — excel_applescript.py"] BACKENDCLS["class ExcelAppleScript"] OSASCRIPT["_run_osascript()
subprocess -> osascript -e"] SPOT["spotlight() / clear_spotlight()
_activate() / _activate_script()"] CROSS["_apply_cross() / _remove_cross()
focus-cell cross-hair CF"] REFOPS["open_ref() / open_refs() / save_copy()"] STUBS["start_record() / poll_events() / stop_record()
(wired but unused — Tier-2 deferred)"] end subgraph EVT["events.py (buffers, unused in this build)"] EVTBUF["EventBuffer
coalesce + drop-oldest"] SPANBUF["SpanBuffer
focus-span capture"] end subgraph TRAY["tray_mac.py"] TRAYAPP["CompanionApp(rumps.App)
menu-bar Status / Quit"] end subgraph EXCEL["Microsoft Excel for Mac (external app)"] EXCELAPP["Excel process
(Apple Events target)"] end PANE -- "HTTPS loopback JSON
Bearer token" --> BIND BIND --> HANDLER HANDLER --> COMP HANDLER -- "unauthed: /pair only
(/status never touches Auth)" --> AUTH AUTH --> DIALOG HANDLER -- "authed calls" --> WORKER WORKER --> BACKENDCLS BACKENDCLS --> OSASCRIPT OSASCRIPT -- "osascript -e ..." --> EXCELAPP BACKENDCLS --> SPOT BACKENDCLS --> CROSS BACKENDCLS --> REFOPS BACKENDCLS --> STUBS STUBS --> EVTBUF STUBS --> SPANBUF WATCHDOG -- "companion.check_idle()" --> COMP WATCHDOG --> WORKER RUN --> MAIN MAIN --> BIND MAIN --> TLSMOD TLSMOD -- "wraps httpd.socket" --> BIND MAIN --> TRAYAPP TRAYAPP -- "reads status via worker.submit" --> WORKER

3. Data-flow diagram(s)

The companion never writes a .rewind file itself and never touches the user's original spreadsheet content in bulk — it moves small, purpose-specific payloads: JSON status/control messages, base64-encoded workbook copies (never the live original), and short AppleScript control scripts. Two flows matter most: (a) pulling a reference workbook's bytes out of Excel for cross-workbook resolution, and (b) pushing focus/selection commands back into Excel for the Provenance Spotlight.

flowchart LR
    subgraph ADDIN["apps/addin-excel (pane)"]
        CTS["companion.ts client"]
        REWIND[".rewind assembly
(packages/format, pane-side — NOT this module)"] end subgraph MAC["companion-mac"] EP["HTTP endpoints (server.py)"] WK["Worker (worker.py)"] BE["ExcelAppleScript (excel_applescript.py)"] DISK["Local files:
~/Library/Application Support/ExcelRewind/
token.txt, tls/*.pem, companion-port.txt
tempdir ExcelRewind/refs (opened ref copies)
~/Library/Containers/com.microsoft.Excel/Data/tmp (save_copy throwaway)"] end subgraph EXCEL["Microsoft Excel for Mac"] WB1["Primary / host workbook"] WB2["Reference workbook(s)"] end CTS -- "GET /workspace (Bearer)" --> EP EP -- "list_workbooks()
AppleScript: name/full name/saved per workbook" --> BE BE -- "osascript" --> WB1 BE -- "osascript" --> WB2 EP -- "JSON [{name,fullName,saved}]" --> CTS CTS -- "POST /open {name,data_b64}" --> EP EP -- "base64 decode -> bytes" --> WK WK -- "open_ref(name,data)" --> BE BE -- "write bytes to REF_DIR/<name>" --> DISK BE -- "osascript: open workbook ... read only true" --> WB2 EP -- "{opened,alreadyOpen}" --> CTS CTS -- "GET /file?name=Book2.xlsx" --> EP EP -- "save_copy(name)" --> BE BE -- "fast path: read saved file bytes from disk
OR live path: copy worksheet -> throwaway -> save workbook as (sandbox tmp) -> read -> delete" --> DISK EP -- "application/octet-stream bytes" --> CTS CTS --> REWIND CTS -- "POST /spotlight {name,sheet,range}" --> EP EP -- "set_spotlight -> worker.submit(spotlight)" --> WK WK -- "spotlight() -> _activate() -> _activate_script()" --> BE BE -- "osascript: activate, select range, cross-hair CF" --> WB2 EP -- "{spotlighted:true, restore:<prevWbName>}" --> CTS CTS -- "POST /spotlight/clear" --> EP EP -- "clear_spotlight()" --> BE BE -- "osascript: activate restore target, delete cross CF" --> WB1

4. Process / sequence diagrams

4.1 Pairing (first-connect trust handshake)

sequenceDiagram
    participant Pane as "Add-in pane (companion.ts)"
    participant Srv as "server.py Handler"
    participant Auth as "auth.py Auth"
    participant OS as "osascript (display dialog)"
    participant User as "macOS user"

    Pane->>Srv: GET /status (no auth)
    Srv->>Srv: excel_detected() via worker
    Srv-->>Pane: 200 {product, version, excelDetected}
    Pane->>Srv: POST /pair (no auth)
    Srv->>Auth: auth.pair()
    alt a dialog is already open
        Auth-->>Srv: raise PairingBusy
        Srv-->>Pane: 409 {error: pairing-in-progress}
    else no dialog open
        Auth->>OS: osascript -e 'display dialog ... buttons {Deny, Allow}'
        OS->>User: "Allow the ExcelRewind add-in to connect to the Companion?"
        alt user clicks Allow
            User-->>OS: button returned:Allow
            OS-->>Auth: returncode 0, stdout contains "Allow"
            Auth-->>Srv: token (32 hex chars)
            Srv->>Srv: companion.mark_seen() (paired=true)
            Srv-->>Pane: 200 {token}
        else user clicks Deny / dialog times out (120s)
            OS-->>Auth: returncode != 0 or no "Allow"
            Auth-->>Srv: None
            Srv-->>Pane: 403 {error: pairing-declined}
        end
    end
    Note over Pane,Srv: Every subsequent call sends
Authorization: Bearer <token>
checked with secrets.compare_digest

4.2 Provenance Spotlight during replay (primary runtime flow)

sequenceDiagram
    participant Pane as "Add-in pane"
    participant Srv as "server.py"
    participant Wk as "worker.py Worker"
    participant BE as "ExcelAppleScript"
    participant Excel as "Microsoft Excel for Mac"

    Pane->>Srv: POST /open-many {files:[{name,data_b64}, ...]}
    Srv->>Wk: submit(open_refs(items))
    Wk->>BE: open_refs(items)
    BE->>BE: host = _active_wb_name() (capture pre-open active workbook)
    loop each ref file
        BE->>BE: write bytes to REF_DIR/<safe name>
        BE->>Excel: osascript: ask to update links -> false, open workbook ... read only true, restore alert/link settings
        Excel-->>BE: workbook opened (or link-dialog suppressed)
        BE->>BE: _maximize(name)
    end
    BE->>Excel: osascript: activate object <host> (restore focus after batch)
    BE-->>Wk: [{name,opened,alreadyOpen}, ...]
    Wk-->>Srv: results
    Srv-->>Pane: 200 {results:[...]}

    Pane->>Srv: POST /spotlight {name,sheet,range}
    Srv->>Wk: submit(spotlight(name,sheet,rng))
    Wk->>BE: spotlight(name, sheet, rng)
    BE->>BE: prev = _active_wb_name(), anchor _spotlight_prev (only if not already anchored)
    BE->>Excel: osascript _activate_script: activate, activate object workbook, activate object sheet,
delay 0.4s, select cross-hair sweep, delay 0.35s, select target_range, activate object target_range Excel-->>BE: workbook/sheet/range now frontmost + selected BE->>BE: _apply_cross(wb,sheet,rng) — only if wb is a companion-opened REF_DIR file BE->>Excel: osascript: make new format condition (formula =OR(ROW()=r,COLUMN()=c)) BE-->>Wk: restore target name Wk-->>Srv: restore Srv-->>Pane: 200 {spotlighted:true, restore} Note over Pane,Srv: repeat POST /spotlight for each cue in a streak,
_spotlight_prev is NOT overwritten while still anchored,
so a multi-cue streak always restores to the true origin Pane->>Srv: POST /spotlight/clear Srv->>Wk: submit(clear_spotlight()) Wk->>BE: clear_spotlight() BE->>Excel: osascript: delete cross-hair format condition (signature-guarded) BE->>Excel: osascript: activate object <_spotlight_prev> BE-->>Wk: restored name (or None if origin workbook was closed) Wk-->>Srv: restored Srv-->>Pane: 200 {cleared:true, restored} Note over Srv: Watchdog (_watchdog, 2s tick) auto-fires clear_spotlight
if the pane never clears within SPOTLIGHT_IDLE_TIMEOUT=15s

4.3 Cross-workbook file resolution — save_copy fast vs. live path

sequenceDiagram
    participant Pane as "Add-in pane"
    participant Srv as "server.py"
    participant BE as "ExcelAppleScript"
    participant FS as "Disk"
    participant Excel as "Microsoft Excel for Mac"

    Pane->>Srv: GET /file?name=Book2.xlsx
    Srv->>BE: save_copy("Book2.xlsx") (via worker)
    BE->>BE: wb = _find_wb(name) — by-index workbook loop (Mac cannot use "repeat with w in workbooks")
    alt workbook not open
        BE-->>Srv: raise FileNotFoundError
        Srv-->>Pane: 404 {reason: not-open}
    else workbook found and saved==true (fast path)
        BE->>FS: read bytes directly from the workbook's on-disk path
        FS-->>BE: bytes
        BE-->>Srv: (data, filename)
        Srv-->>Pane: 200 octet-stream (exact on-disk bytes)
    else workbook unsaved / has pending changes (live path)
        BE->>Excel: osascript: copy worksheet (sheet 1) -> new throwaway workbook,
loop copy worksheet 2..N after last sheet of throwaway BE->>Excel: osascript: save workbook as <throwaway> filename <EXCEL_CONTAINER_TMP/...> Excel-->>BE: throwaway saved inside Excel's sandbox container BE->>Excel: osascript: close workbook <throwaway> saving no BE->>FS: read throwaway bytes, then delete throwaway + Excel's "~$" owner file alt live path fails mid-way BE->>Excel: best-effort: close throwaway workbook saving no BE-->>Srv: re-raise original exception Srv-->>Pane: 500 {reason: save-failed, detail} else success BE-->>Srv: (data, filename) Srv-->>Pane: 200 octet-stream end end Note over BE: chart sheets + VBA are NOT carried by "copy worksheet"
(values/formulas/formats only) — acceptable for ref resolution,
not full-fidelity duplication

4.4 Install / upgrade via the .pkg installer

sequenceDiagram
    participant User as "End user"
    participant Installer as "macOS Installer.app"
    participant Post as "installer/scripts/postinstall (root)"
    participant AsUser as "console user (sudo -u)"
    participant KC as "System Keychain"
    participant LD as "launchd"
    participant App as "ExcelRewind Companion.app"

    User->>Installer: double-click ExcelRewind-Installer.pkg -> Continue -> enter admin password
    Installer->>Post: run postinstall script (as root, $3=target volume)
    Post->>Post: CONSOLE_USER = stat -f%Su /dev/console (identify the real logged-in user)
    Post->>AsUser: mkdir wef folder, cp rewind-manifest.xml into
~/Library/Containers/com.microsoft.Excel/Data/Documents/wef Note right of AsUser: sideloads the add-in manifest — Excel now shows
ExcelRewind under Insert -> My Add-ins -> Developer Add-ins Post->>LD: launchctl bootout gui/<uid>/com.excelrewind.companion (stop any prior instance — idempotent upgrade) Post->>AsUser: pkill -TERM "ExcelRewind Companion.app/Contents/MacOS" (graceful stop, SIGTERM-safe since 0.1.1) Post->>AsUser: APP_BIN --ensure-cert (as the console user) AsUser->>App: tls.ensure_cert() generates companion-cert.pem / companion-key.pem if missing (0600, ~/Library/Application Support/ExcelRewind/tls) App-->>Post: prints cert path to stdout Post->>KC: security add-trusted-cert -d -r trustRoot -p ssl -k /Library/Keychains/System.keychain <cert> KC-->>Post: cert trusted for SSL, scoped to 127.0.0.1/localhost only Post->>AsUser: write ~/Library/LaunchAgents/com.excelrewind.companion.plist (RunAtLoad=true, KeepAlive=false) Post->>LD: launchctl bootstrap gui/<uid> <plist> alt bootstrap fails (already bootstrapped / older macOS) Post->>LD: launchctl kickstart gui/<uid>/com.excelrewind.companion Post->>AsUser: fallback: open "ExcelRewind Companion.app" end LD->>App: launch ExcelRewind Companion.app/Contents/MacOS/ExcelRewind Companion App->>App: main() single-instance check, bind_server, tls.wrap_socket, start tray (rumps, main thread) Note over App: On the FIRST AppleScript call to Excel, macOS separately prompts
"ExcelRewind Companion wants to control Microsoft Excel" (Automation/TCC) — the user must click OK

4.5 Failure paths: pairing collision, Excel-busy timeout, and shutdown CSRF guard

sequenceDiagram
    participant Pane as "Add-in pane"
    participant Srv as "server.py"
    participant Wk as "Worker"
    participant BE as "ExcelAppleScript"
    participant Page as "Malicious/other web page"

    Pane->>Srv: POST /pair
    Note right of Srv: a second /pair arrives while the first dialog is still open
    Pane->>Srv: POST /pair (concurrent)
    Srv->>Srv: auth.pair() -> _pair_lock.acquire(blocking=False) fails
    Srv-->>Pane: 409 {error: pairing-in-progress}

    Pane->>Srv: POST /open {name,data_b64}
    Srv->>Wk: submit(open_ref)
    Wk->>BE: osascript open workbook ...
    Note right of BE: Excel is showing a modal (e.g. an unrelated dialog) and osascript hangs
    Note right of Wk: Worker.submit()'s own wait is a FIXED 8s (set once in main.py's
Worker(backend)) - independent of open_ref's own 30s osascript timeout,
so the 8s caller-facing timeout fires FIRST no matter which endpoint this is Wk--xSrv: box.wait(8.0) elapses -> raise ExcelBusy Srv-->>Pane: 503 {reason: excel-busy} Note over BE: the worker thread is still stuck inside osascript for up to ~22
more seconds (open_ref's internal timeout=30s) - a second request queues
behind it and can ALSO 503 at its own +8s before the first ever finishes BE--xWk: subprocess.TimeoutExpired at 30s (open_ref) / 8s (default elsewhere) - too late, caller already got its 503 Page->>Srv: POST /shutdown (cross-origin fetch, Origin: https://evil.example) Srv->>Srv: origin not in ALLOWED_ORIGINS Srv-->>Page: 403 {error: forbidden-origin} Note over Srv: same-machine curl/CLI/uninstaller send NO Origin header -> allowed through unchanged

5. Interconnections

The companion is intentionally isolated: it has no network calls beyond 127.0.0.1 and never talks to Supabase, Stripe, or any ExcelRewind backend directly. All of its external edges are either the local Excel application (via AppleScript) or local OS services (Keychain, launchd, filesystem).

flowchart LR
    ADDIN["apps/addin-excel
(Office.js pane, WKWebView)"] MAC["apps/companion-mac"] EXCELAPP["Microsoft Excel for Mac
(external application)"] KEYCHAIN["macOS System Keychain"] LAUNCHD["macOS launchd
(per-user LaunchAgent)"] FS["Local filesystem
~/Library/Application Support/ExcelRewind
+ Excel sandbox container tmp"] INSTALLER["apps/companion-mac/installer
(.pkg build + postinstall)"] ADDIN <-- "HTTPS loopback, JSON,
Bearer token, ports 38743-38747" --> MAC MAC -- "osascript / AppleScript
(Apple Events, TCC-gated)" --> EXCELAPP MAC -- "read/write token.txt, tls/*.pem,
companion-port.txt, refs/*" --> FS INSTALLER -- "security add-trusted-cert
(root, SSL-only, 127.0.0.1/localhost SAN)" --> KEYCHAIN INSTALLER -- "sideloads rewind-manifest.xml
into Excel's wef folder" --> EXCELAPP INSTALLER -- "writes + bootstraps LaunchAgent plist" --> LAUNCHD LAUNCHD -- "RunAtLoad, auto-start" --> MAC
External interconnections, protocol, purpose
PeerDirectionProtocol / mechanismPurpose
apps/addin-excel pane (companion.ts)in + outHTTPS loopback REST, JSON bodies, Authorization: Bearer <token> on all but /status//pair; CORS locked to https://addin.excelrewind.com and https://localhost:3000Pairing, workspace listing, cross-workbook ref open/fetch, Provenance Spotlight control, heartbeat, clean shutdown
Microsoft Excel for Mac (application)out onlyosascript -e <script> subprocess per call — Apple Events; requires the user to grant Automation ("control Microsoft Excel") permission on first useList open workbooks, save/copy workbook bytes, open reference workbooks, activate/select ranges for spotlighting, apply/remove the focus-cell cross-hair conditional format
macOS System Keychainout (installer only)security add-trusted-cert -d -r trustRoot -p ssl -k /Library/Keychains/System.keychain, run as root by the pkg postinstallTrust the companion's self-signed loopback TLS cert (SAN-bound to 127.0.0.1/localhost) so WKWebView's mixed-content check allows the pane's https fetch to the companion
macOS launchdout (installer + main.py signal handling)Per-user LaunchAgent plist (~/Library/LaunchAgents/com.excelrewind.companion.plist), launchctl bootstrap/bootout/kickstart; the running process registers SIGTERM/SIGINT handlers for clean bootoutAuto-start the companion at login; clean stop/restart on upgrade or uninstall
Local filesystemin + outPlain file I/O under ~/Library/Application Support/ExcelRewind/ (token, TLS keypair, port marker), tempdir()/ExcelRewindCompanion.lock (single-instance PID lock), tempdir()/ExcelRewind/refs (opened reference copies), ~/Library/Containers/com.microsoft.Excel/Data/tmp (sandbox-writable throwaway for save_copy's live path)Persist pairing token + TLS cert across restarts, discover the live port, stage reference workbook bytes for Excel to open, work around Excel's App Sandbox write restriction
apps/addin-excel/public/manifest.prod.xmlin (build-time, installer only)File copy: installer/build-installer.sh packages this file as rewind-manifest.xml into the pkg's Scripts payload; postinstall copies it into Excel's wef folderSideload the Excel add-in with zero Terminal steps as part of the same installer that installs the companion
Supabase / Stripe / any ExcelRewind backendnoneNot contacted. The companion has no outbound network calls beyond 127.0.0.1; all auth/billing/licence logic lives in the pane and backend, never here.

6. Key functions & files

Load-bearing files/functions — including non-obvious jobs
FileFunction / exportWhat it doesWhy it matters
run_companion.pymodule entryInserts src/ onto sys.path then calls companion.main.main(); used both as python run_companion.py and as py2app's APP targetSame file must work unfrozen (dev) and frozen (packaged) — the sys.path shim is what makes that true
src/companion/main.pymain()Single-instance guard (port probe + PID lockfile), wires ExcelAppleScript+Worker+Auth into a Companion, binds the server, wraps it in TLS if a cert exists, writes the port marker file, installs signal handlers, then blocks on tray_mac.run()The whole process's composition root; also the only place that decides http vs. https at runtime
main.py_another_companion_running()Probes all 5 ports over both https and http for a live /status containing "excelrewind-companion" before even trying the PID lockfileCatches a second launch even if it predates the lockfile guard (upgrade-safety belt-and-braces)
main.py_on_signal()SIGTERM/SIGINT handler that tears down then calls os._exit(0)Without the explicit exit, rumps's NSApplication run loop survives the handler and the process lingers until SIGKILL — observed live 2026-07-14; launchctl bootout depends on this for clean upgrades
src/companion/server.pyclass CompanionShared server state: worker/auth handles, current TLS scheme, paired/connected flags, spotlight-active timestampSingle source of truth the tray's Status dialog and the watchdog both read
server.pyHandler._handle_shutdown()Rejects POST /shutdown if it carries an Origin header not in ALLOWED_ORIGINS; otherwise responds 200 first, tears down 0.3s later on a daemon threadThe only CSRF-relevant endpoint (no-auth by design, since a local tool needs to be killable); this is the guard against a malicious page cross-origin-POSTing it
server.py_handle_record_start_stub / _handle_events_stub / _handle_record_stop_stubReturn the exact response shapes companion-win's real endpoints return, but do nothing and record nothingTier-2 boundary: this is precisely where "record-all-workbooks" would get wired up when built — the add-in already tolerates it being a no-op
src/companion/worker.pyclass Worker, submit(fn)One dedicated thread pulls (fn, box) off a queue and runs fn(backend); caller blocks up to timeout (default 8s) then gets ExcelBusySerializes every AppleScript call so concurrent osascript processes never race each other; gives server.py an OS-agnostic contract identical to Windows' COM/STA worker
src/companion/auth.py_load_or_create_token(), Auth.check()32-hex-char token persisted at ~/Library/Application Support/ExcelRewind/token.txt; compared with secrets.compare_digestConstant-time compare avoids a timing side-channel on the loopback bearer token
auth.pyconfirm_pairing_dialog()osascript -e 'display dialog ... buttons {"Deny","Allow"}', 120s timeout; refuses (rather than silently granting) if osascript is missing or the call errorsThe macOS-native equivalent of Windows' MessageBoxW pairing consent — fail-closed by design
src/companion/excel_applescript.pyExcelAppleScript.list_workbooks()Iterates workbooks by index (repeat with i from 1 to (count of workbooks))Documented mac-verified gotcha: repeat with w in workbooks and in (every workbook) both fail with AppleScript error -50 on Excel for Mac
excel_applescript.pysave_copy(name)Fast path reads on-disk bytes directly for a saved workbook; live path copies every worksheet into a throwaway workbook and saves that into EXCEL_CONTAINER_TMP (Excel's own sandbox-writable tmp)Mac has no SaveCopyAs; save workbook as on the real workbook has SaveAs semantics (would retarget/corrupt the open file) and the App Sandbox rejects writes outside Excel's container — this is the workaround for both
excel_applescript.pyopen_ref(name, data)Writes bytes under REF_DIR/<safe name>, opens via open workbook workbook file name <path> read only true, then re-verifies the workbook actually appearedPlain open "path" intermittently exits 0 while opening nothing (mac-verified); the re-verify step turns that silent failure into a real error instead of a phantom success
excel_applescript.pyopen_ref same-name collision checkIf a workbook of the same name is already open, distinguishes "our real ref" from an unsaved blank workbook (e.g. default Book1) by checking for a disk path via _posixifyField bug fixed 2026-07-15: treating a blank Book1 as "ref already open" silently spotlit an empty workbook
excel_applescript.pyopen_refs(items)Captures the active workbook before opening any refs, opens each (per-file error isolation), then re-activates the original hostWithout the restore, the last-opened ref stayed focused and the first spotlight lost its restore anchor (field bug 2026-07-15)
excel_applescript.py_activate_script(wb_name, sheet, rng)Pure function building the AppleScript for the Provenance Spotlight; uses the select verb (not set selection to) because the latter is deferred/non-synchronous on Excel-for-Mac and races the next apple-eventRoot-caused, agent-proven field bug (2026-07-15): range cues like $B:$J silently never showed because the prior "set selection to" line hadn't committed before the next script line ran
excel_applescript.pyspotlight(name, sheet, rng)Anchors _spotlight_prev only while still pointing at the workbook being left, so a streak of cues without an intervening clear always restores to the true pre-streak originMulti-cue Tier-2-style replays depend on this not clobbering the restore target on the second/third cue
excel_applescript.py_apply_cross / _remove_cross / _is_companion_refAdds/removes a conditional-format "cross-hair" (=OR(ROW()=r,COLUMN()=c)) on the spotlighted sheet, ONLY when the workbook is one the companion itself opened from REF_DIR; removal is guarded by checking the formula prefix before deletingDirect enforcement of "never modify the original spreadsheet" at the code level — the safety check is path.startswith(REF_DIR + os.sep), and deletion won't touch a conditional format the source file shipped with
excel_applescript.py_posixify(p)Pure: maps Excel's full name string to a POSIX path, or None if the workbook was never savedThe load-bearing primitive behind both the blank-workbook collision guard and the fast-vs-live save_copy path decision; unit-tested without osascript
src/companion/tls.pyensure_cert(), wrap_socket(httpd)Generates a per-machine self-signed leaf (SAN = 127.0.0.1 + localhost, RSA-2048, 800-day validity) via the system openssl, then wraps the bound HTTP server's socket in TLSExistence of this module IS the fix for a whole class of "pane can't see companion" reports on Mac — see §7
src/companion/tray_mac.pyrun(companion, port, on_quit)Builds a rumps.App with Status/Quit menu items; starts a 1s rumps.Timer heartbeat purely so pending Python signal handlers get a chance to runrumps blocks the main thread inside the ObjC run loop, during which CPython executes no main-thread bytecode — without the heartbeat, SIGTERM never actually triggers Python's handler (observed live 2026-07-14)
src/companion/events.pyEventBuffer, SpanBufferCoalescing event buffer and focus-span buffer, reused verbatim from companion-win/events.pyCurrently dead weight at runtime (Tier-2 stubs never feed them) but is the exact shape a future Mac record-all-workbooks implementation would need — kept intentionally instead of deleted
installer/scripts/postinstallshell scriptRoot-run, per-user setup: sideload manifest, stop old instance, generate+trust TLS cert, write+bootstrap LaunchAgentIdempotent by construction (re-run = repair/upgrade) — this is the entire "why does clicking the pkg twice not break anything" story
setup.pypy2app OPTIONS plistSets LSUIElement: True (no Dock icon) and NSAppleEventsUsageDescription (required text for the Automation permission prompt on a bundled, non-Terminal app)Without the usage string, a packaged app's Apple Events to Excel can be denied silently instead of prompting — this is the difference between "user sees a permission dialog" and "companion silently never works"

7. Invariants & gotchas

Never modify the original spreadsheet. The companion enforces this at the code level, not just by policy: the focus-cell cross-hair conditional format (_apply_cross) is applied only when _is_companion_ref confirms the workbook's on-disk path lives under the companion's own REF_DIR temp directory — a user-opened original never qualifies. save_copy's live path copies sheets into a disposable throwaway workbook rather than ever calling Mac's SaveAs-semantics save workbook as on the real open workbook.
Loopback must be HTTPS on Mac, unlike Windows. WKWebView (which hosts the Excel-for-Mac task pane) applies mixed-content blocking to an https-origin page's fetch to plain http://127.0.0.1 — silently, with zero packets sent (mac-verified with a signed WKWebView probe, 2026-07-15). Chromium/WebView2 on Windows exempts loopback from this rule; WebKit does not. This is why tls.py exists at all, and why the server falls back to plain http only when no cert pair is present (dev-only path — the pane cannot connect over it).
Companion is designed not to launch Excel — but the guard is not applied on every path. list_workbooks(), save_copy(), spotlight()'s entry check, and _find_wb() all explicitly call _excel_or_none() (a promptless pgrep -x "Microsoft Excel") and return an empty/None result before touching AppleScript. However open_ref(), open_refs(), _maximize(), and _active_wb_name() issue their own tell application "Microsoft Excel" block with no such check — on real macOS, addressing an app via tell application launches it if it isn't running. In practice this is inert only because these paths are reachable exclusively from the Office.js task pane, which cannot exist unless Excel is already running the add-in — there is no code-level enforcement of the "never launches Excel" invariant on this subset of calls, contradicting the module's own docstring claim ("every script ... only if it's already running"). This mirrors companion-win's "attach only" rule (R2 in the design notes) in intent, and is also why Excel detection is cheap enough to poll every 5s from the tray — but the mac backend's implementation of that intent is incomplete.

8. Testing

Two tiers, both under tests/. test_server.py is the everyday suite: a FakeExcel stand-in matches ExcelAppleScript's public method surface so server.py/Companion/Worker/auth.py/events.py and the pure helpers (_posixify, _activate_script, _cross_formula, _parse_workbook_lines, _version_ge, etc.) are exercised with no real Excel/osascript — safe on any CI machine including non-Mac. e2e_live_excel.py and e2e_focus_lifecycle.py are LIVE gates: they boot the real ExcelAppleScript backend and drive the actual HTTP surface against a running Excel 365 for Mac session (macOS-only, require Excel open; skip cleanly otherwise). e2e_focus_lifecycle.py in particular is the regression gate for the 0.3.2 field bugs — it asserts name of active workbook through the full open → spotlight → clear → streak → collision lifecycle against a real link-bearing reference file. All three are plain-assert scripts (no pytest dependency required, though pytest can collect them) — see file docstrings for exact run commands.