|
| 1 | +--- |
| 2 | +depth: standard |
| 3 | +id: PRD-031 |
| 4 | +kind: prd |
| 5 | +last_modified_at: 2026-05-09T11:34:37.535120+00:00 |
| 6 | +last_modified_by: claude-code/2.1.138 |
| 7 | +links: |
| 8 | +- target: PRD-014 |
| 9 | + relation: based_on |
| 10 | +- target: PRD-030 |
| 11 | + relation: based_on |
| 12 | +status: active |
| 13 | +title: Drop precompressed client assets from tarball |
| 14 | +--- |
| 15 | + |
| 16 | +--- |
| 17 | +id: PRD-031 |
| 18 | +title: "Drop precompressed client assets from tarball" |
| 19 | +status: Draft |
| 20 | +author: fedorovvvv |
| 21 | +created: 2026-05-09 |
| 22 | +updated: 2026-05-09 |
| 23 | +priority: P2 |
| 24 | +depth: standard |
| 25 | +domain: general |
| 26 | +projectType: cli_tool |
| 27 | +stepsCompleted: [] |
| 28 | +--- |
| 29 | + |
| 30 | +# PRD-031: Drop precompressed client assets from tarball |
| 31 | + |
| 32 | +## Progress |
| 33 | + |
| 34 | +``` |
| 35 | +Phase 0 ████████████████████████ 1/1 (100%) |
| 36 | +───────────────────────────────────────────────── |
| 37 | +TOTAL 1/1 (100%) |
| 38 | +``` |
| 39 | + |
| 40 | +--- |
| 41 | + |
| 42 | +## Executive Summary |
| 43 | + |
| 44 | +### Vision |
| 45 | + |
| 46 | +Ship a smaller `@forgeplan/web` tarball by removing 84 precompressed |
| 47 | +`*.br` / `*.gz` client siblings emitted by `@sveltejs/adapter-node` — |
| 48 | +saving ~520K per `dist*/` artifact (~29% of the experimental tarball) |
| 49 | +without measurably degrading the local-first user experience. |
| 50 | + |
| 51 | +### Problem |
| 52 | + |
| 53 | +`@sveltejs/adapter-node` defaults to `precompress: true`, so every |
| 54 | +`dist*/client/` carries 21 `.br` + 21 `.gz` files alongside the |
| 55 | +originals (~520K). The tarball ships **two** images (`dist/` for |
| 56 | +`stable`, `dist-nightly/` for `nightly`), so users pay the cost twice |
| 57 | +on every `npm install` / `npx` invocation. |
| 58 | + |
| 59 | +The runtime CPU savings precompressed assets buy (sirv serving the |
| 60 | +cached `.br`/`.gz` directly) are negligible for the canonical |
| 61 | +deployment shape: `npx @forgeplan/web start` on `127.0.0.1` for one |
| 62 | +local user, behind no CDN. Every user pays the install cost; almost |
| 63 | +nobody benefits from the runtime saving. |
| 64 | + |
| 65 | +**Impact**: ~520K × 2 dists = ~1.04 MB of dead weight on every |
| 66 | +install. After PR #117 minified the server bundle (1.4 MB → 683 KB), |
| 67 | +precompressed client assets are now the largest fixed cost in |
| 68 | +`dist-nightly/`. |
| 69 | + |
| 70 | +### Target Users |
| 71 | + |
| 72 | +| Persona | Описание | Ключевая боль | |
| 73 | +|---------|----------|---------------| |
| 74 | +| Local-first user | Runs `npx @forgeplan/web start` on `127.0.0.1` | Pays install cost (tarball download + extraction) for runtime savings they will never measure | |
| 75 | +| CI runner | Installs `@forgeplan/web` in throwaway environments | Slower cold installs because of inflated tarball | |
| 76 | + |
| 77 | +### Differentiators |
| 78 | + |
| 79 | +- Single-flag adapter change (Option A from issue #120) — minimal |
| 80 | + blast radius, fully reversible by flipping `precompress` back to |
| 81 | + `true`. |
| 82 | +- Aligned with `npx @forgeplan/web` philosophy: the package is |
| 83 | + optimized for fast first-run on a developer machine, not for |
| 84 | + high-throughput public-facing serving. |
| 85 | + |
| 86 | +--- |
| 87 | + |
| 88 | +## Success Criteria |
| 89 | + |
| 90 | +| ID | Criterion | Metric | Current | Target | Timeframe | How to Measure | |
| 91 | +|----|-----------|--------|---------|--------|-----------|----------------| |
| 92 | +| SC-1 | Precompressed siblings absent from both shipped images | count of `*.br` + `*.gz` under `dist/client/` ∪ `dist-nightly/client/` after `npm run build` | 84 | 0 | This PR | `find dist dist-nightly -name '*.br' -o -name '*.gz' \| wc -l` | |
| 93 | +| SC-2 | Each tarball image stays under PRD-014 SC-1 cap | `du -sh dist*/` | 2.0M each | ≤ 1.5M each (target) / ≤ 3M (cap) | This PR | `du -sh dist dist-nightly` | |
| 94 | +| SC-3 | `start` still serves usable HTML | HTTP status of `GET /` after `npx @forgeplan/web start` | 200 | 200 | This PR | scratch-dir manual probe (see EVID) | |
| 95 | +| SC-4 | `/api/health` still returns `ok` | endpoint response | `{ ok: true }` | `{ ok: true }` | This PR | manual probe | |
| 96 | +| SC-5 | No regression in `npm run smoke` | smoke exit code | 0 | 0 | This PR | `npm run smoke` | |
| 97 | + |
| 98 | +--- |
| 99 | + |
| 100 | +## Product Scope |
| 101 | + |
| 102 | +### MVP (In-Scope) |
| 103 | + |
| 104 | +- Pass `precompress: false` to `adapter()` in `template/svelte.config.js`. |
| 105 | +- Re-build both images (`dist/` + `dist-nightly/`) and verify the |
| 106 | + precompressed siblings are gone. |
| 107 | +- Smoke test the bin script against a scratch dir end-to-end. |
| 108 | +- Document the trade-off (no precompressed cache; sirv now serves |
| 109 | + uncompressed or compresses on-the-fly per `Accept-Encoding`). |
| 110 | + |
| 111 | +### Out of Scope |
| 112 | + |
| 113 | +- Lowering `DIST_*_MAX_BYTES` cap in `scripts/build.mjs` — separate |
| 114 | + decision (PRD candidate). |
| 115 | +- RFC-013 graduation / legacy `dist/` removal — owned by PRD-014. |
| 116 | +- Adding a custom sirv compression middleware to recover the runtime |
| 117 | + saving — only if a measured regression appears. |
| 118 | + |
| 119 | +### Growth Vision |
| 120 | + |
| 121 | +- If real users complain about uncompressed first-load wire size |
| 122 | + over slow networks, revisit by either (a) flipping `precompress` |
| 123 | + back on, or (b) wiring an on-the-fly compression middleware in |
| 124 | + `template/src/hooks.server.ts`. |
| 125 | + |
| 126 | +--- |
| 127 | + |
| 128 | +## User Journeys |
| 129 | + |
| 130 | +### Journey 1: Local-first user installs and starts |
| 131 | + |
| 132 | +**Цель пользователя**: Spin up the Forgeplan web viewer in a working repo. |
| 133 | + |
| 134 | +| Шаг | Действие пользователя | Ответ системы | Заметки | |
| 135 | +|-----|----------------------|---------------|---------| |
| 136 | +| 1 | `npx @forgeplan/web init -y` | Copies `dist/` (~1.5 MB target) into `.forgeplan-web/` | Smaller download than today | |
| 137 | +| 2 | `npx @forgeplan/web start` | Spawns node, listens on `127.0.0.1:5174` | No precompressed cache hit on assets | |
| 138 | +| 3 | Opens `http://127.0.0.1:5174` | Browser receives HTML; CSS/JS load | sirv serves assets uncompressed or via on-the-fly gzip per `Accept-Encoding` (TBD by smoke) | |
| 139 | + |
| 140 | +**Результат**: Same UX, smaller install footprint. |
| 141 | + |
| 142 | +### Journey 2: CI runner does throwaway install |
| 143 | + |
| 144 | +**Цель пользователя**: Validate Forgeplan integration in CI. |
| 145 | + |
| 146 | +| Шаг | Действие пользователя | Ответ системы | Заметки | |
| 147 | +|-----|----------------------|---------------|---------| |
| 148 | +| 1 | `npm i -g @forgeplan/web@latest` | Pulls smaller tarball, faster cold install | Saves ~1 MB per cold install across both images | |
| 149 | +| 2 | `forgeplan-web --version` | Prints version | Unaffected by this change | |
| 150 | + |
| 151 | +**Результат**: Faster CI cold start, no functional change. |
| 152 | + |
| 153 | +--- |
| 154 | + |
| 155 | +## Functional Requirements |
| 156 | + |
| 157 | +| ID | Category | Priority | Requirement | Journey | |
| 158 | +|----|----------|----------|-------------|---------| |
| 159 | +| FR-001 | Build | Must | Build pipeline can emit `dist*/client/` directories with no `*.br` and no `*.gz` siblings | Journey 1, Journey 2 | |
| 160 | +| FR-002 | Runtime | Must | `start` command can serve an HTTP 200 on `GET /` and `/api/health` from a freshly initialised `.forgeplan-web/` that contains no precompressed siblings | Journey 1 | |
| 161 | +| FR-003 | Runtime | Should | Browser can render a usable page (CSS + JS load successfully) when the static-asset middleware has no precompressed siblings to fall back to | Journey 1 | |
| 162 | + |
| 163 | +--- |
| 164 | + |
| 165 | +## Non-Functional Requirements |
| 166 | + |
| 167 | +| ID | Category | Requirement | Metric | Condition | Measurement | |
| 168 | +|----|----------|-------------|--------|-----------|-------------| |
| 169 | +| NFR-001 | Size | System shall emit each `dist*/` artifact below the existing cap | ≤ 3 MB | After `npm run build` for both `stable` and `nightly` images | `du -sh dist dist-nightly` + `scripts/build.mjs` cap assertion | |
| 170 | +| NFR-002 | Reversibility | System shall allow restoring precompressed assets by flipping a single boolean | 1 line in `template/svelte.config.js` | Forever | Diff inspection | |
| 171 | +| NFR-003 | Compatibility | System shall not regress `npm run smoke` exit code | exit code = 0 | Local + CI matrix (ubuntu/macos/windows) | `npm run smoke` | |
| 172 | + |
| 173 | +--- |
| 174 | + |
| 175 | +## Risks & Mitigations |
| 176 | + |
| 177 | +| ID | Risk | Probability | Impact | Mitigation | Owner | |
| 178 | +|----|------|-------------|--------|------------|-------| |
| 179 | +| R-1 | Without precompressed siblings, sirv serves assets uncompressed → +700 KB first-load wire traffic for users on slow links | Medium | Low (local-first deployments dominate) | Document in README + this PRD; if real complaints surface, add an on-the-fly gzip middleware in `template/src/hooks.server.ts` or revert to `precompress: true` | fedorovvvv | |
| 180 | +| R-2 | A future custom hook in `template/` assumes `.br`/`.gz` siblings exist | Low | Medium | Grep template for `\.br` / `\.gz` references in this PR; none found at time of writing | fedorovvvv | |
| 181 | +| R-3 | Adapter-node behavior changes in a future major version, making `precompress: false` ineffective | Low | Low | Pinned via `template/package.json#dependencies`; bump-with-test is the existing process | fedorovvvv | |
| 182 | + |
| 183 | +--- |
| 184 | + |
| 185 | +## Affected Files |
| 186 | + |
| 187 | +- `template/svelte.config.js` — flip `adapter()` → `adapter({ precompress: false })`. |
| 188 | +- `dist/`, `dist-nightly/` — regenerated by `npm run build` (no source-controlled change beyond the regenerated bundles). |
| 189 | + |
| 190 | +## Related Artifacts |
| 191 | + |
| 192 | +| Artifact | Relation | Status | |
| 193 | +|----------|----------|--------| |
| 194 | +| PRD-014 | parent (tarball-size cap SC-1) | active | |
| 195 | +| PRD-030 | parent (dual-image pipeline `stable` + `nightly`) | active | |
| 196 | +| PR #117 | predecessor (server bundle minify, 1.4 MB → 683 KB) | merged | |
| 197 | +| Issue #120 | source | open (this PRD closes it) | |
| 198 | + |
| 199 | +--- |
| 200 | + |
| 201 | +> **Decision recorded (closes issue #120 acceptance criterion 1)**: |
| 202 | +> Option A — disable precompress at the adapter level. Rejected: |
| 203 | +> Option B (build.mjs strip — strictly more code for the same runtime |
| 204 | +> behaviour) and Option C (keep `.br` only — half the size win for |
| 205 | +> ~zero additional safety, since a sirv fallback regression would still |
| 206 | +> need investigation). |
| 207 | +
|
| 208 | + |
| 209 | + |
| 210 | + |
0 commit comments