|
| 1 | +--- |
| 2 | +title: "Move the rest of the app to remote hosting — prod still falls back to localhost for every undeployed remote" |
| 3 | +lede: "Only the client-facing surfaces (Corpus Builder + Augment-from-DB) were deployed as hosted remotes; the other 12 micro-frontends are hardcoded to http://localhost:3XXX in the production shell build, so augment.didi.sh tries to fetch them from the visitor's own machine at boot — they fail, and on some networks hang on a TCP timeout. This was a deliberate shortcut to get client collaboration live and defer the big refactor. Now the rest of the app needs to graduate to remote hosting." |
| 4 | +date_created: 2026-08-03 |
| 5 | +date_modified: 2026-08-03 |
| 6 | +authors: |
| 7 | + - Michael Staton |
| 8 | +augmented_with: |
| 9 | + - Claude Code on Claude Opus 4.8 |
| 10 | +semantic_version: 0.0.0.1 |
| 11 | +tags: |
| 12 | + - Issue |
| 13 | + - Augment-It |
| 14 | + - Module-Federation |
| 15 | + - Deployment |
| 16 | + - Shell |
| 17 | + - Remote-Hosting |
| 18 | +status: Open · Diagnosed |
| 19 | +--- |
| 20 | + |
| 21 | +# Move the rest of the app to remote hosting |
| 22 | + |
| 23 | +## Why Care? |
| 24 | + |
| 25 | +On augment.didi.sh, the production shell tries to load a dozen micro-frontends |
| 26 | +from **`http://localhost:3002…3015/remoteEntry.js`** — the *visitor's* own |
| 27 | +machine, not a hosted server. They fail every boot (visible in the console as |
| 28 | +`Loading failed for the <script> …`), and on some network configs those fetches |
| 29 | +**hang on a TCP connect timeout** — the residual boot noise the operator felt as |
| 30 | +part of the "minute" (see [[Refactoring-for-API-Speed]]'s measured finding). If a |
| 31 | +collaborator happens to have local dev servers on those ports, it's worse: the |
| 32 | +prod shell silently mounts *their laptop's* dev code. |
| 33 | + |
| 34 | +## How we got here (the deliberate shortcut) |
| 35 | + |
| 36 | +The priority was getting **client collaboration** live: the **Corpus Builder** |
| 37 | +(strategy-curator) and the **Augment-from-DB** flow (org-workbench + |
| 38 | +search-and-add + search-results), plus **chat**. Those surfaces were deployed as |
| 39 | +hosted Railway services with real `PUBLIC_*_REMOTE` URLs. Everything else — the |
| 40 | +original CSV-first pipeline and the resolver micro-frontends — was left pointing |
| 41 | +at its dev `localhost` port, on the theory that a humain-vc/client deploy never |
| 42 | +opens those Flows so it wouldn't matter. It was a knowing shortcut to avoid the |
| 43 | +big "deploy the whole federation" refactor. This issue is the debt coming due. |
| 44 | + |
| 45 | +## The exact mechanism |
| 46 | + |
| 47 | +In `shell/rsbuild.config.ts` the `remotes` map has **two tiers**: |
| 48 | + |
| 49 | +- **Environment-aware (5, deployed):** `process.env.PUBLIC_*_REMOTE || 'http://localhost:…'` |
| 50 | + — a prod build injects the hosted URL via the shell Dockerfile's `ARG`/`ENV`. |
| 51 | +- **Hardcoded localhost (12, undeployed):** e.g. |
| 52 | + `recordCollector: 'recordCollector@http://localhost:3002/remoteEntry.js'` — |
| 53 | + **no env branch at all**, so the value ships verbatim in the production bundle. |
| 54 | + |
| 55 | +And they load **at boot, not lazily**, because the shell's **default Flow is |
| 56 | +Record Collector** (`recordCollector`, undeployed) and its peek-deck rotations |
| 57 | +reference other undeployed remotes (`recordDbResolver`, `personDbResolver`, |
| 58 | +`affiliationRatingResolver`) — so mounting the default surface fetches their |
| 59 | +`localhost` entries immediately. |
| 60 | + |
| 61 | +## Inventory — every remote, hosting status |
| 62 | + |
| 63 | +| Remote | Port | Deployed as a Railway service? | Config tier | |
| 64 | +|---|---|---|---| |
| 65 | +| chat | 3006 | ✅ yes | env-aware | |
| 66 | +| org-workbench | 3014 | ✅ yes | env-aware | |
| 67 | +| search-and-add | 3016 | ✅ yes | env-aware | |
| 68 | +| strategy-curator (Corpus Builder) | 3017 | ✅ yes | env-aware | |
| 69 | +| search-results | 3018 | ✅ yes | env-aware | |
| 70 | +| **record-collector** | 3002 | ❌ no | **hardcoded localhost** | |
| 71 | +| **prompt-template-manager** | 3003 | ❌ no | **hardcoded localhost** | |
| 72 | +| **request-reviewer** | 3004 | ❌ no | **hardcoded localhost** | |
| 73 | +| **response-reviewer** | 3005 | ❌ no | **hardcoded localhost** | |
| 74 | +| **enhanced-records-list** | 3007 | ❌ no | **hardcoded localhost** | |
| 75 | +| **record-db-resolver** | 3008 | ❌ no | **hardcoded localhost** | |
| 76 | +| **pack-runner** | 3009 | ❌ no | **hardcoded localhost** | |
| 77 | +| **person-db-resolver** | 3010 | ❌ no | **hardcoded localhost** | |
| 78 | +| **records-surface** | 3011 | ❌ no | **hardcoded localhost** | |
| 79 | +| **affiliation-rating-resolver** | 3012 | ❌ no | **hardcoded localhost** | |
| 80 | +| **sort-filter-lens** | 3013 | ❌ no | **hardcoded localhost** | |
| 81 | +| **person-enrichment** | 3015 | ❌ no | **hardcoded localhost** | |
| 82 | + |
| 83 | +Twelve undeployed remotes, all shipping `localhost` in prod. |
| 84 | + |
| 85 | +## What "move to remote hosting" means — two tracks |
| 86 | + |
| 87 | +### Track A — the real goal: host the rest of the app |
| 88 | +Graduate each remaining micro-frontend to a hosted remote, the same way the five |
| 89 | +client-facing ones already are: |
| 90 | +1. Add a Railway service per remote (its `Dockerfile`, a Railway-generated or |
| 91 | + `*.didi.sh` domain). |
| 92 | +2. Add a `PUBLIC_<REMOTE>_REMOTE` build ARG to `shell/Dockerfile` + set it as a |
| 93 | + shell service variable, and switch the `rsbuild.config.ts` line to |
| 94 | + `process.env.PUBLIC_<REMOTE>_REMOTE || 'http://localhost:<port>'`. |
| 95 | +3. Sequence by what a client/collaborator actually needs next (the CSV pipeline — |
| 96 | + record-collector, response-reviewer, enhanced-records-list — is the obvious |
| 97 | + first cluster; the resolvers next). |
| 98 | + |
| 99 | +Note: this is exactly the "deploy the whole federation" refactor the shortcut |
| 100 | +deferred — so it wants its own **plan** with a sequencing decision, not a |
| 101 | +one-shot. |
| 102 | + |
| 103 | +### Track B — the interim safety net (cheap, do regardless) |
| 104 | +Even before all twelve are hosted, prod should **never fetch `localhost`**: |
| 105 | +1. Make the remotes map **environment-aware** — in a production build, **omit** |
| 106 | + any remote without a real hosted URL, so Module Federation never tries it. |
| 107 | +2. **Guard the mount path** and pick a **deployed default Flow** in prod |
| 108 | + (strategy-curator / Corpus Builder, what humain-vc actually uses) so boot |
| 109 | + never mounts an undeployed remote. |
| 110 | + |
| 111 | +Track B stops the boot noise and the hang-on-a-collaborator's-network failure |
| 112 | +mode immediately; Track A is the durable "the whole app is hosted" end state. |
| 113 | + |
| 114 | +## Acceptance |
| 115 | + |
| 116 | +- Loading augment.didi.sh (or any client deploy) produces **zero** |
| 117 | + `localhost:3XXX` fetch attempts in the console. |
| 118 | +- No boot mounts an undeployed remote; the default Flow is a deployed one. |
| 119 | +- Each remote intended for a client deploy resolves to a hosted `remoteEntry.js`. |
| 120 | + |
| 121 | +## See also |
| 122 | + |
| 123 | +- [[Refactoring-for-API-Speed]] — the measurement that surfaced this (backend |
| 124 | + was ~543ms; these localhost loads were the residual). |
| 125 | +- `DEPLOYMENT.md` — the shell build-args + per-service deploy pattern the five |
| 126 | + hosted remotes already follow. |
| 127 | +- [[Augment-From-DB-Flow]] · [[Strategy-Curator-Entry-Point-for-Augment-It]] — |
| 128 | + the two surfaces that WERE prioritized and hosted. |
| 129 | +- Boot instrumentation (gh #80) — how the localhost loads became visible. |
0 commit comments