Why
e2e/nextjs + e2e/nextjs-component currently pnpm deploy --legacy the app into a mktemp dir inside playwright's webServer.command — a full fresh registry resolution of the workspace graph on every run, under one 180s timeout (the #435 flake). The deploy exists only to give Next/Turbopack a real non-symlinked node_modules (serverExternalPackages requires resolution under node_modules/; workspace symlinks resolve outside it — see b1a96d8).
Spike-verified (3 rounds, evidence in session): pnpm's own primitives replace the whole dance —
dependenciesMeta: {"@conciv/it": {"injected": true}} installs a real hard-copy. In-place next dev boots in 0.3-1s, widget spec green, and the builtin server extensions load (plain symlinks reproducibly log failed to load builtin server extensions … MODULE_NOT_FOUND from the terminal extension's dynamic require and go green anyway — silent coverage loss, which disqualifies the zero-injection option).
syncInjectedDepsAfterScripts: ["build"] (pnpm ≥10.5, top-level key in pnpm-workspace.yaml) closes the staleness gap: verified the sync fires when turbo spawns the build (turbo shells to pnpm run), fires on direct pnpm --filter run build, and recreates missing/new dist files (inode-verified hardlinks). CI order is safe: test:e2e dependsOn ^build, and the build is what triggers the sync.
- The packed tarball harness (
e2e/nextjs/packed/*) is orthogonal and untouched — it remains the published-shape verifier.
Tasks
T1 — injection config (XS)
dependenciesMeta.injected for the @Conciv runtime deps of both nextjs e2e apps (verify which deps each app's runtime code actually imports — for nextjs it is @conciv/it only; verify nextjs-component equivalently). Add syncInjectedDepsAfterScripts: ["build"] to pnpm-workspace.yaml. Acceptance: injected copies are real dirs with current dist; marker-verified resync after pnpm turbo run build --filter=@conciv/it --force.
T2 — in-place webServer (S)
Both playwright configs: webServer.command = in-place pnpm exec next dev --port <port> only; delete the mktemp/deploy shell chain; tighten webServer timeout to an honest 60s. No deploy helpers, no CLIs, no root-walkers anywhere.
T3 — extension-load guard (S)
The silent-failure mode found in the spike must be a test failure, not a log line: add a positive assertion that builtin server extensions actually loaded (prefer asserting through real behavior/engine surface — e.g. an extension-provided UI or endpoint visible in the existing spec flow; implementer picks the least-invasive honest signal and states it in the PR). A run where extensions fail to load must go red.
T4 — gates + CI validation (S)
Full local gates; then verify on the PR that CI's install→build→e2e order produces fresh injected copies (the spike's mechanism analysis says yes — confirm empirically on the PR run, quote the timings). Expect e2e consumer shard time to drop by the deploy duration.
T5 — cleanup (XS)
Close PR #443 as superseded (its diagnosis was right; this replaces its mechanism). Closes #435. Remove CONCIV_DEPLOY_FRESH references if any landed anywhere.
Deferred, documented
@conciv/it's transitive workspace deps remain symlinked one level down (spike: extensions load correctly with top-level injection alone). If a transitive-symlink resolution bug surfaces later, the lever is dependenciesMeta.injected inside packages/it — do not apply pre-emptively.
Risks
| Risk |
Impact |
Mitigation |
| An e2e path exercises a transitive symlink Turbopack mishandles |
M |
T3's guard turns it red instead of silent; deferred lever documented |
| syncInjectedDepsAfterScripts interacts with unrelated packages' build scripts |
L |
key is script-name-scoped; verify pnpm install behavior unchanged for the rest of the workspace in T4 |
| nextjs-component has a different dep shape |
L |
T1 verifies per-app instead of assuming |
🤖 Generated with Claude Code
Why
e2e/nextjs+e2e/nextjs-componentcurrentlypnpm deploy --legacythe app into a mktemp dir inside playwright'swebServer.command— a full fresh registry resolution of the workspace graph on every run, under one 180s timeout (the #435 flake). The deploy exists only to give Next/Turbopack a real non-symlinkednode_modules(serverExternalPackagesrequires resolution undernode_modules/; workspace symlinks resolve outside it — see b1a96d8).Spike-verified (3 rounds, evidence in session): pnpm's own primitives replace the whole dance —
dependenciesMeta: {"@conciv/it": {"injected": true}}installs a real hard-copy. In-placenext devboots in 0.3-1s, widget spec green, and the builtin server extensions load (plain symlinks reproducibly logfailed to load builtin server extensions … MODULE_NOT_FOUNDfrom the terminal extension's dynamic require and go green anyway — silent coverage loss, which disqualifies the zero-injection option).syncInjectedDepsAfterScripts: ["build"](pnpm ≥10.5, top-level key in pnpm-workspace.yaml) closes the staleness gap: verified the sync fires when turbo spawns the build (turbo shells topnpm run), fires on directpnpm --filter run build, and recreates missing/new dist files (inode-verified hardlinks). CI order is safe:test:e2edependsOn^build, and the build is what triggers the sync.e2e/nextjs/packed/*) is orthogonal and untouched — it remains the published-shape verifier.Tasks
T1 — injection config (XS)
dependenciesMeta.injectedfor the @Conciv runtime deps of both nextjs e2e apps (verify which deps each app's runtime code actually imports — fornextjsit is@conciv/itonly; verifynextjs-componentequivalently). AddsyncInjectedDepsAfterScripts: ["build"]to pnpm-workspace.yaml. Acceptance: injected copies are real dirs with current dist; marker-verified resync afterpnpm turbo run build --filter=@conciv/it --force.T2 — in-place webServer (S)
Both playwright configs:
webServer.command= in-placepnpm exec next dev --port <port>only; delete the mktemp/deploy shell chain; tighten webServer timeout to an honest 60s. No deploy helpers, no CLIs, no root-walkers anywhere.T3 — extension-load guard (S)
The silent-failure mode found in the spike must be a test failure, not a log line: add a positive assertion that builtin server extensions actually loaded (prefer asserting through real behavior/engine surface — e.g. an extension-provided UI or endpoint visible in the existing spec flow; implementer picks the least-invasive honest signal and states it in the PR). A run where extensions fail to load must go red.
T4 — gates + CI validation (S)
Full local gates; then verify on the PR that CI's install→build→e2e order produces fresh injected copies (the spike's mechanism analysis says yes — confirm empirically on the PR run, quote the timings). Expect e2e consumer shard time to drop by the deploy duration.
T5 — cleanup (XS)
Close PR #443 as superseded (its diagnosis was right; this replaces its mechanism).
Closes #435. RemoveCONCIV_DEPLOY_FRESHreferences if any landed anywhere.Deferred, documented
@conciv/it's transitive workspace deps remain symlinked one level down (spike: extensions load correctly with top-level injection alone). If a transitive-symlink resolution bug surfaces later, the lever isdependenciesMeta.injectedinsidepackages/it— do not apply pre-emptively.Risks
pnpm installbehavior unchanged for the rest of the workspace in T4🤖 Generated with Claude Code