Top-level view across all 12 modules · monorepo C:\Users\TM\Documents\claude proj\fable pqrs · built from verified per-module interface edges, cross-checked against package.json deploy scripts and repo source
Every arrow below is a real interface edge reported by a module's own documentation (see the per-module pages linked from index.html). Edges are deduplicated where two modules describe the same wire call from both sides (e.g. addin-excel’s companion loopback call and companion-win’s response). Dashed arrows are compile-time ESM imports of packages/format/packages/branding (no network I/O); solid arrows are runtime network/IPC calls, labelled with protocol and payload.
flowchart TB
subgraph SHARED["Shared library (in-process import only)"]
FMT["packages/format
+ packages/branding"]
end
subgraph DESKTOP["Excel / Google Sheets Desktop"]
ADDIN["apps/addin-excel
(Office.js task pane, React+TS)"]
OFFICE["Office.js / Excel JS API
(in-process)"]
CWIN["apps/companion-win
(Python tray, COM via pywin32)"]
CMAC["apps/companion-mac
(Python, AppleScript/osascript)"]
SHEETS["apps/addon-sheets
(Apps Script sidebar)"]
GAS["Apps Script server
(Code.js, poll-only)"]
end
subgraph WEB["Browser clients (static, Cloudflare Pages)"]
VIEWER["apps/viewer
(zero-backend replay SPA)"]
ADMINAPP["apps/admin
(superadmin console)"]
TEAM["apps/team
(org/account console)"]
WEBSITE["apps/website
(marketing site)"]
end
subgraph SUPA["Supabase backend — no application server"]
BDATA["backend-data
Postgres (RLS + SECURITY DEFINER RPCs) + Storage"]
BSVC["backend-services
8 Deno Edge Functions"]
end
subgraph EXT["External services"]
STRIPE["Stripe"]
RAZOR["Razorpay (written, not live)"]
RESEND["Resend"]
GOOG["Google / Apple OAuth"]
DNS["Domain DNS (TXT records)"]
end
subgraph DEPLOY["Deployment"]
CFP["Cloudflare Pages
(5 projects, --branch master)"]
INSTALL["Windows NSIS installer +
Mac .pkg installer"]
end
%% ---- compile-time imports ----
FMT -.->|"ESM import: writeRewind/readRewind, crypto, replayTo, branch.ts"| ADDIN
FMT -.->|"ESM import: readRewind(limited:true), replayTo, spotlightCues"| VIEWER
FMT -.->|"ESM import: keyring.ts key-row lifecycle"| TEAM
FMT -.->|"ESM import, bundled into dist/sidebar.html"| SHEETS
FMT -.->|"BUILD_TAG grep post-deploy"| INSTALL
FMT -.->|"KeyRowFields schema-shape mirror only"| BDATA
%% ---- addin-excel runtime edges ----
ADDIN -->|"Office.js: onChanged/onCalculated/onFormatChanged, getFileAsync"| OFFICE
ADDIN -->|"HTTPS supabase-js: signInWithOtp/verifyOtp/OAuth"| BSVC
ADDIN -->|"PostgREST SELECT/INSERT (JWT, RLS): profiles/org_keys/user_keys/usage_events"| BDATA
ADDIN -->|"RPC: register_device, effective_features, exam_*, claim_handoff"| BSVC
ADDIN -->|"POST /functions/v1/exam-storage (Bearer access token)"| BSVC
ADDIN -->|"Storage upload bug-reports/<uid>/"| BDATA
ADDIN -->|"window.open google-signin.html (forced system browser)"| VIEWER
ADDIN -->|"HTTP(S) loopback 127.0.0.1:38743-47, Bearer token"| CWIN
ADDIN -->|"HTTPS loopback 127.0.0.1:38743-47, self-signed cert"| CMAC
ADDIN -->|"window.open Stripe Payment Link (system browser)"| STRIPE
ADDIN -->|"window.open /companion/ installer landing page"| WEBSITE
CWIN -->|"JSON: workbook list, coalesced cell-change events, file bytes"| ADDIN
CMAC -->|"JSON: workbook list, spotlight spans, file bytes"| ADDIN
CWIN -->|"COM: GetActiveObject('Excel.Application'), attach-only"| OFFICE
CMAC -->|"AppleScript/osascript Apple Events (TCC-gated)"| OFFICE
%% ---- addon-sheets ----
SHEETS -->|"google.script.run.rewindPoll() every ~2s"| GAS
SHEETS -->|"HTTPS supabase-js: OTP auth, profiles/app_config read, usage_events insert"| BDATA
SHEETS -->|"out-of-band link to /privacy.html"| VIEWER
%% ---- viewer ----
VIEWER -->|"PostgREST insert viewer_events (anon key, keepalive)"| BDATA
VIEWER -->|"google-signin.html: PKCE OAuth exchange + rpc stash_handoff"| BSVC
VIEWER -->|"OAuth2/PKCE redirect (google-signin.html only)"| GOOG
VIEWER -->|"outbound <a href> install.html?utm_*"| ADDIN
%% ---- admin ----
ADMINAPP -->|"GET /functions/v1/metrics?days=N, Bearer OWNER_METRICS_TOKEN"| BSVC
ADMINAPP -->|"POST /functions/v1/admin-ops {action}, Bearer + x-superadmin-jwt"| BSVC
ADMINAPP -->|"supabase-js: OTP auth (superadmin email) + app_config read"| BDATA
%% ---- team ----
TEAM -->|"RPC: org_*, cohort_*, exam_*, effective_features"| BDATA
TEAM -->|"POST send-invite / verify-domain / exam-storage (forwarded JWT)"| BSVC
TEAM -->|"window.open exam .rewind handoff"| VIEWER
TEAM -->|"Stripe Payment Links / billing portal"| STRIPE
TEAM -.->|"reads entitlements admin approved (effective_features)"| ADMINAPP
%% ---- website ----
WEBSITE -->|"REST POST rpc/public_tiers, public_promo_config, get_my_org (anon key)"| BDATA
%% ---- backend internals ----
BSVC -->|"service-role supabase-js: RPC + table read/write, bypasses RLS"| BDATA
BDATA -->|"RPC JSON responses (effective_features, exam config, etc.)"| ADDIN
%% ---- billing ----
STRIPE -->|"HTTPS webhook, stripe-signature verified (constructEventAsync)"| BSVC
RAZOR -->|"HTTPS webhook, x-razorpay-signature HMAC-SHA256"| BSVC
BSVC -->|"POST api.resend.com/emails (send-invite)"| RESEND
BSVC -->|"Deno.resolveDns TXT lookup (verify-domain)"| DNS
%% ---- deployment ----
CFP -->|"serves static dist/"| ADDIN
CFP -->|"serves static dist/"| VIEWER
CFP -->|"serves static dist/"| ADMINAPP
CFP -->|"serves static dist/"| TEAM
CFP -->|"serves static dist/"| WEBSITE
INSTALL -->|"registers WEF TrustedCatalogs (Flags=3) via SMB UNC share"| OFFICE
INSTALL -->|"hidden PowerShell POST /shutdown, then taskkill"| CWIN
INSTALL -->|"postinstall: sideload manifest, trust TLS cert, LaunchAgent"| CMAC
INSTALL -->|"HTTPS POST uninstall-feedback (anonymous, best-effort)"| BSVC
.rewind file lifecycleRecord (Excel or Sheets) → optional zero-knowledge encryption → save/share → replay (viewer or in-Excel temp sheet). Same writeRewind/readRewind contract on both platforms (packages/format/src/container.ts).
flowchart LR
subgraph RECORD["Record"]
EXCEL["Excel Desktop
worksheets.onChanged (Office.js)"]
SHEETSREC["Google Sheets
poll-diff every ~2s"]
end
EXCEL -->|"recorder.ts normalizes to CellChangeEvent[]"| BUF["addin-excel: IndexedDB crash buffer
(encrypted at rest, buffer-crypto.ts)"]
SHEETSREC -->|"diff.ts shadow-map diff"| DRAFT["addon-sheets: in-memory SessionDraft"]
BUF --> STOP1["User clicks Stop & Save"]
DRAFT --> STOP2["User clicks Save .rewind"]
STOP1 --> WRITE["writeRewind(doc, encSpec?)
@excelrewind/format container.ts"]
STOP2 --> WRITE
WRITE --> ENCGATE{"account encryption
policy ON?"}
ENCGATE -- "no (free/starter or opt-out)" --> PLAIN["plaintext: ZIP (store) of
deflate(MessagePack(entry))
-- deterrence only, not encryption"]
ENCGATE -- yes --> ENC["AES-256-GCM per entry (AAD=entry name),
CEK wrapped to passphrase/org/RSA-3072 recipients,
escrow entry embedded for offline unlock"]
PLAIN --> FILE[(".rewind file
(ZIP archive)")]
ENC --> FILE
FILE -->|"local save"| LOCAL["addin-excel: File System Access / download"]
FILE -->|"share via email / Slack / link"| SHARE["Recipient (no ExcelRewind account needed)"]
LOCAL -->|"replay-excel.ts: disposable temp sheets
inside the SAME workbook"| REPLAYX["In-Excel replay"]
SHARE -->|"drop/pick in browser, zero install"| VIEWERO["apps/viewer: peekRewind()
-> cached secret / UnlockDialog
-> readRewind(bytes,{limited:true})"]
VIEWERO --> REPLAYV["Viewer.tsx: replayTo() drives grid
+ audio/webcam + Provenance Spotlight at playhead t"]
Email OTP is the common path; Google/Apple must be handed off to the system browser because WebView2/Apps-Script iframes block embedded OAuth. Every authenticated call downstream is gated by the effective_features() global→tier→org→user cascade.
sequenceDiagram
participant User
participant Pane as "addin-excel auth.ts"
participant SysBrowser as "System Browser"
participant ViewerAuth as "viewer google-signin.html"
participant GoTrue as "Supabase Auth (GoTrue)"
participant PG as "Postgres (backend-data)"
alt Email OTP (all client apps)
User->>Pane: enter email
Pane->>GoTrue: signInWithOtp(email)
GoTrue-->>User: OTP code via email
User->>Pane: enter code
Pane->>GoTrue: verifyOtp(email, token)
GoTrue-->>Pane: session (JWT access + refresh)
else Google / Apple (addin-excel only, system-browser handoff)
User->>Pane: click "Continue with Google"
Pane->>SysBrowser: window.open viewer.excelrewind.com/google-signin.html#nonce
Note over Pane,SysBrowser: viewer origin is deliberately excluded
from manifest AppDomains -- forces the OS
to open the real browser, not the pane webview
SysBrowser->>ViewerAuth: load page (viewer origin)
ViewerAuth->>GoTrue: signInWithOAuth(google|apple), PKCE redirect
GoTrue-->>ViewerAuth: exchangeCodeForSession -> JWT
ViewerAuth->>PG: rpc stash_handoff(nonce, access_token, refresh_token)
loop poll every ~2s, expires after 10 min
Pane->>PG: rpc claim_handoff(nonce)
end
PG-->>Pane: access + refresh tokens
Pane->>Pane: setSession(tokens)
end
Pane->>PG: rpc effective_features()
PG->>PG: resolve_feature_state(global, tier, org, user)
coalesce(user, org, tier, global),
global 'disabled' is a hard-kill floor no narrower scope re-enables
PG-->>Pane: feature state map
Pane->>Pane: gate UI (encryption, spotlight, exam mode, cohort features, etc.)
Stripe is live; Razorpay is written but undeployed (no live webhook secret yet). Both webhooks are the only code path allowed to write a paid profiles.tier value — security fix C-1 closed a self-granted-tier hole in org_create().
sequenceDiagram participant User participant Client as "addin-excel / team (window.open)" participant Stripe participant Razorpay participant Webhook as "backend-services: stripe-webhook / razorpay-webhook" participant PG as "Postgres: profiles.tier" User->>Client: click Upgrade Client->>Stripe: window.open Stripe Payment Link (system browser) Note over Client,Stripe: client NEVER writes profiles.tier directly Stripe-->>User: checkout complete Stripe->>Webhook: POST checkout.session.completed (stripe-signature header) Webhook->>Webhook: stripe.webhooks.constructEventAsync() + SubtleCryptoProvider Webhook->>PG: match profiles.email via ESCAPED ILIKE (%/_/\ escaped)
UPDATE tier, stripe_customer_id PG-->>Webhook: ok Webhook-->>Stripe: 200 (idempotent -- safe to retry) par Razorpay path (written, NOT yet deployed/tested) User->>Client: click Upgrade (India entity) Client->>Razorpay: window.open Razorpay Payment Link Razorpay->>Webhook: POST payment_link.paid (x-razorpay-signature HMAC-SHA256) Webhook->>Webhook: verify via Web Crypto (crypto.subtle, no SDK) Webhook->>PG: same escaped-ILIKE match -> UPDATE tier, razorpay_customer_id end Note right of PG: profiles has NO authenticated UPDATE policy in RLS --
only service-role webhooks or SECURITY DEFINER RPCs can change tier
Five Cloudflare Pages projects (prod branch master, not main), two Supabase projects (prod/dev), and two native installers (Windows NSIS, Mac .pkg) that also register the Excel add-in and start the companion helper. Publishing the Windows installer to the public website is a manual step, not part of npm run deploy.
flowchart TB
subgraph CF["Cloudflare Pages projects (wrangler --branch master)"]
P1["excelrewind-addin
addin.excelrewind.com"]
P2["excelrewind-viewer
viewer.excelrewind.com"]
P3["excelrewind-website
excelrewind.com"]
P4["excelrewind-admin
admin.excelrewind.com"]
P5["excelrewind-team
team.excelrewind.com / account.excelrewind.com"]
end
subgraph SB["Supabase projects"]
PROD["prod: tohvepsjfeecjycspocg.supabase.co"]
DEV["dev: ragrgchidtdpgkyjkznu.supabase.co"]
end
DEPLOY1["npm run deploy
manifest -> build -> deploy:addin -> deploy:viewer -> deploy:website -> smoke"]
DEPLOY1 --> P1
DEPLOY1 --> P2
DEPLOY1 --> P3
DEPLOY2["npm run deploy:admin (separate script, not in the chain)"] --> P4
DEPLOY3["npm run deploy:team (separate script, not in the chain)"] --> P5
MANIFEST["scripts/make-manifest.mts"] -->|"writes manifest.prod.xml + install.html"| P1
SMOKE["npm run smoke -> scripts/smoke.mts"] -->|"GET + BUILD_TAG grep"| P1
SMOKE -->|"GET + BUILD_TAG grep + sample-session.rewind ZIP-magic check"| P2
SMOKE -->|"GET + BUILD_TAG grep"| P3
P1 -.->|"CSP connect-src"| PROD
P2 -.->|"CSP connect-src"| PROD
P3 -.->|"anon-key RPC"| PROD
P4 -.->|"CSP connect-src"| PROD
P5 -.->|"CSP connect-src"| PROD
subgraph WININST["Windows install path"]
NSIS["apps/installer/ExcelRewind-Setup.nsi
(built by build-installers.ps1, Nuitka companion payload)"]
USERW["User's Windows machine"]
OFFICEW["Excel Desktop (Windows)"]
COMPWIN["ExcelRewindCompanion.exe (tray)"]
end
MANUAL["MANUAL step: copy Setup.exe into apps/website/,
rerun manifest, wrangler-deploy website"]
NSIS -.-> MANUAL -.-> P3
P3 -->|"download ExcelRewind-Setup.exe"| USERW
USERW -->|"WEF TrustedCatalogs (SMB UNC share, Flags=3, HKCU)"| OFFICEW
USERW -->|"non-elevated launch via explorer.exe"| COMPWIN
COMPWIN -->|"loopback :38743-47"| OFFICEW
subgraph MACINST["Mac install path"]
MACPKG["apps/companion-mac build-installer.sh (.pkg, py2app,
currently unsigned/unnotarized)"]
USERM["User's Mac"]
OFFICEM["Excel Desktop (Mac)"]
COMPMAC["Companion.app (rumps menu-bar)"]
end
MACPKG -->|"pkg postinstall (root)"| USERM
USERM -->|"sideloads rewind-manifest.xml into Excel's wef folder"| OFFICEM
USERM -->|"trusts self-signed loopback TLS cert (Keychain)"| USERM
USERM -->|"LaunchAgent RunAtLoad=true"| COMPMAC
COMPMAC -->|"loopback HTTPS :38743-47"| OFFICEM
For auditors: what crosses each boundary, what is zero-knowledge encrypted (server structurally cannot read it), and what is architecturally incapable of ever leaving the client.
| Boundary | What crosses it | Classification | Enforcement mechanism |
|---|---|---|---|
| Excel/Sheets workbook → recording engine | Full cell values/formulas the user edits | Stays in-process (pane / Apps Script sandbox) only | Recorder normalizes to CellChangeEvent before anything leaves the process; no raw workbook snapshot is sent anywhere by default |
| Client add-ins → backend-data / backend-services | Session metadata: CellChangeEvent coordinates + before/after values needed for replay, auth tokens, telemetry | Session events only — never full spreadsheet content (root CLAUDE.md constraint) | telemetry.ts sanitizeProps()/flattenSettings() caps every tracked payload to ≤10 primitive keys and redacts free text; OAuth scopes stay minimal (Sheets add-on: spreadsheets.currentonly only) |
| Encryption key material | Passphrase, recovery code, unwrapped org/user master key (OMK/UMK), RSA private key | Zero-knowledge — never reaches any server in usable form | Lives only in module-level in-memory variables (keyring-client.ts, React state in team), wiped on sign-out (clearUnlockCache()); DB (org_keys/user_keys) stores only ciphertext, KDF salts/iterations, and SHA-256 fingerprints — public keys are the only plaintext by design |
.rewind file at rest / in transit (shared via email, Slack, link) | Encrypted files: AES-256-GCM ciphertext + wrapped CEK + escrow. Unencrypted files: deflate-obfuscated MessagePack (readable with effort) | Encrypted = confidential (recipient needs passphrase/recovery code/org key); plaintext = deterrence only, explicitly documented as NOT encryption | container.ts per-entry AES-256-GCM (AAD=entry name) + GCM auth tag catches tampering; viewer applies decompression-bomb caps ({limited:true}) since a shared file is untrusted input |
| Crash-recovery buffer (IndexedDB, add-in) | In-progress session draft + audio/video chunks | Encrypted at rest when the account's encryption policy is ON (closed a prior plaintext-buffer gap) | buffer-crypto.ts: same AES-256-GCM/wrapCek/unwrapCek path as a real file, AAD='rewind.recovery-buffer'; never persisted to localStorage |
| Companion loopback (addin-excel ↔ companion-win/mac) | Workbook lists, cell-change events, raw reference-workbook bytes | Local machine only — never crosses the network beyond 127.0.0.1 | Bearer token + CORS allowlist (addin.excelrewind.com, localhost:3000 only); Mac additionally requires a locally-trusted self-signed TLS cert since WKWebView blocks plain-HTTP loopback |
| Billing webhooks (Stripe/Razorpay → backend-services) | Signed payment events → a single tier-flip write | Server-to-server, signature-verified; the only path allowed to grant a paid tier | constructEventAsync (Stripe) / HMAC-SHA256 via crypto.subtle (Razorpay); security fix C-1 (20260724_org_no_forged_paid_tier.sql) closed a self-granted-tier hole; profiles has no authenticated UPDATE RLS policy at all |
| Admin console (apps/admin → admin-ops) | Tier/feature state, user PII (email, status), coupons, pricing, destructive data-reset | Owner-only, highest-privilege write surface in the system | Dual factor: static OWNER_METRICS_TOKEN bearer + live Supabase OTP session for one hardcoded superadmin email, both re-validated server-side; destructive reset additionally requires an exact confirm-phrase re-checked in SQL |
Anonymous telemetry (viewer_events, usage_events, uninstall_feedback) | Event names, UTM params, boolean/number/short-enum props only | Metadata-only by schema, not by convention | Explicit code comment on viewer_events: "the SCHEMA is the defense" — CHECK constraints bound the event vocabulary and cap props at 1KB; no RLS needed because there's nothing sensitive to protect |
| AI training pipeline (SDD-scoped, not yet built) | Would be: free-tier, non-"Personal"-tagged sessions only, after PII-stripping anonymization | Deferred post-MVP (root CLAUDE.md); no Claude/Anthropic API call exists anywhere in the current interface set | SESSION_TAGS is append-only and 'Personal / other' must stay present & last — the future training-exclusion gate is already baked into the type, even though the processing layer itself is not implemented |
supabase-js/PostgREST + SECURITY DEFINER RPCs, or to one of 8 Deno Edge Functions for privileged work. SDD Milestone 2 (a real API server) has not been started.
profiles.tier value (security fix C-1). org_create() previously self-granted “enterprise” gated only by a free-email-domain check — any ~$1 custom domain bypassed it.
.rewind's deflate is deterrence only (documented in container.ts's own header comment), not confidentiality. AES-256-GCM zero-knowledge encryption (OPS-6) shipped to production in v3.23.0-.2; verify any doc/marketing security claim against the actual code path (per rewind-encryption-gap project memory).
viewer.excelrewind.com/google-signin.html in the system browser (deliberately excluded from the manifest's AppDomains to force the handoff) and polling a nonce-keyed stash_handoff/claim_handoff RPC pair that expires after 10 minutes. The Google Sheets add-on sidesteps the problem entirely by using email-OTP only.
disabled is a hard-kill floor no narrower scope can re-enable (resolve_feature_state(), effective_features() RPC). public.tiers is the single canonical source of the 4 tiers (free/starter/team/enterprise); tiers retire (active=false) rather than delete.
GetActiveObject (Windows COM) / AppleScript (Mac), and the Windows COM reference is released after every idle call, the root-cause fix for a historical ghost-EXCEL.EXE ownership bug. Both speak an identical loopback wire protocol (5 ports, same JSON shapes, Bearer auth) so apps/addin-excel/src/companion.ts is unmodified across OSes — Mac's difference is HTTPS-only (WKWebView blocks plain-HTTP loopback as mixed content) via a per-machine self-signed cert.
OBJECTS_MARKER/CHARTS_MARKER/PIVOTS_MARKER/TABLES_MARKER are synthetic cell addresses the recorder stamps after diffing per-sheet object counts — hard-won platform knowledge encoded directly in the type file.
opts.limited:true, which only apps/viewer sets (untrusted, possibly-shared file). The add-in reads its own local file unbounded by design. A future untrusted-upload surface must remember to pass this flag.
profiles.email via ILIKE and must escape %/_/\ first — documented inline as a real fixed vulnerability (an unescaped pattern like a_%@x.com could have upgraded another account's tier).
master, not main. Every wrangler pages deploy call must include --branch master or the custom domain silently keeps serving the previous build. deploy:admin and deploy:team are separate scripts NOT included in the top-level npm run deploy chain and must be run explicitly.
npm run manifest, and manually wrangler pages deploy apps/website (documented in OPS.md §4b).
/status probe as a CSP violation — apps/addin-excel/public/_headers must keep connect-src http(s)://127.0.0.1:*.
ExcelRewindCompanion.exe triggers SmartScreen on first run. Mac: the .app/.pkg is unsigned and unnotarized (Apple Developer ID signing + notarization is a tracked owner-side TODO).
CLAUDE.md). No downstream logic anywhere should assume millisecond-accurate cross-cell ordering beyond the sort-by-t contract in packages/format/src/replay.ts.