Repository navigation
Description
Phase 1 — Low-risk multi-framework fixes (package managers, routing, host binding, env)
Part of the multi-framework compatibility effort (PR #45, single PR, gated per phase). Builds on Phase 0 (#42, the mock-writer registry). Phase 1 is the batch of low-risk, high-value correctness fixes organized into three file-disjoint lanes so they implement in parallel without conflicts.
Non-goal: server-side data mocking for SvelteKit/Astro/Remix — that's Phase 2. Lane A only does the host/allowedHosts patch, not data mocks.
File-disjointness (safe parallel execution)
| Lane | Source owned | Tests owned |
|---|---|---|
| A | stack.py, config.py, discovery.py, mock_server.py, Dockerfile |
test_stack/config/discovery/mock_server |
| B | routing.py, routes.py |
test_routing/routes |
| C | env_inject.py |
test_env_inject |
No lane edits main.py, visual.py, or renderpr_config.py.
LANE A — Launch/boot robustness
- A1 Yarn Berry flag (W4):
build_install_command(stack.py:89) emits--frozen-lockfilefor all yarn; Berry v2+ needs--immutable. Parse thepackageManagerfield (yarn@4.5.0→berry,yarn@1.22→classic;.yarnrc.ymlas secondary signal) and branch the flag. Threadyarn_majorthroughbuild_launch_profile. - A2 Dockerfile corepack (W4):
corepack prepare yarn@stable(Dockerfile:23-27) force-pins Yarn 4 globally. Switch tocorepack enable+ let the repo'spackageManagerdrive the version; pin a classic fallback (yarn@1.22.x) for repos with no field. - A3 pnpm workspace root (W5):
find_workspace_root(discovery.py:53-74) only reads package.jsonworkspaces; also recognizepnpm-workspace.yamlas a root marker. - A4 npm-shrinkwrap (W5): add
"npm-shrinkwrap.json": "npm"toLOCKFILES(config.py:40) → restoresnpm ci+ caching. - A5 allowedHosts via Phase-0 registry (W2): add
SvelteKitMockWriter/AstroMockWriter/RemixMockWriterin mock_server.py whosewrite_dev_origin_allowlistpatches Vite allowed-hosts — SvelteKit/Remix →vite.config.*(reusewrite_vite_allowed_hosts), Astro → newwrite_astro_allowed_hostspatchingastro.config.*vite.server.allowedHosts. Register them; shrink the bare no-op loop to("cra","spa"). Deliberately replaces the Phase-0 locktest_sveltekit_astro_allowlist_parity_noop(this behavior change is intended, not test-weakening).test_stub_writers_generate_nothingstays green (doesn't call the allowlist). - A6 Remix host binding (W3): add
remixto_HOST_FLAG_FRAMEWORKS(stack.py:22) so--hostis appended; setFRAMEWORK_DEFAULT_PORTS["remix"]=5173(config.py:54; 3000 stays a candidate). - A7/A8 robustness (W8):
DEV_SERVER_START_TIMEOUT60→120 (config.py:27); add5174,4322to candidate ports (config.py:62); Dockerfile Node 20→22 (setup_22.x); pin pnpm + bun versions.
Lane A commits: (1) yarn berry flag, (2) config: shrinkwrap+remix-port+ports/timeout, (3) pnpm-workspace root, (4) remix host bind, (5) allowedHosts writers + replace parity test, (6) Dockerfile.
LANE B — Routing inference correctness
- B1 Next App Router (BLOCKER):
file_to_route(routing.py:91) doesn't strip route groups(folder),@slotparallel routes, or(.)/(..)/(...)intercepting markers, and doesn't recognizeroute.tsas API. Add_normalize_next_segments(drop groups/slots, returnNonefor intercepting +route.*files); reuse inis_layout_file. - B2 Remix flat-routes (BLOCKER): rewrite
RemixStrategy.file_to_route(routing.py:250) — protect[.]escapes before splitting on.; strip trailing_(layout opt-outblog_.post→/blog/post); drop leading-_pathless layouts and(optional)segments;_index→/;$param/splat→None. - B3 BFS extensions (Astro/SvelteKit):
_find_importers(routes.py:113) only scans.ts/.tsx/.js/.jsx, never reaching.astro/.sveltepages. Addsource_extensionsto each strategy (Astro +.astro, SvelteKit +.svelte), thread into_find_importers/_bfs_to_pages; anchor stem matching ([./]{stem}(["'/.])) soindexdoesn't matchreindexand directory imports resolve. - B4 SvelteKit coverage: broaden
_PAGE_RE/discovery to+page.{svelte,ts,js,server.ts,server.js,md}; optional[[param]]/rest[...]→ drop the segment and yield the concrete URL (only required[param]→None). - B5 Hybrid Next:
_resolve_next_variant(routing.py:293) picks app when bothapp/+pages/exist. AddNextHybridStrategycomposing app+pages (union routes); registernext-hybrid.
Lane B commits: (1) Next groups/slots/intercepting/route.ts [BLOCKER], (2) Remix flat-routes [BLOCKER], (3) per-strategy source_extensions + SvelteKit coverage, (4) BFS importer extensions + anchored stem, (5) hybrid Next union.
LANE C — Env-var prefix awareness
- W7:
build_injected_env/write_env_local(env_inject.py:69-118) write a flat.env.localwith no awareness of client-exposure prefixes. AddFRAMEWORK_PUBLIC_PREFIXES(next→NEXT_PUBLIC_, vite→VITE_, sveltekit/astro→PUBLIC_, cra→REACT_APP_, remix/spa→None) local to env_inject.py. Extendbuild_injected_env(..., framework=None)to WARN when an injected key lacks the framework's public prefix; Remix gets an info log that.env.localisn't auto-loaded (process-env path already delivers it). Defaultframework=None→ no warning (back-compat). Warning only — never auto-rewrite vars.
Lane C commits: (1) framework client-prefix warnings + tests.
Test rules (all lanes)
Failing-test-first per AGENTS.md; full suite green after every commit; coverage ≥80%. The only existing test intentionally changed is Lane A's test_sveltekit_allowlist_parity_noop (A5). Run: .venv/bin/python -m pytest.
Activity
feat(env): warn when injected vars lack framework client prefix
fix(stack): yarn berry --immutable via packageManager detection (A1)
fix(routing): strip Next route groups/parallel/intercepting + exclude…
Merge Phase 1 lanes A/B/C (package managers, routing, host binding, env)
feat(env): wire framework into build_injected_env call site