FoldKit 0.163 Migration Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Upgrade all three active workspaces to FoldKit 0.163 and Effect rc.116, then prove the new Astro document and SSR contracts without weakening the package boundaries.
Architecture: Keep apps/web responsible for canonical URL policy, boot fixtures, and any future pointer interaction. Keep @opsydyn/astro-foldkit responsible for server/client document handoff and Astro lifecycle, and keep @opsydyn/foldkit-viz pure and framework-free. Consume actual 0.162/0.163 API changes only where the repository has real usages; do not add release-tour examples.
Tech Stack: Bun workspaces, FoldKit 0.163.0, Effect 4.0.0-rc.116, Astro 7.1.1, TypeScript 6.0.3, @foldkit/vite-plugin 0.24.0, Vitest, bun:test, Oxlint, Oxfmt, and the existing Astro Cloudflare build.
Spec: docs/superpowers/specs/2026-09-20-foldkit-0-163-migration-design.md
Global Constraints
- Target FoldKit
0.163.0in all direct workspace consumers. - Target Effect
4.0.0-rc.116and the matching@effect/platform-browserrelease. - Target
@foldkit/vite-plugin0.24.0after confirming its peer metadata. - Use the Oxlint FoldKit plugin release whose metadata supports FoldKit 0.163; do not weaken rules to hide incompatibility.
- Keep canonical URL selection application-owned; never infer canonical URLs from
Astro.urlor browser location. - Keep
@opsydyn/foldkit-vizfree of runtime dependencies on FoldKit, Effect, Astro, HTTP, Machine, and Port APIs. - Do not add synthetic
keyBindings,EntryGates,Stream.mapBoth, ormodifyFieldsexamples. - Leave
Subscription.animationFrame({ toMessage })andSubscription.lift({ toParentMessage })unchanged. - Defer pointer-identity interaction changes to a later visual slice.
- Do not rewrite historical migration specs, plans, or generated changelogs.
- Use
bun run checkfor the repository lint/format gate; Biome is not part of this repository.
Review Focus
- Canonical with omitted
ogUrl: an explicitDocument.canonicalmust preserve FoldKit 0.163’s canonical/ogUrlsemantics. Test this inpackages/astro-foldkit/test/unit/server-document.test.ts. - No metadata inference: a page that omits canonical, language, and direction must remain free of those fields. Retain and extend the omission test in
packages/astro-foldkit/test/unit/server-document.test.ts. - SSR artifact shape: the server build must omit the unrendered root
dist/client/index.htmlwhile retaininggreeting-static/index.html. Test this inpackages/astro-foldkit/test/integration/astro-page-smoke.test.ts. - Inbound message during boot: an inbound Stateflow Port event sent immediately after
Runtime.embedmust be processed once after boot. Test this inapps/web/src/apps/stateflow/main.scene.test.ts. - Package boundary compatibility: packed server imports, consumer typechecking, FoldKit record types, and the final old-API search must remain green. Test this through
packages/astro-foldkit/test/integration/package-import-smoke.test.ts,packages/foldkit-viz/test/foldkit-compatibility.test.ts, and the final workspace audit.
Task 1: Establish the FoldKit 0.163 dependency contract
Files:
- Modify:
apps/web/package.json - Modify:
packages/astro-foldkit/package.json - Modify:
packages/foldkit-viz/package.json - Modify:
package.jsononly if the current@foldkit/oxlint-pluginmetadata rejects FoldKit 0.163 - Modify:
bun.lock
Interfaces:
-
Consumes: the approved 0.163 dependency contract and the registry peer metadata for the Vite/Oxlint FoldKit plugins.
-
Produces: one resolved FoldKit
0.163.0line, Effect rc.116 line, compatible Vite/Oxlint tooling, and package peer ranges that reject the tested-incompatible FoldKit 0.161 line. -
Step 1: Capture the baseline before changing manifests.
Run:
Terminal window bun typecheckbun run checkbun run testRecord the exit status and the existing error summary in the task notes. Do not fix unrelated baseline failures in this task.
-
Step 2: Verify upstream peer metadata.
Run:
Terminal window bunx npm view @foldkit/vite-plugin@0.24.0 peerDependencies --jsonbunx npm view @foldkit/oxlint-plugin version peerDependencies --jsonConfirm that the selected Vite plugin accepts FoldKit 0.163 and Effect rc.116.
@foldkit/vite-plugin@0.24.0is the selected release because its published peer metadata accepts rc.116. Keep@foldkit/oxlint-plugin@0.14.0if its metadata accepts the target; otherwise select the first registry release whose peer metadata accepts FoldKit 0.163 and record that concrete version in the root manifest. -
Step 3: Update direct dependencies.
Set these manifest values:
{"foldkit": "0.163.0","effect": "4.0.0-rc.116","@effect/platform-browser": "4.0.0-rc.116","@foldkit/vite-plugin": "^0.24.0"}Apply them to the existing locations in
apps/web/package.jsonand the relevant dependency/devDependency locations in both published packages. Do not add@effect/platform-browserto either published package unless an existing import requires it. -
Step 4: Raise package peer floors.
In
packages/astro-foldkit/package.json, change the FoldKit peer range to:"foldkit": ">=0.163.0 <0.164.0"In
packages/foldkit-viz/package.json, change the peer ranges to:"effect": ">=4.0.0-rc.116 <5.0.0","foldkit": ">=0.163.0 <0.164.0"Keep the existing optional-peer policy and the existing Astro peer range.
-
Step 5: Resolve and inspect the lockfile.
Run:
Terminal window bun installbun pm ls --all | rg -n "foldkit|effect|platform-browser|vite-plugin|oxlint-plugin"rg -n "foldkit@0\.163\.0|effect@4\.0\.0-rc\.116|platform-browser.*rc\.116|vite-plugin.*0\.24" bun.lockExpected: no workspace resolves FoldKit 0.161 or Effect rc.115, and no incompatible peer override is present.
-
Step 6: Build the two published packages against the new contract.
Run:
Terminal window bun run --filter @opsydyn/foldkit-viz buildbun run --filter @opsydyn/astro-foldkit buildExpected: both builds succeed before any source migration is attempted.
-
Step 7: Commit the dependency contract.
Terminal window git add package.json apps/web/package.json packages/astro-foldkit/package.json packages/foldkit-viz/package.json bun.lockgit commit -m "build: align workspace with FoldKit 0.163"
Task 2: Audit renamed APIs and update compatibility coverage
Files:
- Modify:
packages/foldkit-viz/test/foldkit-compatibility.test.ts - Modify: any application/package source file only if the audit finds a real 0.162/0.163 renamed API usage
- Test:
packages/astro-foldkit/test/integration/package-import-smoke.test.ts
Interfaces:
-
Consumes: the dependency and peer contract from Task 1.
-
Produces: a source tree with no obsolete 0.162/0.163 names and compatibility tests labelled for FoldKit 0.163.
-
Step 1: Run the post-install API audit.
Run:
Terminal window rg -n "\bevo\b|makeConstrainedEvo|keyboardShortcuts|fromEventFilterMap|fromEventFilterMapPreventDefault|\.fromEvent\(|Stream\.mapBoth|onSuccess|onFailure|shortcut:" apps packages --glob '!packages/foldkit-viz/docs/**' --glob '!**/dist/**'Expected: no active source hits for the renamed APIs. Do not edit
animationFrametoMessageorSubscription.lifttoParentMessageresults. -
Step 2: Migrate any real hits using the 0.163 names.
Use this exact mapping if the audit or typecheck exposes a hit:
evo -> modifyFieldsmakeConstrainedEvo -> makeModifyFieldsForkeyboardShortcuts -> keyBindingsshortcut -> keysSubscription.fromEvent mapper -> mapEventfilter-map event mapper -> filterMapEventStream.mapBoth onSuccess -> onElementStream.mapBoth onFailure -> onErrorKeep the existing constructor, model, and message ownership. Do not introduce a wrapper that supports both names.
-
Step 3: Update the viz compatibility test label and keep the record assertion.
In
packages/foldkit-viz/test/foldkit-compatibility.test.ts, change the suite description to:describe('FoldKit 0.163 document compatibility', () => {Keep the
TextDirectiondecoding assertion and theReturn<Model, never>record assertion. The test must still prove thatcommandsis optional when omitted. -
Step 4: Run focused type and package tests.
Terminal window bun run --filter @opsydyn/foldkit-viz typecheckbun run --filter @opsydyn/foldkit-viz test -- test/foldkit-compatibility.test.tsbun run --filter @opsydyn/astro-foldkit typecheckbun run --filter @opsydyn/astro-foldkit test -- test/integration/package-import-smoke.test.tsExpected: the pure viz package has no new runtime dependency and the packed Astro consumer still resolves
@opsydyn/astro-foldkit/serverunder Bun and Node. -
Step 5: Commit the API audit result.
Terminal window git add packages/foldkit-viz/test/foldkit-compatibility.test.ts packages/astro-foldkit/test/integration/package-import-smoke.test.tsgit commit -m "test: verify FoldKit 0.163 API compatibility"If Step 2 changed a source file, add that exact file path to the command. Do not stage generated
distor test artifact directories.
Task 3: Pin the Astro document metadata contract
Files:
- Modify:
packages/astro-foldkit/test/unit/server-document.test.ts - Modify:
packages/astro-foldkit/src/server-document.tsonly if the focused test proves the adapter loses the 0.163 canonical/ogUrlresult - Modify:
packages/astro-foldkit/README.md
Interfaces:
-
Consumes:
RenderedApplicationmetadata from FoldKit 0.163. -
Produces:
resolvePageDocumentbehaviour that preserves explicit canonical metadata, preserves the upstream canonical-to-ogUrlfallback, and leaves omitted metadata absent. -
Step 1: Refactor the fixture and add the canonical-only contract test.
Change the existing
pageConfigconstant into amakePageConfig(metadata)helper whoseviewspreads the supplied metadata into the returnedDocument. Use a model-based metadata factory so the existing default URL remains unchanged. The helper must use this shape:type PageMetadata = Pick<Document, 'canonical' | 'ogUrl'>;type MetadataFactory = (model: Model) => PageMetadata;const makePageConfig = (metadata: MetadataFactory = (model) => ({canonical: '',ogUrl: `https://example.com${model.pathname}`,}),) =>({Flags,Model: {},init: (flags: Flags) => ({ model: flags, commands: [{ _tag: 'IgnoredCommand' }] }),update: (model: Model, _message: Message) => ({ model }),view: (model: Model, h: HtmlBuilder<Message>): Document => ({title: `Server page ${model.locale}`,lang: model.locale,dir: textDirectionByLocale[model.locale] ?? 'Auto',...metadata(model),body: h.section([],[`${model.locale}:${model.pathname}:${model.routeLocale ?? 'missing'}`],),}),}) satisfies PageConfig<Flags, Model, Message>;const makePage = (metadata?: MetadataFactory) =>definePage<{ readonly locale: string; readonly noMeta?: boolean | '' }, Flags>(() => Promise.resolve(makePageConfig(metadata)),{flags: ({ request, url, params, props }) => ({locale: props.locale,pathname: `${request.method}:${url.pathname}`,routeLocale: params.locale,noMeta: shouldSkipMetadata(props),}),},);Preserve the existing fixture’s title, language, direction, body, and flag derivation inside
makePageConfig. A canonical-only page is thenmakePage(() => ({ canonical: 'https://example.com/ar/dashboard' }))evaluated with the existingcontextvalue. Add this assertion:it('preserves canonical semantics when the page omits ogUrl', async () => {process.env.FOLDKIT_BUILD_ID = 'resolver-build';const resolved = await resolvePageDocument(makePage(() => ({ canonical: 'https://example.com/ar/dashboard' })),context,);expect(resolved.canonical).toBe('https://example.com/ar/dashboard');expect(resolved.ogUrl).toBe('https://example.com/ar/dashboard');});Reuse the existing
Flags,PageConfig, and context helpers rather than creating a second test harness. The test must exerciseresolvePageDocument, not only a hand-built adapter record. -
Step 2: Run the focused test and inspect the actual failure.
Terminal window bun run --filter @opsydyn/astro-foldkit test -- test/unit/server-document.test.tsThis establishes whether FoldKit 0.163 already supplies the fallback through
RenderedApplicationor whether the Astro adapter loses it. -
Step 3: Make the smallest adapter correction if required.
If the focused test shows that the upstream
RenderedApplicationcontains the expected fallback, leavepackages/astro-foldkit/src/server-document.tsunchanged. If it shows that the adapter receivescanonicalwithoutogUrl, preserve the 0.163 contract at this boundary by changing only the optional metadata mapping soogUrlfalls back to the explicit canonical value; never derive either value fromPageContext.url. -
Step 4: Retain the omission and
noMetatests.Confirm the existing omission test still expects exactly
{ title: 'Only title en' }and that thenoMetatest still proves island metadata suppression does not suppress server document metadata. -
Step 5: Document the application-owned URL rule.
In the server-rendering section of
packages/astro-foldkit/README.md, add a short “Canonical metadata” paragraph with this contract:definePage does not infer a canonical URL from Astro.url, route parameters, or the browser location. The page view must return Document.canonical when the application has a canonical identity. Query parameters and alternate representations remain application-owned. When FoldKit supplies the 0.163 canonical-to-ogUrl fallback, the resolver passes it through unchanged. -
Step 6: Run the unit and greeting metadata tests.
Terminal window bun run --filter @opsydyn/astro-foldkit test -- test/unit/server-document.test.tsbun run --filter @opsydyn/web test -- src/apps/greeting/main.page.test.tsExpected: explicit greeting canonical/
ogUrl, canonical-only fallback, omitted metadata, andnoMetabehaviour all pass. -
Step 7: Commit the document contract.
Terminal window git add packages/astro-foldkit/src/server-document.ts packages/astro-foldkit/test/unit/server-document.test.ts packages/astro-foldkit/README.mdgit commit -m "test: codify FoldKit document metadata semantics"
Task 4: Cover SSR artifact output and boot-time subscription buffering
Files:
- Modify:
packages/astro-foldkit/test/integration/astro-page-smoke.test.ts - Modify:
apps/web/src/apps/stateflow/main.scene.test.ts
Interfaces:
-
Consumes: the upgraded Astro renderer, existing
greeting-staticpage, existing Stateflow replay Port, and existing runtime test harness. -
Produces: integration evidence for the 0.163 SSR output change and an app-level regression test for an inbound Port message arriving immediately after embed.
-
Step 1: Add a build-output existence helper to the Astro smoke test.
Extend the existing
node:fs/promisesimports withaccessand add:const fileExists = async (file: string): Promise<boolean> => {try {await access(file);return true;} catch {return false;}}; -
Step 2: Add the root-template regression assertions.
Immediately after the existing
bun run buildcall inastro-page-smoke.test.ts, assert:expect(await fileExists(path.join(webDir, 'dist', 'client', 'index.html'))).toBe(false);expect(await fileExists(path.join(webDir, 'dist', 'client', 'greeting-static', 'index.html')),).toBe(true);Keep the existing static HTML metadata assertions; they prove that removing the unrendered root template did not remove valid prerendered output.
-
Step 3: Add an early inbound Port test.
In
apps/web/src/apps/stateflow/main.scene.test.ts, add a test that uses the existingRuntime.embedsetup, sendsfixture[0]immediately afterRuntime.embedreturns, and waits for the app-owned update recorder to observe exactly oneMessage.ReceivedReplayEvent:it('processes an inbound replay event sent during runtime boot', async () => {const received: AppMessage[] = [];const container = document.createElement('div');document.body.appendChild(container);const handle = Runtime.embed(Runtime.makeElement({Model,init: () => ({ model: initModel }),update: (model, message) => (received.push(message), update(model, message)),view: (_model, h) => h.div([], []),subscriptions,ports: { inbound: { replay: ReplayEventPort } },container,}),);handle.ports.replay.send(fixture[0]);await vi.waitFor(() => {expect(received).toEqual([Message.ReceivedReplayEvent({ event: fixture[0] })]);});handle.dispose();container.remove();});Keep the existing telemetry test as the outbound Port/Command coverage. This test intentionally does not introduce a ManagedResource fixture because the application has no current managed resource; it pins the repository’s actual boot-time Subscription/Port path.
-
Step 4: Run the focused integration tests.
Terminal window bun run --filter @opsydyn/web test -- src/apps/stateflow/main.scene.test.tsbun run --filter @opsydyn/astro-foldkit test -- test/integration/astro-page-smoke.test.tsExpected: Stateflow processes the early replay once, and the Astro production build has no unrendered root template while retaining static greeting output.
-
Step 5: Commit the lifecycle and SSR coverage.
Terminal window git add apps/web/src/apps/stateflow/main.scene.test.ts packages/astro-foldkit/test/integration/astro-page-smoke.test.tsgit commit -m "test: cover FoldKit 0.163 boot and SSR changes"
Task 5: Refresh current compatibility guidance
Files:
- Modify:
AGENTS.md - Modify:
CLAUDE.md - Modify:
docs/roadmap.md - Modify:
packages/astro-foldkit/README.md - Modify:
packages/foldkit-viz/README.md - Modify:
packages/foldkit-viz/test/foldkit-compatibility.test.tsonly if Task 2 did not already update its suite label
Interfaces:
-
Consumes: the tested versions and behaviour from Tasks 1-4.
-
Produces: repository guidance that describes the current FoldKit 0.163 contract without rewriting historical migration records.
-
Step 1: Update agent guidance.
In
AGENTS.md, change the stack line to FoldKit0.163.0and Effect4.0.0-rc.116. Keep the existing named-import, record-return, no-inline-runtime, and no-dynamic-import guidance.In
CLAUDE.md, replace the stale “Currently onfoldkit@0.126.0” section with current 0.163 guidance. Retain the useful named-export andfoldkit/httpnotes, and add the 0.163 names only as migration guidance:modifyFields,keyBindings,mapEvent, andfilterMapEvent. State explicitly that existinganimationFrame.toMessageandlift.toParentMessageremain valid. Also change the old tuple-shapedStepexample to the current record form, omittingcommandswhen no commands are produced. -
Step 2: Update package documentation.
In
packages/astro-foldkit/README.md, update the compatibility paragraph and peer-dependency table to FoldKit0.163.x, Effect rc.116 where mentioned, and Vite plugin0.24.x. Keep the canonical metadata paragraph from Task 3.In
packages/foldkit-viz/README.md, update the compatibility line to FoldKit0.163.xand preserve the statements that the package is framework-free, pure, and does not own server or remote-data policy. -
Step 3: Update the current roadmap target.
In
docs/roadmap.md, change only the current compatibility paragraph from FoldKit 0.161/Effect rc.115/Vite plugin 0.22 to the tested 0.163/rc.116/0.24 line. Leave historical 0.136 and Stateflow entries intact. -
Step 4: Check for stale active guidance.
Run:
Terminal window rg -n "0\.126\.0|0\.161\.0|0\.161\.x|rc\.115|0\.22\.x|keyboardShortcuts|makeConstrainedEvo|fromEventFilterMap" AGENTS.md CLAUDE.md README.md apps packages docs/roadmap.md --glob '!packages/foldkit-viz/docs/**' --glob '!docs/superpowers/**' --glob '!**/dist/**'Expected: no stale active compatibility statement remains. Historical migration documents are excluded deliberately.
-
Step 5: Commit the guidance update.
Terminal window git add AGENTS.md CLAUDE.md docs/roadmap.md packages/astro-foldkit/README.md packages/foldkit-viz/README.md packages/foldkit-viz/test/foldkit-compatibility.test.tsgit commit -m "docs: update FoldKit 0.163 guidance"
Task 6: Run the release qualification gates
Files:
- Modify: none unless a focused verification exposes a defect in the preceding task
- Test: all workspace package tests, typechecks, lint/format checks, builds, and packed-consumer smoke tests
Interfaces:
-
Consumes: all committed migration tasks.
-
Produces: a clean, verified branch ready for a separate package version/release decision; this task does not publish or push.
-
Step 1: Run focused package checks.
Terminal window bun run --filter @opsydyn/foldkit-viz checkbun run --filter @opsydyn/foldkit-viz typecheckbun run --filter @opsydyn/foldkit-viz testbun run --filter @opsydyn/astro-foldkit checkbun run --filter @opsydyn/astro-foldkit typecheckbun run --filter @opsydyn/astro-foldkit test -
Step 2: Run the web app checks and builds.
Terminal window bun run --filter @opsydyn/web checkbun run --filter @opsydyn/web typecheckbun run --filter @opsydyn/web testbun run --filter @opsydyn/web build -
Step 3: Run the repository gates.
Terminal window bun run checkbun typecheckbun run testExpected: all three commands exit 0. Use the workspace script
bun run testso the gate runs the active workspaces rather than traversing the vendoredfoldkit-maincheckout. -
Step 4: Inspect the final package surface.
Terminal window (cd packages/astro-foldkit && bun pm pack --dry-run)(cd packages/foldkit-viz && bun pm pack --dry-run)git diff --check origin/main...HEADgit status --short --branchConfirm that package tarballs include the documented exports and no generated
dist,artifacts, or temporary smoke files are left as untracked changes. -
Step 5: Review the complete diff before release work.
Terminal window git log --oneline --decorate -6git diff --stat origin/main...HEADgit diff origin/main...HEAD -- package.json apps/web/package.json packages/astro-foldkit/package.json packages/foldkit-viz/package.json packages/astro-foldkit/src packages/astro-foldkit/test packages/foldkit-viz/test apps/web/src/apps/stateflow AGENTS.md CLAUDE.md docs/roadmap.mdVerify that the diff contains only the approved dependency, compatibility, canonical/SSR, boot-test, and documentation work. Package version bumps, changelog generation, tagging, and pushing are separate release actions.