Windows Companion — Architecture & Audit Doc

apps/companion-win — Python tray helper that gives the Excel add-in COM access to Excel it otherwise can't have.

1. Overview

The Companion is a small, optional Windows-native helper process that runs alongside a locally-installed Excel and gives the Office.js task pane (apps/addin-excel) capabilities Office.js structurally cannot provide: enumerating other open workbooks, pulling their bytes so cross-workbook VLOOKUP/external references resolve, watching Application.SheetChange across every non-primary workbook while a recording is in progress, and driving "Provenance Spotlight" — bringing a source workbook's window forward during replay so a viewer can see where a referenced value actually came from. The add-in's own worksheets.onChanged subscription already records the primary workbook; the Companion is told which workbook to exclude so events are never double-counted.

It is written in Python (apps/companion-win/src/companion/) and talks to Excel exclusively through COM automation (pywin32's win32com.client), never by driving the UI. It exposes a tiny loopback-only HTTP JSON API on 127.0.0.1, port-scanned by the add-in in the range 38743–38747. It runs headless with a system-tray icon as its only visible surface (pystray, with a hand-rolled ctypes/Shell_NotifyIcon fallback if pystray/Pillow fail to import). There is no window, no console (built with --windows-console-mode=disable), and it is launched either from a Start-Menu shortcut, an HKCU Run autostart key, or via explorer.exe immediately after install.

Per CLAUDE.md's "never launch Excel" constraint, the Companion only attaches to an Excel that is already running (win32com.client.GetActiveObject("Excel.Application")) — it never starts a new Excel process, so if Excel isn't open, every workbook-dependent endpoint just reports excelDetected: false.

Build/deploy: the shipping artifact is a native-compiled binary produced by build-nuitka.ps1 (Nuitka, --standalone, Python → C → machine code, no shippable .pyc), named ExcelRewindCompanion.exe. build.ps1 (PyInstaller onefile) is kept only as a legacy fallback build path into a separate dist\ folder. The compiled .dist folder is staged into the unified NSIS installer (apps/installer/ExcelRewind-Setup.nsi, Section SEC_COMPANION), which also owns upgrade/uninstall shutdown sequencing (§4.4) and a per-user, non-admin autostart/shortcut path. There is also a standalone, source-based per-user installer, install-companion.ps1, that copies the exe to %LOCALAPPDATA%\ExcelRewind and optionally sets the HKCU Run key.

2. Architecture diagram

flowchart TB
  subgraph ENTRY["Entry points"]
    RUNPY["run_companion.py
(sys.path shim + main())"] MAIN["main.py
main()"] end subgraph GUARD["Single-instance guard (main.py)"] MUTEX["_acquire_single_instance()
Local named mutex"] PROBE["_another_companion_running()
GET /status on 38743-38747"] end subgraph COMLAYER["COM layer (excel_com.py)"] WORKER["ComWorker
STA thread + Queue, 2s timeout"] COMEXCEL["ComExcel
real backend: GetActiveObject only"] FAKEEXCEL["FakeExcel
in-memory test backend"] EVBUF["EventBuffer
(events.py) coalesce+cap"] SPANBUF["SpanBuffer
(events.py) spotlight spans"] end subgraph HTTPLAYER["HTTP server (server.py)"] BIND["bind_server()
first free port 38743-38747"] HANDLER["Handler(BaseHTTPRequestHandler)
routes /status /pair /workspace /file
/open /open-many /record/* /spotlight*"] COMPANION_STATE["Companion
shared state: paired, recording,
excel_cached, spotlight_active"] WATCHDOG["_watchdog thread
check_idle() every 2s"] end subgraph AUTHMOD["Auth (auth.py)"] AUTHCLS["Auth
token check() / pair()"] TOKENFILE["%APPDATA%\\ExcelRewind\\token.txt"] DIALOG["confirm_pairing_dialog()
native MessageBoxW Yes/No"] end subgraph TRAYMOD["Tray (tray.py)"] TRAYRUN["run() — pystray icon
Status / Quit menu"] TRAYFALLBACK["_run_ctypes()
Shell_NotifyIcon fallback"] end subgraph WINFOCUSMOD["winfocus.py"] FOCUS["bring_to_foreground()
find_window_by_title()"] end RUNPY --> MAIN MAIN --> PROBE --> MUTEX MAIN --> WORKER WORKER --> COMEXCEL COMEXCEL --> EVBUF COMEXCEL --> SPANBUF MAIN --> AUTHCLS AUTHCLS --> TOKENFILE AUTHCLS --> DIALOG COMEXCEL -.spotlight foreground.-> FOCUS MAIN --> COMPANION_STATE COMPANION_STATE --> WORKER COMPANION_STATE --> AUTHCLS MAIN --> BIND --> HANDLER HANDLER --> COMPANION_STATE MAIN --> WATCHDOG --> COMPANION_STATE MAIN --> TRAYRUN TRAYRUN -.import fails.-> TRAYFALLBACK TRAYRUN --> COMPANION_STATE FAKEEXCEL -.tests only.-> WORKER

Solid arrows = direct calls/composition inside the process. Dotted arrows = best-effort/optional paths (test-only backend, OS focus-steal workaround, tray import fallback).

3. Data-flow diagrams

3.1 Steady-state request flow (HTTP → COM → HTTP)

flowchart LR
  ADDIN["Excel add-in
(apps/addin-excel/src/companion.ts)"] -->|"HTTP/1.1 JSON, loopback,
Bearer token"| HANDLER["server.py Handler
do_GET / do_POST"] HANDLER -->|"lambda b: b.method(...)
queued on ComWorker"| WORKER["ComWorker._q (Queue)"] WORKER -->|"fn(backend) on STA thread"| COMEXCEL["ComExcel"] COMEXCEL -->|"GetActiveObject('Excel.Application')
Workbooks / SaveCopyAs / Range.Goto"| EXCEL["Running Excel.exe
(COM Application object)"] EXCEL -->|"SheetChange / WindowActivate /
SheetSelectionChange events (sync callback)"| SINK["_Events sink
(DispatchWithEvents)"] SINK -->|"cell {workbook,sheet,addr,value,formula}"| EVBUF["EventBuffer.add()"] SINK -->|"focus/select spans"| SPANBUF["SpanBuffer.focus()/select()"] EVBUF -->|"poll(since) -> {events[], dropped}"| HANDLER SPANBUF -->|"poll() -> spans[]"| HANDLER HANDLER -->|"JSON response"| ADDIN COMEXCEL -->|"bytes via SaveCopyAs(tmp) -> read -> delete tmp"| HANDLER HANDLER -->|"application/octet-stream"| ADDIN

3.2 Reference-workbook file movement

flowchart LR
  ADDIN["add-in: attached reference
workbook bytes (base64)"] -->|"POST /open or /open-many
{name, data_b64}"| HANDLER["server.py"] HANDLER -->|"base64.b64decode + 100MB cap"| BYTES["raw bytes"] BYTES -->|"_open_ref(name, data)"| COMEXCEL["ComExcel"] COMEXCEL -->|"write file"| REFDIR["%TEMP%\\ExcelRewind\\refs\\<safe-name>"] COMEXCEL -->|"unblock_file(): remove :Zone.Identifier ADS"| REFDIR COMEXCEL -->|"Workbooks.Open(path, ReadOnly=True)"| EXCEL["Excel.exe"] COMEXCEL -->|"maximize + refocus primary"| EXCEL COMEXCEL -->|"{opened, alreadyOpen} or per-file error"| HANDLER --> ADDIN

3.3 Persisted local state (no cloud storage — the module talks to no backend)

Files this module writes on disk
PathWritten byContents / formatLifetime
%APPDATA%\ExcelRewind\token.txtauth._load_or_create_token()32 hex-char pairing token (ASCII)Persists across restarts; one token per install, re-used by the add-in via localStorage key rewind.companion-token
%APPDATA%\ExcelRewind\companion-port.txtmain.main()ASCII decimal port numberRewritten every launch; optional discovery aid (the add-in primarily port-scans)
%TEMP%\ExcelRewind\refs\<name>excel_com._open_refRaw bytes of an attached reference workbook, under its real filenameLeft on disk (Excel has it open read-only); overwritten idempotently on next open of the same name
OS temp file (tempfile.mkstemp)ComExcel._save_copySaveCopyAs snapshot of an open workbook, up to 100 MBDeleted in a finally immediately after being read into memory and streamed to the add-in

The module never talks to Supabase, Stripe, or any cloud endpoint directly — its only network surface is the loopback HTTP server described in §5. All .rewind assembly, upload, and cloud sync happen in apps/addin-excel, not here; this module only ever produces raw cell-change JSON and workbook bytes over loopback.

4. Process / sequence diagrams

4.1 Startup, single-instance guard, and ghost-process prevention

sequenceDiagram
  participant OS as "Windows (shortcut/autostart/explorer.exe)"
  participant Main as "main.py main()"
  participant Probe as "_another_companion_running()"
  participant Mutex as "CreateMutexW"
  participant Worker as "ComWorker/ComExcel"
  participant HTTP as "bind_server()"
  participant Tray as "tray.run()"

  OS->>Main: launch ExcelRewindCompanion.exe
  Main->>Probe: GET /status on 38743..38747 (300ms each)
  alt a live companion (any version) answers with product marker
    Probe-->>Main: True
    Main->>OS: MessageBoxW "already running"
    Main->>Main: sys.exit(0)
  else nothing answers
    Main->>Mutex: CreateMutexW("Local\ExcelRewindCompanion.SingleInstance")
    alt ERROR_ALREADY_EXISTS
      Mutex-->>Main: already held
      Main->>OS: MessageBoxW "already running"
      Main->>Main: sys.exit(0)
    else acquired
      Main->>Worker: ComExcel() + ComWorker(backend) — spawns STA thread
      Main->>Main: Auth() loads/creates token.txt
      Main->>HTTP: bind_server() — first free port in range
      Main->>Main: spawn _watchdog thread (check_idle every 2s)
      Main->>Main: write companion-port.txt
      Main->>Main: register atexit + SIGINT/SIGTERM/SIGBREAK -> _shutdown
      Main->>Tray: run_tray(companion, port, _shutdown) — blocks main thread
      Note over Tray: pystray import/init failure -> _run_ctypes fallback,
total tray failure -> httpd.serve_forever() headless end end

4.2 Pairing handshake (first connection from the add-in)

sequenceDiagram
  participant Addin as "add-in companion.ts"
  participant Handler as "server.py Handler"
  participant Auth as "auth.py Auth"
  participant User as "Windows user"

  Addin->>Handler: scanPorts() — GET /status on each port/scheme
  Handler-->>Addin: 200 {product:"excelrewind-companion", version, excelDetected}
  Addin->>Handler: POST /pair (Origin: https://addin.excelrewind.com)
  Handler->>Handler: _origin_host_ok() — Origin allow-listed AND Host = loopback:port
  alt origin/host check fails
    Handler-->>Addin: 403 forbidden-origin
  else ok
    Handler->>Auth: pair(origin)
    alt a dialog is already open
      Auth-->>Handler: raise PairingBusy
      Handler-->>Addin: 409 pairing-in-progress
    else
      Auth->>User: MessageBoxW "Allow <origin> to connect to the Companion?"
(force-foregrounded via AttachThreadInput) alt user clicks Yes User-->>Auth: IDYES Auth-->>Handler: token (32 hex chars) Handler->>Handler: companion.mark_seen() — paired=True Handler-->>Addin: 200 {token} Addin->>Addin: localStorage.setItem('rewind.companion-token', token) else user clicks No / dialog times out User-->>Auth: IDNO / non-6 Auth-->>Handler: None Handler-->>Addin: 403 pairing-declined end end end

4.3 Cross-workbook recording: start → poll → stop

sequenceDiagram
  participant Addin as "add-in (records primary itself)"
  participant Handler as "server.py"
  participant Companion as "Companion (shared state)"
  participant Worker as "ComWorker (STA queue)"
  participant Com as "ComExcel"
  participant Excel as "Excel.exe (COM events)"

  Addin->>Handler: POST /record/start {exclude:"Primary.xlsx", captureChanges:true}
  Handler->>Companion: start_record(exclude, captureChanges)
  Companion->>Worker: submit(b.start_record)
  Worker->>Com: start_record(exclude, capture_changes)
  Com->>Excel: DispatchWithEvents(app, _Events) — hooks OnSheetChange/
OnWindowActivate/OnSheetSelectionChange Com-->>Handler: recording started (Application ref retained while recording) Handler-->>Addin: 200 {recording:true, exclude} loop every ~2-3s while recording Excel-->>Com: OnSheetChange(sh, target) [sync COM callback] Com->>Com: EventBuffer.add() per changed cell (coalesced 500ms/cell) Addin->>Handler: GET /record/events?since=<seq> Handler->>Companion: _touch_poll() (feeds R12 idle watchdog) Handler->>Worker: submit(b.poll_events(since)) Worker->>Com: PumpWaitingMessages() then buffer.poll(since) Com-->>Handler: {events:[...], dropped, spotlights:[...]} Handler-->>Addin: 200 JSON end alt add-in stops polling for >30s (R12) Companion->>Companion: _watchdog.check_idle() detects idle > RECORD_IDLE_TIMEOUT Companion->>Worker: submit(b.stop_record) — auto-stop else add-in explicitly stops Addin->>Handler: POST /record/stop Handler->>Companion: stop_record() Companion->>Worker: submit(b.stop_record) Worker->>Com: stop_record() — pump, close final span, drop sink + app ref Com-->>Handler: completed spotlight spans[] Handler-->>Addin: 200 {recording:false, spotlights:[...]} end

4.4 Companion upgrade / uninstall shutdown ladder (NSIS installer)

sequenceDiagram
  participant Installer as "ExcelRewind-Setup.nsi StopCompanion"
  participant PS as "hidden PowerShell"
  participant Handler as "server.py /shutdown"
  participant Main as "main._shutdown()"
  participant Worker as "ComWorker/ComExcel"

  Installer->>PS: Invoke-RestMethod POST http://127.0.0.1:<port>/shutdown
for port in 38743..38747 PS->>Handler: POST /shutdown (no bearer token, no Origin header) Handler->>Handler: _origin_host_ok() — Origin absent is OK, Host must be loopback:port alt origin present but not allow-listed Handler-->>PS: 403 forbidden-origin else ok (local CLI has no Origin) Handler-->>PS: 200 {stopping:true} (responds BEFORE tearing down) Handler->>Main: spawn thread -> sleep 0.3s -> shutdown_hook() Main->>Main: _shutdown(): stop_event.set(), httpd.shutdown() Main->>Worker: worker.close() — submit shutdown(), join STA thread Worker->>Worker: ComExcel.shutdown(): drop sink, release Application ref,
CoUninitialize() ON the STA thread end Installer->>Installer: Sleep 1500ms Installer->>Installer: taskkill /IM ExcelRewindCompanion.exe (graceful WM_CLOSE) Installer->>Installer: Sleep 1500ms Installer->>Installer: taskkill /F /IM ExcelRewindCompanion.exe (final fallback) Note over Installer,Worker: Per main.py, the COM ref is only held DURING active
recording, /shutdown makes even that case clean, and a
force-kill while idle already holds no ref (ghost-Excel fix).

4.5 Provenance Spotlight during replay

sequenceDiagram
  participant Viewer as "add-in replay (or web viewer, via add-in bridge)"
  participant Handler as "server.py"
  participant Com as "ComExcel"
  participant Excel as "Excel windows"
  participant Win as "winfocus.py"

  Viewer->>Handler: POST /spotlight {name, sheet, range}
  Handler->>Com: spotlight(name, sheet, rng) via ComWorker
  Com->>Com: capture current ActiveWorkbook.Name as _spotlight_prev (once)
  Com->>Excel: wb.Activate(), sheet.Activate(), app.Goto(range, True)
  Com->>Win: bring_to_foreground(hwnd) — AttachThreadInput dance
(defeats the foreground-lock timeout) alt named workbook is not open Com-->>Handler: raise FileNotFoundError Handler-->>Viewer: 404 not-open (replay continues without the freeze) else opened Com-->>Handler: restore-target name Handler-->>Viewer: 200 {spotlighted:true, restore} end Viewer->>Handler: POST /spotlight/clear (when the freeze dwell ends) Handler->>Com: clear_spotlight() Com->>Excel: re-Activate the pre-spotlight workbook Handler-->>Viewer: 200 {cleared:true, restored} Note over Handler: If the viewer crashes/closes mid-freeze and never clears,
the 2s watchdog auto-clears after SPOTLIGHT_IDLE_TIMEOUT=15s
so focus can never be stranded on the source workbook.

5. Interconnections

flowchart LR
  ADDIN["addin-excel
(apps/addin-excel/src/companion.ts)"] <-->|"loopback HTTP/1.1 JSON,
Bearer token, CORS-locked"| COMPANION["companion-win"] COMPANION -->|"COM: GetActiveObject,
Workbooks/Range/Application events"| EXCEL["Microsoft Excel
(local process, already running)"] COMPANION -->|"user32.dll: MessageBoxW,
SetForegroundWindow, Shell_NotifyIcon"| WINOS["Windows OS APIs"] INSTALLER["installer-deploy
(ExcelRewind-Setup.nsi)"] -->|"POST /shutdown (no auth),
then taskkill ladder"| COMPANION INSTALLER -->|"stages Nuitka .dist folder
into the installer payload"| COMPANION
External edges (protocol + purpose)
PeerDirectionProtocolPurpose
addin-excelin/outHTTP/1.1 over 127.0.0.1:38743-38747, JSON bodies, CORS restricted to https://addin.excelrewind.com and https://localhost:3000, Bearer-token auth on every endpoint except /status and /pairEnumerate/attach other open workbooks, stream cross-workbook cell-change events, save/attach workbook bytes, drive Provenance Spotlight during replay
Microsoft Excel (local process)out (attach only)COM automation via pywin32 (win32com.client.GetActiveObject("Excel.Application"), DispatchWithEvents for SheetChange/WindowActivate/SheetSelectionChange)Read workbook list/bytes, watch non-primary cell edits, open reference workbooks read-only, drive window focus/selection for Spotlight. Never launches Excel.
Windows OS (user32/shell32/kernel32 via ctypes)outWin32 API calls (no network)Native pairing/status MessageBoxW dialogs, focus-steal-guard workaround (AttachThreadInput + SetForegroundWindow), tray icon (pystrayShell_NotifyIcon), single-instance named mutex
installer-deploy (apps/installer/ExcelRewind-Setup.nsi)inHTTP POST /shutdown via hidden PowerShell Invoke-RestMethod (no auth token — gated by Origin/Host check instead), then taskkillClean COM teardown before overwriting the binary on upgrade, or on uninstall
Filesystem (%APPDATA%, %TEMP%)in/outLocal file I/O, no networkPersist pairing token, discovery port marker, staged reference-workbook copies (§3.3)

This module has no direct edge to Supabase, Stripe, or any Anthropic/Claude API — it is a pure local bridge between the add-in's web-view sandbox and the user's own running Excel process.

6. Key functions & files

FileFunction / exportWhat it doesWhy it matters
src/companion/main.py_acquire_single_instance(), _another_companion_running()Named-mutex + port-probe double guard against a second companion instanceThe historical field bug: a stacked launch each held a COM ref and left a ghost EXCEL.EXE that blocked the add-in cache from clearing. The port-probe belt exists because a pre-mutex (<v0.2.0) build has no mutex to see.
src/companion/main.pymain() / _shutdown()Wires backend→worker→auth→server→tray; registers atexit + SIGINT/SIGTERM/SIGBREAK handlers all funneling into one idempotent _shutdownEvery exit path (console close, signal, tray Quit, /shutdown POST) releases the COM ref exactly once; a bare taskkill /F is documented as safe only while idle (no ref held) — during active recording it would still leak.
src/companion/excel_com.pyComWorker (class)Single dedicated STA thread; HTTP handler threads submit() a callable and block up to 2s for a result box, else ExcelBusyExcel's COM apartment is single-threaded; this is the only thing standing between concurrent add-in requests and RPC_E_CALL_REJECTED. Every COM call in the process funnels through here.
src/companion/excel_com.pyComExcel._attach() / _idle_op() / _release_app()GetActiveObject-only attach with a liveness probe; releases the Application COM reference after every call unless recording is activeRoot-cause fix for the ghost-Excel bug: an idle companion holding zero COM refs means force-killing it can never orphan Excel. Documented explicitly in the source (lines 376-399).
src/companion/excel_com.py_install_sink(), OnSheetChange/OnWindowActivate/OnSheetSelectionChangeDispatchWithEvents sink hooked onto the Application; conditionally hooks the expensive OnSheetChange (reads every changed cell's formula+value) only when capture_changes (Record-all-workbooks) is requested — spotlight-only recording skips itThe COM sink executes synchronously inside Excel's UI thread; an exception here would crash into Excel, hence the blanket except Exception: pass in every handler. pump() (COM message pumping every ~50ms on the idle STA loop) exists specifically because without it the synchronous dispatch stalled Excel's UI — "painfully slow" field feedback.
src/companion/excel_com.py_safe_ref_name(), unblock_file()Strips path components to a bare filename (defeats ..\/absolute-path traversal); removes the NTFS Zone.Identifier ADS (Mark-of-the-Web) so Excel doesn't show Protected ViewSecurity boundary on untrusted name input from the add-in; the MOTW strip only ever touches a file this process itself just wrote under the user's own temp dir, never a downloaded file the user hasn't already trusted.
src/companion/excel_com.pyComExcel._retry()Retries a COM call up to 3× (200ms backoff) specifically on RPC_E_CALL_REJECTED (-2147418111)Bounds the "Excel is busy" surface — a modal dialog or in-progress paste in Excel rejects re-entrant COM calls; this absorbs transient rejections before the caller sees 503.
src/companion/excel_com.pyComExcel.spotlight() / clear_spotlight() / _activate()Brings a source workbook's window forward, activates a sheet/range, remembers the pre-spotlight active workbook, restores it on clearProvenance Spotlight Tier-2 UX; _spotlight_prev is a workbook name (string), not a COM ref, specifically so it survives the idle-release cycle safely.
src/companion/events.pyEventBufferPer-cell 500ms coalescing window + a 5000-event drop-oldest deque with a running dropped counter; monotonic seq for incremental pollingBounds memory under an edit flood (e.g. a huge paste) and lets the add-in poll with ?since=<seq> without re-fetching everything. Pure logic, unit-testable without Excel.
src/companion/events.pySpanBufferTracks focus/selection "spans" on non-primary workbooks (open span → close on next focus change or stop); drops sub-600ms flicker spans, merges re-entries to the same file/sheet/range within 2sRaw material for exact recorded Provenance Spotlight windows, including provenance of hand-typed (non-formula) values. A wandering author clicking through files doesn't spawn a spotlight storm.
src/companion/server.pyCompanion (class)Shared mutable state behind one lock: recording flag, last-poll timestamp, paired/connected liveness, cached excelDetected, spotlight-active timestampconnected() (12s window) vs paired (latched forever) is what makes the tray's Status box accurate; excel_cached() is what lets /ping answer without ever touching COM.
src/companion/server.pyCompanion.check_idle() / _watchdog()2s-tick watchdog: auto-stops recording after 30s with no /record/events poll (R12); auto-clears a stale spotlight after 15sThe two "can't strand the user" safety nets — a crashed/closed add-in pane can't leave Excel silently recording forever, nor leave focus stuck on a source workbook.
src/companion/server.pyHandler._origin_host_ok()CSRF guard for pre-auth POSTs (/pair, /shutdown): allow-lists Origin if present, always requires Host to equal the bound loopback portThese two endpoints intentionally require no bearer token (chicken-and-egg for pairing; no token available to the local uninstaller) so this is the entire defense against a malicious webpage hitting the loopback port.
src/companion/server.pybind_server()Binds the first free port in PORTS = [38743..38747]Defines the exact range the add-in port-scans (apps/addin-excel/src/companion.ts PORTS constant) — the two must stay in sync.
src/companion/auth.py_load_or_create_token()32-hex-char token, generated once with secrets.token_hex(16), persisted to token.txtThe entire bearer-auth scheme's root secret; loss/rotation means the add-in must re-pair.
src/companion/auth.pyAuth.pair() / _pair_lockNon-blocking lock around showing the native consent dialog; a concurrent /pair raises PairingBusy instead of stacking a second MessageBoxWFixes a real UX bug: ThreadingHTTPServer handles each POST on its own thread, so retried clicks used to stack blocking dialogs.
src/companion/auth.py_force_foreground(), confirm_pairing_dialog()Finds the modal by window class #32770 + caption and forces it to the OS foreground via the AttachThreadInput trickWorks around the Windows XP+ focus-steal guard that otherwise leaves a background process's MessageBoxW merely flashing in the taskbar.
src/companion/tray.pyrun() / _run_ctypes()Primary path: pystray icon with Status/Quit menu, 5s tooltip refresh. Fallback: hand-rolled Shell_NotifyIcon window if pystray/Pillow fail to import at runtimeThe exe is windowless/consoleless by design — the tray icon is the only "it's running" signal for the user, so both paths matter for field builds where a dependency didn't bundle correctly.
src/companion/winfocus.pybring_to_foreground(), find_window_by_title()Foreground-steal workaround (drop the lock timeout, AttachThreadInput, SetForegroundWindow); called only from Provenance Spotlight's window activation (excel_com.py's _activate())Despite the module's own docstring claiming it's shared with the pairing dialog, auth.py never imports winfocusconfirm_pairing_dialog() has its own separate, near-identical _force_foreground() implementation (see the auth.py row above). The same Windows quirk is solved twice, not once — doc corrected 2026-07-17 against the actual imports.
build-nuitka.ps1(script)Compiles run_companion.py with Nuitka --standalone into dist-nuitka\...\ExcelRewindCompanion.exe, forcing win32com/pythoncom/pystray/PIL as explicit includes (COM sub-packages are lazily imported and Nuitka's static analysis misses them)This is the shipping build since 2026-07-16 — no .py/.pyc ships, only compiled machine code (plus stdlib/dep DLLs, which are not proprietary code). build.ps1 (PyInstaller) is legacy fallback only.

7. Invariants & gotchas