Call Analyst — Refactor Progress
The 20,500-line single-file app is now a clean, modular, CI-guarded codebase — done in safe, individually-verified steps with nothing live touched. Here's exactly what changed, how it was proven safe, and where we'd love your read on what's next (architecture only — legal is parked on purpose).
Branch: refactor/split-monolith (pushed, not merged) · Companions: build docs · the original plan · your AWS proposal
Summary
No build step was added (still plain ordered <script> tags, instant deploys). The consumer (callanalyst.app) and internal (analyst.daxos.us) builds still share one codebase, and that difference now lives in one declarative file. Every step was gated by a battery of checks and the app behaves identically to the original monolith in both builds.
Before → after
| Aspect | Before | After |
|---|---|---|
| App shell | 1 file, 20,513 lines, CSS+JS inline | index.html 1,187 lines + ordered refs |
| Modules | everything tangled in one block | 27 named js/NN-*.js files, biggest 2,648 |
| Core engine | one atomic 5,810-line block | 7 logical parts (11a–11g) |
| Build segregation | IS_CONSUMER ? x : y ternaries scattered everywhere | one FLAVOR_PROFILES.{consumer,internal} you read side by side |
| Tests / CI | none | both-flavor boot smoke test + GitHub Actions on every PR |
| Hardcoded secrets | a live Resend key in a comment | removed; lib-drift guard added |
What got done all verified, branch only
| Commit | What |
|---|---|
| f5634a0 | Split the monolith — all inline CSS/JS extracted into ordered files, byte-exact (reconstruction proves it re-expands to the original) |
| abe0284 | Smoke-test guard — boots both flavors headless, asserts a clean app |
| 4001618 | Declarative flavor profiles — proven byte-identical across 29 hostnames |
| 13fc3f4 | Core split — 5,810-line core → 7 parts at AST-proven-safe seams (zero forward references) |
| 836456b | CI workflow — syntax-check every module + function, boot-smoke both flavors, on every PR |
| a057571 | Cleanup — removed a leaked secret + dead code, added npm scripts + a shared-lib drift guard |
How it was verified (every step)
The whole point of "don't break anything" was making each change provably safe. Each commit had to pass all of:
- Reconstruction — the split files re-concatenate to the original byte-for-byte (mathematical proof of identical content)
- Syntax —
node --checkon every module (proves each cut lands on a valid boundary) - Boot smoke — both flavors come up with no JS errors, all globals present, correct gate/hub/model-selector per flavor
- Differential interaction — monkey-clicked the original monolith vs the new build, both flavors: identical, zero errors (consumer 16 clicks/0, internal 10/0)
- Equality proof (flavor change) — old vs new config evaluated across 29 hostnames incl. edge cases, byte-identical
- Adversarial review — an independent agent tried to disprove byte-identity and couldn't (SHA-256 match on the flavor file)
<script>), the core was one atomic block. We ran a full AST dependency analysis first and found zero load-time forward references, which is what made splitting it safe. We didn't guess — we proved it, then re-verified at runtime.Extra fixes this round
- ✓ Secret leak removed — a live Resend API key was hardcoded in a comment in
welcome-email.mjs(runtime already used the env var). It was the only hardcoded secret in the tree. Pablo/Mark: that key should be rotated since it's in git history. - ✓ Dead code deleted — an orphaned
startMonitoring()and a write-onlyca_aiProviderpreference (grep-proven zero readers, smoke + QA confirmed zero behavior change) - ✓ Shared-lib drift guard —
bot-branding.mjsandr2.mjsare byte-identical duplicates across thefunctions/andedge-functions/trees; CI now fails if they ever diverge - ✓ npm scripts —
npm run smoke / check / lib-drift
Pablo — this part's for you
The safe, mechanical decomposition is done and locked behind CI, so the codebase is finally legible enough to hand around. Everything above was provably reversible and touched nothing live.
What's left is the interesting part — the calls that need a real engineer's judgment about build and deploy topology, not mechanical edits. We've started a backlog below, but you've got far more infra mileage than we do. What would you add, reorder, or push back on? Especially keen on your read on the two structural hazards (the internal branch drift and main=prod), and whether the Phase-2 path here connects to the AWS direction you proposed or stays independent of it.
To be clear on scope: this list is architecture only. Legal/compliance is deliberately parked until there are real users — not because it doesn't matter, but so we don't boil the ocean pre-launch.
Architectural backlog
✅ Done (this session)
Monolith split · declarative flavor profiles · core split · smoke test + CI · secret removed · dead code · lib-drift guard.
🟢 Safe to do next (low risk, fully verifiable)
| Item | Notes | Effort |
|---|---|---|
| Edge-function syntax check in CI | Node functions are checked; the Deno edge functions aren't (need a deno check job) | S |
| Physically de-dup the two identical serverless libs | guard is in place; a shared source dir + copy step removes the duplication entirely | S |
Reconcile shared.mjs (88 dup lines) | edge copy is a superset of the functions copy; merge the common prefix | M |
🟠 Needs your call (touches prod / internal / build topology)
| Item | Why it matters | Effort · Risk |
|---|---|---|
Kill the internal branch drift biggest | A long-lived internal branch (main +9 / internal +5, ~1,300 lines diverged) re-creates the exact segregation hazard the flavor file was built to kill — and this whole refactor isn't on it. Clean end-state: everything on main, internal selected by a deploy-time FLAVOR=internal env var. | L · high |
Stop main = prod | main auto-publishes to callanalyst.app. The smoke CI runs on PRs but a direct push to main bypasses it. Protect main + require the smoke check, or a release branch / manual promote. | M · med |
| Decompose the big IIFE modules | 12-projects-hub (2,648), 17-cvhome-wiring (1,045), etc. are single IIFEs — can't be byte-split, need an internal refactor (promote helpers out of closure). Higher risk than the core split was. | L · med |
| Phase-2 build step (esbuild, optional TS) | Removes load-order fragility, adds minify/tree-shake/module isolation. ~1 config file, no source rewrite. TS is a separate larger decision. | M · med |
| Finish or shelve the consumer file-drop | It's wired but localStorage-only (never hits R2), so the storage meter reads 0. Product call: wire to R2, or hide the meter. | M · med |
How to review it
- Branch:
refactor/split-monolithoncus-commits/call-analyst— 6 focused commits, each independently verifiable - Map:
call-analyst-pwa/ARCHITECTURE.md— the module list, load-order rules, and how to add a feature - Run it:
npm i -D puppeteer && npm run smoke— boots both flavors and asserts a clean app - Preview: callanalyst-split-preview.netlify.app (renders the internal build, password
daxos2027, because the host isn't a consumer host — which itself shows segregation working) - Nothing is merged.
mainis prod, so this lands through a staging gate after review, never a raw push.
refactor/split-monolith · companions: build docs · plan · AWS proposal. Behind the Daxos gate. Questions → Mark.