Skip to content

RFC: web/wasm leaves the CLI core (RFC #406, web phase) #466

Closed

Description

@apotema

Status: revision 4. D1–D13 approved by the owner; implementation planned with the review qualifications below.

Revision 4 (owner decision, 2026-09-27): retain watch and live reload for browsers. This supersedes revision 3's removal of watch mode.

  • Keep labelle run --watch through the web provider: generic watching and rebuild supervision in core; HTTP serving and browser reload in the provider.
  • Require explicit watch capability, consistent successful-output publication, session-change diagnostics and child cleanup (§3.4 and §4). Desktop/native watch support is outside this migration.
  • Restore the separate watch-supervision PR A2 and browser acceptance tests. Carry forward the prebuild ignore/replan fix described in wasm serve --watch: refresh the watcher's ignore list when prebuild outputs change #463; its earlier closure does not remove that requirement.
  • The legacy labelle wasm serve --watch command still goes away with labelle wasm; browser watch uses labelle run --watch.
  • D1–D13 are accepted (§7). See the D1–D3/D5–D13 approval and D4 approval.

Revision 2's environment rules, FP-before-3.0 release order and review acceptance requirements remain in effect.

This is the web phase of RFC #406. v2.0.0 delivered the provider infrastructure and moved Android out of the core. This RFC plans the next step: moving web/wasm out of the CLI core into the web provider (labelle-toolkit/labelle-web), the same way Android moved into labelle-android.

The research is read-only, against origin/main: cli 8188800 (v2.0.1), web 1d669ee (v0.2.0), assembler b239c9d. File sizes are line counts.

0. Findings that shape the plan

  1. --platform=wasm already requires the web provider. web v0.2.0 already replaces core serve and export whenever it is pinned:

    • web's replace run (serve) and replace bundle (export) hooks take over from core.
    • The legacy labelle wasm serve|export is refused whenever a run replacement exists (pipeline/args_resolve.zig:236-257, pipeline/install.zig:203-206).
    • So core serve and export only run for a provider that owns wasm but has no replace run. In practice that is only the CLI's own fixture, test/fixtures/wasm-provider.
    • Core serve/http.zig, serve/server.zig, export.zig and pipeline/export_output.zig are therefore dead weight for real users. web already carries copies, "extracted from labelle-cli at 5eccdbc": src/provider/serve.zig (2247 lines) and src/provider/export.zig (1018).
  2. Core still does four wasm jobs the provider can't take over today:

    • defaulting to ReleaseSafe (pipeline.zig:263-264);
    • activating emsdk after the fingerprint pass (pipeline/generate.zig:190-218, which includes a Python preflight);
    • the managed-emsdk environment for the core zig build (runner.buildWasmEnv, runner.zig:22-90);
    • --watch: watcher, rebuild loop and live reload (pipeline/run.zig:218-317). Generic watching stays in core; browser live reload moves to web.

    Each of these is a contract gap (§3).

  3. All three backends already honour an external EMSDK and otherwise fall back to the emsdk Zig dependency:

    • bgfx backend.hook.zig:388-424, sokol :397-430, raylib :107-142.
    • The assembler's ensureEmsdkActivated preflight short-circuits when EMSDK is set (codegen/emsdk_preflight.zig:26-36).
    • So one generic "provider contributes env to the core compile" mechanism lets web use a single shared emsdk for every backend, with no emcc knowledge in core.
  4. Flying Platform already works around the core activation. Its ci.yml:363-386 and deploy-pages.yml:78-84 run ./emsdk install/activate 4.0.9 by hand on every zig-pkg/*/emsdk: the graph holds two emsdk hashes, and core activates only one (emsdk_activate.zig:151-230). FP also passes --optimize=ReleaseSafe explicitly. So FP keeps building while core loses wasm, which makes this train lower-risk than Android's.

  5. The file watcher is platform-neutral code wearing wasm names. Retain and rename its generic parts; remove HTTP coupling.

    • serve/tree.zig, serve/baseline.zig, serve/watch.zig, serve/cancel.zig, pipeline/watch.zig (WasmRebuildCtx) and pipeline/watch_replan*.zig never touch emcc.
    • Only the HTTP live-reload endpoint is web-specific.
    • Providers can't build, so the rebuild loop stays in core.
  6. Python has a second, non-wasm user. .prebuild steps get the managed interpreter through python_provision.autoWireEnv (pipeline/install.zig:51-75).

  7. web v0.2.0 is capped at >=1.0.0 <1.2.0, so it gets the 1.1.0 wire: no target_dir and no run options.

  8. Allowlist count: the guard's allowed_files has 53 entries: 52 legacy files, including stb_image.h/.c, plus the guard itself.

  9. Studio (out of scope for this train) depends on core wasm features:

    • doctor --json's wasm capability (ToolchainGate.tsx, services/doctor.ts, src-tauri/src/lib.rs);
    • labelle install python;
    • labelle build --platform=wasm.

1. Inventory (labelle-cli origin/main)

1a. Toolchain provisioning

File What it does Fate
emsdk_toolchain.zig (777) Managed emsdk: precedence LABELLE_EMSDK > --emcc > .emsdk_version > engine-derived > 4.0.9 pinned commit; ensureInstalled, cmdInstallEmsdk, cmdEmsdkWhich → web
emsdk_cache.zig (177) Layout ~/.labelle/emsdk/<ver>/… → web (cache_dir)
emsdk_activate.zig (759) In-place activation of the fetched zig-pkg emsdk (dep-hash lookup, fs2 lock) → web (activate every zig-pkg/*/emsdk)
python_provision.zig (324) python-build-standalone 3.11.9, pinned sha256; autoWireEnv Stays generic (D2); scrub the wording
install.zig:40-67 install python, install emsdk emsdk → web
cli.zig:42-43,151-157 labelle toolchain emsdk → web command
launcher_manifest.zig:19-23 .emsdk_version → providers/web.json
args.zig:13,496-530 --emcc Delete (D9)
docker.zig:5-54,230-313 Wasm container branch, STOPSIG stdlib sed Delete (D5)

1b. How labelle build --platform=wasm works today

  1. Resolve: the target is resolved to the web provider, and parsed.platform = .wasm is kept, because the assembler schema still needs it.
  2. Compatibility: compatibility.zig:10-14 hard-codes which backends support wasm.
  3. Generate: defaults to ReleaseSafe; runs the ASTC prepass for .web; runs the assembler; then fixFingerprints fetches emsdk into zig-pkg.
  4. Wasm-only block (generate.zig:190-218): Python wiring and preflight, then emsdk version resolution and activation.
  5. Build: runner.buildWasmEnv sets EMSDK, EM_CONFIG and PATH; then core zig build; then web's after build shell hook.
  6. Run and bundle: web's replacements. The core fallback branch (pipeline/run.zig:194-318) runs only when there is no replacement. legacyRunBranch(.wasm) = true exempts wasm from NoRunReplacement.

1c. Serve, watch and live reload

File Fate
serve.zig, serve/server.zig (213), serve/http.zig (631) Delete (web has copies)
serve/watch.zig, serve/tree.zig, serve/baseline.zig, serve/testing.zig Move generic watch code/tests out of serve/; remove wasm-specific names
serve/cancel.zig Retain generic cancellation support outside serve/
pipeline/watch.zig (747), pipeline/watch_replan*.zig Retain generic rebuild/replan supervision; refresh prebuild steps and ignore paths together
pipeline/run.zig:34-141 (serveNoBuild), :194-318 (core wasm branch) Remove legacy serving; replace wasm-specific watch path with generic provider supervision
cli.zig/args.zig `wasm serve export` parsing and help
provider_dispatch.zig reserved "wasm" namespace Unreserve
cli#463 (watcher ignore list) Its fix is required in A2 despite the earlier closure: replan before prebuild and atomically refresh steps plus ignore paths

1d. Export and bundle

  • export.zig (884) and pipeline/export_output.zig (209): delete. web's export supersedes them and adds shell staging and its own destructive-output guard.

1e. ASTC for web

1f. Config, args, help, doctor

  • help.zig legacy wasm section.
  • doctor.zig: checkPython, checkEmsdk, and the --json wasm capability (Studio).
  • runner.buildWasmEnv.
  • compatibility.zig wasm whitelist and hint text.
  • Comment-only sites in prebuild.zig, material_toolchain.zig, provider_hooks.zig, assembler_proc.zig, pipeline/testing.zig.

1g. Tests and CI

  • ~40 wasm serve/export specs in args_tests.zig.
  • Module tests in export, serve and watch.
  • test/provider_hooks_e2e.py:434-460; test/provider_targets_e2e.py:171-194,309-317.
  • test/fixtures/wasm-provider.
  • CI: ci.yml:166-276 (Docker wasm); toolchain.yml:17-19,73-121 (Python from zero, doctor-json contract, from-zero wasm).

2. The web provider today (v0.2.0)

  • Manifest: namespace = "web", targets = {"wasm"}, command_contract = ">=1.0.0 <1.2.0".
  • Hooks:
    • shell (after build): stages the loader and stamps data-wasm-bytes;
    • serve (replace run): serves, with no watch;
    • export (replace bundle).
  • Commands: serve and export act on existing output (--port, --no-open, --output, --zip, --platform).
  • Settings: {build_dir, port, open_browser}, with no schema version.
  • Runtime: IndexedDB storage and the shell.
  • Duplicates core: serve (http, server, cancel), export plus ZIP plus wasm-opt, and the output guard.
  • Missing:
    • emsdk provisioning and pin;
    • doctor;
    • a default optimize;
    • run.args/target_dir (range capped below 1.2);
    • a settings schema version and export settings.

3. Contract gaps: an additive contract 1.3.0

Need Expressible today? Proposal
Toolchain env for the core compile (EMSDK, EM_CONFIG, PATH) No: a hook's env dies with the hook env_file on before generate, after generate and before build hooks
Persistent toolchain cache (~1 GB) Only by re-implementing LABELLE_HOME resolution cache_dir
Default optimize per target No: hard-coded in core Manifest .target_defaults (not wire)
Browser watch/reload No generic rebuild notification run.watch plus explicit run-replacement .watch = true capability
Optimize and run.args Yes, on 1.2.0 web moves to ≥1.2
Progress JSON The contract already requires NDJSON on stdout under --progress=json web prints diagnostics to stderr then

1.3.0 keys: supported_versions = {1.3.0, 1.2.0, 1.1.0, 1.0.0}, with the new keys gated by wire version.

3.1 cache_dir (PR A1)

  • Required on 1.3.
  • Absolute <LABELLE_HOME>/providers/<canonical-provider-id>/, where the canonical id is the pinned repo identity (github.com/<owner>/<repo>, normalised), not the project's plugin alias. Two projects that alias one provider share its cache.
  • Created by the CLI, and persistent across versions and projects.
  • The provider owns the layout inside. It must key toolchain installs by host (os/arch) and SDK identity (version plus pinned commit), and make concurrent installation safe: a lock plus atomic rename of a completed install. Two builds installing the same emsdk at once must end with one valid install and no partial tree.

3.2 Environment contributions: env_file (PR A1)

Where it applies. env_file is non-null on the before generate, after generate and before build hooks: an absolute, empty path the hook may write. It is null on all other hooks.

  • Contributions accumulate: every later zig invocation in the same build or rebuild sees the merged result of all contributing hooks that have already run.
  • That covers the generation-time fingerprint pass (which runs after the assembler, inside the generate step, so before generate contributions reach it), the compile, and the processes listed under "Scope" below.
  • Phase meaning is unchanged: before build still runs just before the compile.

File format. Strict JSON, decoded with unknown fields rejected:

{ "set": [ { "name": "EMSDK", "value": "/abs" } ], "path_prepend": [ "/abs/dir" ] }

Reserved names. Instead of a LABELLE_* prefix ban, a fixed list of CLI-owned names can't be set:

  • PATH (only via path_prepend), ZIG_GLOBAL_CACHE_DIR, ZIG_LOCAL_CACHE_DIR;
  • LABELLE_HOME, LABELLE_CONTEXT, LABELLE_OFFLINE, LABELLE_ZIG, LABELLE_ASSEMBLER, and every LABELLE_* name the CLI itself reads (kept as a single table in provider_contract.zig, with a guard test).

Other LABELLE_* names, such as LABELLE_SDL2_LIB for the SDL phase, are allowed. Names must match [A-Za-z_][A-Za-z0-9_]*.

Merge rules, deterministic:

  • Precedence: a hook contribution overrides the inherited environment, because a provider that provisions a toolchain must win over a stale shell EMSDK. A provider that wants to honour a user's inherited value reads it from its own environment and writes that value back.
  • Order: contributions are applied in hook execution order (hooks are already totally ordered: phases, dependencies, then id).
  • Conflicts: two hooks setting the same name to different values is an error naming both hooks. Setting the same value is fine.
  • Windows: names are compared case-insensitively for both conflicts and override (Path/PATH, EMSDK/Emsdk), and the inherited spelling of the key is kept.
  • PATH order: path_prepend entries go in hook order, each hook's list in its given order, all in front of the inherited PATH. Duplicates are removed keeping the first. Entries must be absolute; a relative entry is an error.

File states:

  • Missing: no contribution. This is normal.
  • Empty or malformed: the build fails before compiling and names the hook and the parse error.
  • Written but the hook failed: the file is ignored and the hook failure stands.

Freshness. The environment is built fresh on every build, from the inherited environment plus the contributions of the hooks that ran in that rebuild. A hook removed by a replan leaves nothing behind, and env_file paths are per-invocation temp files deleted afterwards.

Scope. The merged environment goes to:

  1. the core zig build compile;
  2. the generation-time zig build --list-steps fingerprint repair: see "Ordering" below;
  3. hooks and replacements that run after the contributing hook in the same invocation. That covers later generate hooks, after build, the replace build/replace bundle/replace run processes, and the after run hooks, so a replacement build sees the same toolchain.

It does not reach provider host-tool compilation (zig build install-provider for a provider's own tool). That runs with the plain inherited environment plus the managed host Zig, so a provider can't change how other providers' tools are built.

Ordering: the earlier toolchain consumer. Generation runs zig build --list-steps for fingerprint repair before the build phase, and that can already evaluate backend and toolchain configuration. So web's toolchain hook is a before generate hook: its EMSDK/EM_CONFIG/PATH contribution reaches the fingerprint pass and every later zig invocation.

  • The package mode (source = "package", activating the fetched zig-pkg/*/emsdk) needs the fetched tree. It runs as an after generate hook instead, and its contribution applies from the compile onward.
  • Clean-cache acceptance exercises the fingerprint pass under both modes (§8). The final compile is not assumed to be the first toolchain consumer.

3.3 Target defaults (PR A1, manifest, not wire)

  • The manifest declares, for example, .target_defaults = .{ .{ .target = "wasm", .optimize = .ReleaseSafe } }.

  • Only the target's owner (the provider that declares the target in .targets) may declare defaults for it. A default on a target the provider doesn't declare is a manifest error, and two declarations for one target are an error.

  • An explicit CLI flag always wins (--optimize, --release). The default applies only when the flag is absent, and the wire optimize reports the effective value.

  • Recompute effective defaults on every allowed build replan; if the effective optimize affects the running replacement, apply the restart rule below.

3.4 Browser watch (PR A2)

  • labelle run --watch --platform=wasm uses a long-lived web run replacement. Require both contract ≥1.3 and an explicit .watch = true capability on that replacement. Reject unsupported replacements with an actionable diagnostic; protocol negotiation alone is insufficient. Desktop/native watch is outside this migration.
  • On wire 1.3, run.watch is null outside watch sessions or an object with absolute generation_file and output_dir paths. Older wire versions omit it. Core initializes generation to 0 before starting the replacement; the provider serves the published output and polls generation to notify browsers to reload.
  • Publication and notification are separate. Build into isolated staging, finish all after-build hooks, then publish a complete generation and switch the served output consistently before advancing generation. The provider must never serve partially rewritten build output. Preserve the last successful output and generation on failure. Validate the publication mechanism on Windows as well as POSIX; a counter alone is not publication.
  • Code/assets trigger rebuilds. Build-only configuration changes may replan atomically. Changes affecting the running replacement require restart labelle run --watch: <what> changed and publish nothing: provider package/version/pin, replacement hook, backend, target, output directory, effective optimize, replacement settings or watch capability.
  • Replan changed project configuration before its prebuild phase; swap prebuild steps and their ignore set together (wasm serve --watch: refresh the watcher's ignore list when prebuild outputs change #463). Construct a fresh environment for each rebuild (§3.2).
  • The running replacement retains its session configuration; never silently combine an old server with incompatible new build plans.

4. Target architecture

labelle build  --platform=wasm -> before generate web/toolchain : ensure emsdk (cache_dir) -> env_file {EMSDK,EM_CONFIG,PATH}
                                 -> assembler generate; zig-pkg fetch + fingerprint pass (sees the env)
                                 -> core zig build (-Doptimize from --optimize | target_defaults) with env merged
                                 -> after build   web/shell     : stage + stamp index.html
labelle run    --platform=wasm [-- --port=N --no-open]
                                 -> same build, then replace run web/serve (blocks)
labelle run    --platform=wasm --watch -> same initial build, then web/serve + core rebuild supervision
labelle bundle --platform=wasm  -> same build, then replace bundle web/export
labelle web serve|export|doctor|toolchain [args]
labelle doctor                   -> core checks + web/doctor (emsdk, python)

Stays in core (generic), and why:

  • File watching, debounce, rebuild/replan orchestration and successful-build notification, with browser support delivered through web (§3.4).
  • Interrupt handling and session supervision. Server exit stops the watcher and cancels/reaps any in-flight build before session teardown. Forward termination to the child process group or Windows job object, then enforce cleanup; no child outlives the command. Only a clean status-zero replacement exit runs after run; signal death, timeout, forced termination and non-zero exit skip it. Cleanup always runs. Test interruption during provisioning, compilation and after-build hooks, not only an idle server.
  • labelle install python + autoWireEnv (D2). .prebuild needs it. Provider commands and hooks receive the same managed-Python PATH wiring.
  • The 1.3 keys, which are generic; the SDL and iOS phases reuse env_file and cache_dir.

Moves to labelle-web:

  • the emsdk pin, fetch, verify, activate, cache and which;
  • the in-place zig-pkg activation, as a fallback mode that activates every zig-pkg/*/emsdk (this fixes FP's two-hash workaround);
  • HTTP serve and live reload;
  • export;
  • doctor checks for emsdk and python-for-emsdk;
  • the ReleaseSafe default, as manifest data;
  • the wasm spike and segfault docs.

Deleted without replacement:

5. PR sequence

# Repo Title Depends on
A1 cli Contract 1.3.0 context: cache_dir, env_file (full rules §3.2), manifest target_defaults none
A2 cli Generic browser-watch supervision, explicit capability, publication and session-change rules (§3.4) A1
W labelle-web v0.3.0: contract ≥1.3, toolchain before-generate hook (+ after generate package mode), doctor/toolchain commands, settings v1, browser live reload A1, A2
B cli Remove web/wasm from core, refuse --docker for provider targets, shrink the guard A1, A2, W; FP migrated (F) before B is published
S labelle-assembler (optional) Preflight message points to the web provider; backend-supports-wasm check if absent none
R labelle-registry Add the web 0.3.0 record W
F flying-platform-labelle Pin web 0.3.0, drop the manual emsdk loops W, R

Backends (bgfx, sokol, raylib) need no PR: their wasm CI uses plain zig build with the emsdk dependency and honours EMSDK.

PR A1: contract 1.3.0 context (additive)

Files:

  • provider_contract.zig: the version, the cache_dir and env_file keys, and validation and serialization.
  • provider_hooks.zig: cache_root, per-hook env_file allocation, read-back and merge (§3.2).
  • provider_manifest.zig: target_defaults.
  • pipeline.zig: effective_optimize from target_defaults.
  • pipeline/generate.zig: the accumulated env reaches the fingerprint pass.
  • pipeline/build.zig: apply the merged env to the compile.
  • Comment scrubs in prebuild.zig and material_toolchain.zig.
  • Docs.

Tests:

  • Decoder: 1.3 accepts the keys and ≤1.2 rejects them; negotiation.

  • Env merge: a unit table covering reserved names, conflicts, Windows case-insensitivity, PATH order and dedup, and missing/malformed files.

  • e2e:

    1. a fixture before generate hook's env reaches the fingerprint pass and the fixture build.zig;
    2. a hook removed between two builds leaves no value behind;
    3. target_defaults gives ReleaseSafe, and an explicit --optimize wins.

    They run on Windows too.

Guard: 53 → about 50 (recount in the PR).

Release: CLI 2.1.0, after A1 and A2 (D1).

PR A2: browser-watch supervision (additive)

  • Add the version-gated run.watch context and explicit run-replacement watch capability.
  • Retain generic watcher/replan/cancellation code under neutral names; remove its HTTP/server dependency.
  • Implement §3.4 publication and configuration rules, including wasm serve --watch: refresh the watcher's ignore list when prebuild outputs change #463's prebuild/ignore refresh.
  • Keep rebuild state separate from the running session; cancel/reap workers before freeing session state.
  • Test capability rejection, successful notification only after after-build hooks, failed-build output preservation, configuration changes, fresh rebuild environments, server exit and interruption during each build phase. Cover Windows process cleanup and publication.
  • Release with A1 in CLI 2.1.0; keep these as separate reviewable PRs.

PR W: labelle-web v0.3.0

Manifest:

  • command_contract = ">=1.3.0 <1.4.0", plus target_defaults.
  • Commands: serve, export, doctor, toolchain which|install [<ver>].
  • Hooks: toolchain (before generate: managed/root/inherited modes), toolchain-package (after generate: source = "package" mode), shell (after build), serve (replace run, .watch = true), export (replace bundle).

src/provider/emsdk.zig, ported from the CLI. It picks emsdk in this order:

  1. an already-set EMSDK with an existing emcc, passed through;
  2. settings emsdk.root;
  3. managed <cache_dir>/emsdk/<host>/<version-and-commit>/ (pinned commit, fs2 lock);
  4. emsdk.source = "package", which activates every zig-pkg/*/emsdk.

It writes env_file with EMSDK, EM_CONFIG and a path_prepend of upstream/emscripten.

Other changes:

  • Python: found on PATH, where core wires the managed one; if missing, it fails with "run labelle install python".
  • doctor: its --json keeps Studio's {id:"wasm", items:[python, emsdk]} shape (D7).
  • serve: reads run.args (--port=, --no-open). In watch mode it serves only the consistently published run.watch.output_dir, polls generation_file and reloads browsers after a successful generation change. Remove duplicated rebuild/file-watching code; core owns rebuilds.
  • Settings v1, strict:
    { "schema_version": 1, "port": 8080, "open_browser": true, "build_dir": null,
      "emsdk": { "version": "4.0.9", "source": "managed", "root": null },
      "export": { "platform": "none", "zip": false } }

Tests:

  • Host unit tests: version resolution, env_file, two-hash package mode, the generation-file poll.
  • An e2e against CLI A1/A2 with a fake emsdk.
  • A new wasm-from-zero job on ubuntu, macOS and Windows: clean HOME, labelle init, local: web, build --platform=wasm; assert the wasm magic and data-wasm-bytes. It moves from the CLI's toolchain.yml.

Release: GitHub Release v0.3.0, plus the codeload sha256.

PR B: remove wasm from core (breaking)

Deleted:

  • emsdk_activate.zig, emsdk_cache.zig, emsdk_toolchain.zig
  • export.zig, pipeline/export_output.zig
  • serve.zig, serve/http.zig, serve/server.zig
  • test/fixtures/wasm-provider/
  • the wasm spike and segfault docs, moved to labelle-web

Edited:

  • cli.zig, args.zig, args_tests.zig, help.zig: drop the wasm subcommands, toolchain emsdk, --emcc and the legacy help.
  • install.zig: remove install emsdk.
  • doctor.zig: remove the emsdk/python checks; --json per D7.
  • runner.zig: remove buildWasmEnv.
  • pipeline.zig, pipeline/generate.zig:190-218, pipeline/run.zig, pipeline/args_resolve.zig: remove the wasm branches.
  • pipeline/install.zig: legacyRunBranch keeps only .ios.
  • provider_dispatch.zig: unreserve wasm.
  • docker.zig: refuse --docker for provider targets.
  • compatibility.zig: delete the whitelist (D13).
  • python_provision.zig: scrub the wording, and wire the managed interpreter unconditionally when it exists.
  • launcher_manifest.zig: remove emsdk_version.
  • Comment scrubs in the files listed in §1f.
  • New docs/migrating-to-3.0.md.
  • CI: the Docker job becomes a desktop cross build; toolchain.yml drops the emsdk and wasm jobs.

Tests:

  • labelle wasm serve gives the unknown-command diagnostic, and nothing is generated.
  • NoRunReplacement applies to wasm.
  • The guard passes with the shrunken list.
  • The provider e2es run on 3 OSes.

Clean-HOME acceptance (fresh FP clone, empty HOME, no siblings, dev CLI, web as local: and then pinned):

  1. providers fetch, then build --platform=wasm with no --optimize. Expect -Doptimize=ReleaseSafe, no manual emsdk step, and emcc under <cache_dir>/emsdk/<host>/<version-and-commit>/.
  2. run --platform=wasm --watch: edit an asset and observe a browser reload; break a build and verify the last successful output remains served.
  3. bundle --platform=wasm --output dist, then FP's smoke.py --soak-seconds 150.
  4. doctor shows the web doctor rows.
  5. LABELLE_OFFLINE=1 reuses the cached toolchain.
  6. Windows: steps 1–3, after labelle install python.

Guard: expected to shrink toward 32 after generic watcher names and web policy are removed from the allowlist. Retained generic watch files are not deleted; recount in A1, A2 and B.

Release: CLI 3.0.0 (D1).

PR S (assembler, optional)

  • The preflight message points to labelle web doctor.
  • Verify whether the assembler already refuses a backend without .platforms.wasm; if not, add the check. It replaces compatibility.zig:10-14.

PR F (Flying Platform)

  • Pin web 0.3.0: providers resolve, then --accept.
  • Delete the manual emsdk loops in ci.yml and deploy-pages.yml.
  • Add the actions cache for <LABELLE_HOME>/providers/<canonical-provider-id>.

6. Release train (revised: FP migrates before the removal is published)

  1. CLI A1 and A2, then release 2.1.0 (additive).
  2. labelle-web v0.3.0 against released 2.1.0, then its labelle-registry record.
  3. Assembler S, if taken, but its D13 verification is required before B (§7).
  4. FP F, committed and green on CLI 2.1.0 + web 0.3.0: the manual emsdk loops are removed and the clean-machine acceptance passes (§8). FP installs "latest", so the committed migration must be in place before 3.0 is published. Testing an unpublished combination is useful but doesn't close that upgrade window.
  5. CLI B, then tag v3.0.0; verify R2 and latest.txt; FP's next CI run is on 3.0.

Why 3.0.0: B removes CLI features:

  • the wasm subcommands;
  • install emsdk and toolchain emsdk;
  • --emcc, LABELLE_EMSDK and .emsdk_version;
  • --docker --platform=wasm;
  • core auto-activation;
  • the doctor --json wasm rows.

A1 and A2 are additive, so 2.1.0.

7. Approved decisions

All D1–D13 are accepted by the owner with these qualifications. D4 supersedes revision 3.

Decision Approved outcome
D1: versioning CLI 2.1.0 (A1 + A2), web 0.3.0 + registry, FP committed and validated, then CLI 3.0.0 removal (§6).
D2: Python Keep install python and generic managed-Python wiring in core. Provider commands and hooks get the same PATH wiring as prebuild scripts.
D3: environment Accept env_file with the complete §3.2 rules and clean-cache verification of the earlier fingerprint pass.
D4: browser watch Keep labelle run --watch for browsers. Core watches/rebuilds; web serves/reloads. Explicit capability, consistent publication, restart diagnostics and cancellation rules apply (§3.4/§4). Native/desktop watch is outside this migration.
D5: Docker Refuse --docker for provider targets in this migration; use a desktop CI lane. Generic container execution is deferred.
D6: optimize Target-owner-only manifest defaults; reject duplicates; explicit CLI flags win; recompute on replan with session-change safeguards.
D7: doctor JSON Aggregate validated provider capability objects into one core-owned JSON document. The web provider owns wasm; no core hardcoding. Duplicate IDs fail naming both providers; provider failures produce an error capability with ok:false. Capture provider JSON or use a CLI-owned file so stdout remains one final document.
D8: cache New persistent cache keyed by canonical provider identity, with host/SDK identity and concurrent installation handled by provider layout. Do not migrate or delete the old cache automatically; document optional cleanup.
D9: escape hatches Remove --emcc, LABELLE_EMSDK, .emsdk_version; use EMSDK and provider emsdk.root/emsdk.version.
D10: project scope Provider toolchain commands remain project-scoped until a separate projectless bootstrap feature.
D11: init defaults Desktop + bgfx; no provider proposed by default.
D12: Studio Not a release gate. Track provider pin, doctor contract and toolchain-command migration as follow-up work.
D13: compatibility Remove the CLI wasm backend whitelist only after verifying equivalent assembler/manifest rejection, adding it if absent. Verification is mandatory even when no assembler change is required.

Document the breaking changes in docs/migrating-to-3.0.md.

8. Risks and acceptance

Completion criterion: clean-machine web builds through provider-owned policy. The shrinking allowlist (starting at 53; intermediate and final counts to be verified) is supporting evidence, not the acceptance criterion on its own.

Acceptance for A1/A2/W/B, in addition to the §5 lists. Each item is a test that asserts the path taken:

  • Clean-cache toolchain bootstrap in a controlled environment: clean HOME alone isn't enough, because an inherited EMSDK, PATH or installed Python can hide missing provisioning.
    • Run with EMSDK unset, PATH scrubbed of emcc and python, and LABELLE_HOME empty; assert the provider provisioned emsdk and labelle install python was required and used.
    • Separately: with an external EMSDK set, the provider passes it through and doesn't download.
    • With Python missing, the failure message names labelle install python.
  • Fingerprint/toolchain ordering: managed, root and inherited modes contribute before the generation-time zig build --list-steps. Package mode contributes after generation, so test the earlier fingerprint pass on a clean cache explicitly; if it requires an activated toolchain, resolve that ordering before shipping. Do not assume an after-generate contribution reaches an earlier pass.
  • Browser watch: require explicit capability as well as the negotiated wire version. Verify asset/code rebuilds notify the browser only after all build hooks succeed.
  • Failed-build preservation: while a rebuild writes staging output, the server continues serving a consistent successful generation. A compile or after-build failure changes neither published output nor generation. Verify successful publication on Windows and POSIX.
  • Configuration changes during watch: build-only replans refresh environment, prebuild steps and ignore paths together before prebuild. Changes affecting the running replacement yield a restart diagnostic and publish nothing.
  • Server exit: stops the watcher and cancels/reaps an in-flight rebuild before session teardown.
  • Cancellation: interrupt during provisioning, during the compile and during an after build hook. Each time the in-flight child is cancelled and reaped, and no orphan process remains (POSIX process group; Windows job object, asserted on windows-latest). The existing run outcome rules hold: a replacement exiting 0 gets after run; a signal, timeout or non-zero exit skips it; cleanup runs in both cases.
  • Concurrent toolchain installation: two builds that start the same emsdk install at once end with one valid install, no partial tree and both builds succeeding.
  • Offline cache miss: LABELLE_OFFLINE=1 with no cached emsdk fails clearly in web/toolchain, naming the missing version; with a cached one it succeeds with no network.
  • Progress JSON: under --progress=json, no web hook or provider-doctor bytes reach stdout outside core's NDJSON/JSON document.

Risks:

  • FP CI and Pages: low risk. FP already activates by hand and passes --optimize, and it migrates before 3.0 is published (§6). After F, cache <LABELLE_HOME>/providers/<canonical-provider-id> in actions.

  • Non-FP web users on 0.2.0 with CLI 3.0 lose auto-activation. The assembler preflight fails with an actionable message, and the 3.0 guide says to pin web ≥0.3.0.

  • Backend wasm examples and CI: unaffected (plain zig build with the emsdk dependency).

  • Studio Play mode: breaks on 3.0 through the doctor --json capability, LABELLE_EMSDK/install emsdk and the missing web pin. Out of scope; flag it on studio#123.

  • Windows:

    • emsdk.bat plus the Python bootstrap;
    • long zig-pkg paths (managed mode avoids them);
    • case-insensitive env keys (§3.2);
    • job objects for child cleanup;
    • consistent output publication under Windows filesystem semantics.

    W's windows-latest from-zero job and A1/A2's Windows e2e cover this.

  • Disk: old and new emsdk caches coexist during migration, about 1–2 GB.

9. Follow-on phases

  • iOS (ios.zig, and the ios branches in cli.zig, pipeline.zig, pipeline/run.zig, args_resolve.zig, bundle.zig, and IosConfig): a labelle-ios provider with replace run (simctl) and possibly replace build (xcodebuild), using env_file and cache_dir. legacyRunBranch then disappears.
  • SDL provisioning (sdl_provision.zig, the doctor SDL checks, SDL2.dll staging, wants_sdl2): the SDL backend package gets a before build hook on desktop that sets LABELLE_SDL2_LIB via env_file, plus an after build DLL staging hook.
  • Backend identity: RFC #406 step 5: make backend identities manifest-declared (drop the core Backend enum) #432, blocked on labelle-assembler#378.
  • Platform enum: project_config.Platform / AssetCompression, a schema shared with the assembler, goes with chore: pin labelle-assembler 0.105.0 #378.
  • Also open: the docker.zig xcode cross-build, and the vendored stb_image.h/.c (upstream text names platforms; exempt them by hash or move them to a package).

Expected allowlist after the web phase (32 candidates; recount after A1/A2/B):

  • agnostic_guard_test.zig, astc/cmd.zig, cli.zig
  • cli/args.zig, cli/assembler_proc.zig, cli/bundle.zig, cli/compatibility.zig, cli/config.zig, cli/docker.zig, cli/doctor.zig, cli/help.zig, cli/ios.zig, cli/linux_desktop.zig, cli/pack.zig
  • cli/pipeline.zig, cli/pipeline/args_resolve.zig, cli/pipeline/build.zig, cli/pipeline/generate.zig, cli/pipeline/install.zig, cli/pipeline/run.zig, cli/pipeline/screenshot.zig
  • cli/progress.zig, cli/project_config.zig, cli/provider_dispatch.zig, cli/runner.zig, cli/screenshot_format.zig, cli/sdl_provision.zig, cli/status.zig
  • cli/stb_image.h, cli/stb_image_impl.c, cli/update_check.zig, cli/upgrade.zig

Each still names ios, android, sdl, xcode or a backend.

Critical files

  • labelle-cli:
    • src/cli/provider_contract.zig: wire 1.3.0
    • src/cli/pipeline/run.zig: legacy wasm branch removal and generic provider watch supervision
    • src/cli/pipeline/watch*.zig and generic watcher files in src/cli/serve/*: retained/renamed under neutral modules; HTTP/server files removed from core
    • src/cli/pipeline/generate.zig:190-218 and src/cli/emsdk_toolchain.zig: the activation and provisioning that move
    • src/agnostic_guard_test.zig: 53 initially; recount after A1, A2 and B
  • labelle-web: src/provider_main.zig, src/provider/serve.zig, plugin.labelle.
  • flying-platform-labelle: .github/workflows/ci.yml and deploy-pages.yml (the manual emsdk loops).

https://claude.ai/code/session_01R2K5sJDL5Msn1uRTkybiDP

Activity

  1. apotema commented on Sep 27, 2026

    @apotema
    ContributorAuthor

    Review: approve the direction; revise the contract before implementation

    Read-only review against CLI 8188800 and the assembler/provider sources referenced by this proposal. The separation is sound: generic orchestration and watching stay in core; web tooling and policy move into the provider. The revisions below should be settled before implementation.

    Required revisions

    1. Define watched configuration changes. A long-lived replacement server cannot silently keep its old provider/configuration while rebuilds adopt a different provider version, backend, output directory or run replacement. For the first version, allow script/asset rebuilds and atomic build-only replans; require a restart with a diagnostic when changes affect the running replacement. Refresh prebuild steps and their ignore set together (wasm serve --watch: refresh the watcher's ignore list when prebuild outputs change #463). When the server exits, stop the watcher and cancel/reap an in-flight build before finishing the session. Separate arenas address memory lifetime, not this semantic mismatch.

    2. Declare watch support explicitly. Negotiating contract >=1.3 means a provider understands the wire, not that its replacement stays alive and consumes reload notifications. An Android-style deploy replacement could accept 1.3 and immediately exit. Add an explicit watch capability on the run replacement. Keep the generation file, but distinguish reload notification from artifact publication: advancing the counter only on success does not stop the server reading partially rewritten output. Specify last-successful-output serving or another consistent publication policy.

    3. Complete the environment-contribution contract. Section 3 forbids LABELLE_*, but section 9 requires LABELLE_SDL2_LIB. Reserve specific CLI-owned variables instead of the entire prefix. Define inherited-environment precedence, deterministic PATH ordering, Windows case-insensitive keys, missing/malformed env files, conflicting contributions and failure behavior. Build a fresh environment on every rebuild so removed hooks cannot leave stale values. Specify whether contributions reach replacement build processes as well as the core compile, while keeping provider host-tool compilation separate.

      Check the earlier toolchain consumer too: generation runs zig build --list-steps for fingerprint repair before before build. That can evaluate backend/toolchain configuration. This is not a demonstrated failure of the proposal, but clean-cache acceptance must exercise it; the final compile is not necessarily the first consumer of the toolchain environment.

    4. Preserve shutdown outcome semantics. The proposed test “SIGTERM, then after run still runs” is correct only when the provider handles termination and exits successfully. A clean status-zero exit runs after-run hooks; signal death, timeout and forced termination skip them. Cleanup happens in both cases. Test cancellation during provisioning, compile and after-build hooks, not only an idle server, including Windows process cleanup.

    5. Migrate Flying Platform before publishing the removal. Recommended order: CLI 2.1 additive release -> web 0.3 and registry -> migrate and validate FP -> CLI 3.0 removal. Because FP installs latest, leaving its committed migration until after 3.0 publication creates an avoidable upgrade window. Testing an unpublished combination is useful but does not remove that window.

    Recommendations on D1–D13

    These are review recommendations for the owner's decision, not a claim that the draft's decisions have already been accepted.

    Decision Recommendation
    D1: 2.1 then 3.0 Yes, with FP migration before 3.0 publication.
    D2: Python in core Yes: generic prebuild commands use it. Specify managed Python wiring for provider commands too.
    D3: env contributions Yes, after completing the rules above.
    D4: core watcher + generation file Yes, with explicit capability and session-change rules.
    D5: refuse Docker for provider targets Acceptable scoped deferral. A generic container executor remains a future option; keeping the wasm branch is not the only alternative.
    D6: target defaults Yes. Only the target owner can declare them; reject duplicates, preserve explicit CLI precedence, and recompute on replan.
    D7: doctor JSON aggregation Yes. Define validation, duplicate capability IDs, provider failures and stdout ownership. Core emits one final JSON document.
    D8: persistent cache Yes. Namespace ownership by canonical provider identity rather than a project alias; provider layout must account for host, SDK identity and concurrent installation.
    D9–D12 Escape-hatch removal, project-scoped tools, no default web and Studio deferral are reasonable within the chosen migration scope; document the breaks.
    D13: delete compatibility whitelist Yes only after verifying equivalent assembler/manifest rejection. That verification is a prerequisite, not optional.

    PR boundaries and acceptance

    Split A before implementation rather than after three review rounds: first context/environment/cache/defaults, then watch supervision. They have different failure modes.

    Add acceptance for failed-build output preservation, changes to providers/configuration during watch, cancellation during rebuild, concurrent toolchain installation and offline cache misses. Clean HOME alone does not establish toolchain bootstrap: inherited EMSDK, PATH and installed Python can conceal missing provisioning. Include controlled external-toolchain cases and explicit missing-Python behavior.

    The completion criterion is clean-machine web builds through provider-owned policy. The shrinking agnosticism allowlist is supporting evidence, not the acceptance criterion by itself.

  2. apotema commented on Sep 27, 2026

    @apotema
    ContributorAuthor

    Thanks. The RFC body is now revision 2, and every required revision is addressed. Summary, with section references:

    1. Watched configuration changes (§3.4).

      • Session-change rules: script/asset rebuilds and build-only replans are allowed. Any change the running replacement depends on ends the rebuild with "restart labelle run --watch: <what> changed" and publishes nothing. That covers the provider package, version or pin, the replacement hook, the backend, the target, the output dir, the effective optimize, the replacement's settings, and the .watch capability.
      • Prebuild steps and their ignore set swap together (wasm serve --watch: refresh the watcher's ignore list when prebuild outputs change #463), and a project.labelle edit replans before its prebuild phase.
      • Server exit stops the watcher and cancels/reaps any in-flight rebuild before the session finishes (§4). Arenas are called out as covering lifetime only.
    2. Explicit watch capability plus publication (§3.4).

      • A run replacement declares .watch = true, and --watch requires it and ≥1.3.
      • Publication is separate from notification: core publishes into a fresh published-<n>/ and atomically swaps run.watch.output_dir (symlink, or junction on Windows) only after a fully successful rebuild, then advances generation_file. The server serves only the published dir.
      • A failed rebuild leaves the last successful output served.
    3. Environment-contribution contract (§3.2).

      • A fixed reserved-name table replaces the LABELLE_* prefix ban, so LABELLE_SDL2_LIB is allowed.
      • It now defines precedence (contribution over inherited), hook-order application, conflicts as errors naming both hooks, Windows case-insensitive keys, deterministic PATH order with dedup, and the missing (no-op), malformed (fail before compile) and failed-hook (ignored) states.
      • The env is rebuilt fresh on every (re)build, so removed hooks leave nothing behind.
      • Scope: the core compile, the fingerprint pass and all later hooks/replacements (including replacement build processes). Provider host-tool compilation stays separate.
      • One design change to note: rather than redefining before build, env_file is also allowed on before generate and after generate hooks. Contributions accumulate, so web's toolchain hook runs before generate and reaches the generation-time zig build --list-steps fingerprint pass. Package mode, which needs the fetched tree, is an after generate hook. Clean-cache acceptance exercises the fingerprint pass in both modes.
    4. Shutdown outcome semantics (§4). These are the existing run rules:

      • a status-0 exit runs after run;
      • signal death, timeout, force or non-zero skips it;
      • Ctrl+C/SIGTERM is forwarded to the process group or Windows job object;
      • cleanup always runs;
      • no child outlives labelle run.

      The PR A test is rewritten accordingly. Acceptance cancels during provisioning, compile and an after-build hook, on Windows too.

    5. FP before the removal (§6). The order is now 2.1 → web 0.3 + registry → FP committed and green on 2.1 + web 0.3 → 3.0.

    Also:

    • §7 records your D1–D13 recommendations next to the draft's, with the extra specifications: D2 Python wiring for provider commands; D6 owner-only defaults with an explicit flag winning; D7 validation, duplicate ids, failures and single-document stdout ownership; D8 cache keyed by canonical provider identity with host/SDK/concurrency; D13 verification as a prerequisite of B.
    • §5 splits PR A up front into A1 (context/env/cache/defaults) and A2 (watch supervision).
    • §8 states the completion criterion as clean-machine web builds through provider-owned policy, with the allowlist as supporting evidence. It adds acceptance for:
      • controlled-toolchain bootstrap: scrubbed EMSDK/PATH/Python, the external-EMSDK passthrough, and the missing-Python message;
      • failed-build preservation;
      • configuration changes during watch;
      • cancellation during rebuild;
      • concurrent installs;
      • offline cache misses;
      • progress JSON.

    The decisions are still open for the owner.

    https://claude.ai/code/session_01R2K5sJDL5Msn1uRTkybiDP

  3. apotema commented on Sep 27, 2026

    @apotema
    ContributorAuthor

    Revision 3: watch mode removed (owner decision). The CLI will have no watch command.

    • PR A2 is gone. PR A is now only the contract 1.3.0 context: cache_dir, env_file with the full §3.2 rules, and manifest target_defaults.
    • There is no run.watch key, .watch capability, publication policy or session-change rules.
    • PR B deletes the existing labelle wasm serve --watch together with the watcher code (serve/*, pipeline/watch*), instead of keeping it as a generic core feature. cli#463 is closed as not planned.
    • web v0.3.0's serve has no live reload.
    • D4 is recorded as decided. The watch-specific acceptance items (failed-build preservation, config changes during watch) are removed; cancellation acceptance stays for normal builds and runs.
    • Guard: 53 → ~50 (A) → 32 (B), to be recounted in each PR.

    https://claude.ai/code/session_01R2K5sJDL5Msn1uRTkybiDP

  4. apotema commented on Sep 27, 2026

    @apotema
    ContributorAuthor

    Owner decisions: D1–D3 and D5–D13 accepted; D4 remains open

    The owner approved locking D1–D3 and D5–D13 with the qualifications from the preceding review. Record these as accepted decisions in the plan:

    • D1 — Release sequence: additive CLI 2.1.0, then breaking 3.0.0. Sequence: CLI 2.1 -> web 0.3 + registry -> migrate and validate Flying Platform -> publish CLI 3.0 removal. FP's committed migration must precede the removal release.
    • D2 — Python: retain labelle install python and generic managed-interpreter wiring in core because prebuild commands use it. Define that wiring for provider commands too.
    • D3 — Environment contributions: accept the generic env_file mechanism. Before implementing, specify inherited-env precedence, deterministic PATH ordering, Windows case-insensitive keys, fresh env per rebuild, malformed/missing files and conflicts, and its scope for core versus replacement builds. Reserve specific CLI-owned variables, not all LABELLE_*; the proposed SDL use must remain possible. Verify the earlier fingerprint/configuration invocation on a clean cache.
    • D5 — Docker: refuse --docker for provider targets in this migration; retarget the CI lane to desktop. Generic container execution is a separate future capability.
    • D6 — Optimize defaults: manifest-owned target_defaults. Only the target owner may declare them; reject duplicates, keep explicit CLI options highest precedence, and recompute on replan.
    • D7 — Doctor JSON: pass JSON requests to provider doctors and aggregate validated capability objects. Keep wasm as the web provider's capability ID, without hardcoding it in core. Define collision/failure behavior; core emits one final JSON document.
    • D8 — Cache: use a new persistent provider cache, without migrating the old emsdk cache. Namespace it by canonical provider identity rather than a project-local alias; version SDK layouts and handle host identity and concurrent installation. Document optional removal of the old cache; do not delete it automatically.
    • D9 — Escape hatches: remove --emcc, LABELLE_EMSDK and .emsdk_version; use EMSDK and provider-owned emsdk.root / emsdk.version settings.
    • D10 — Project scope: accept project-scoped labelle web toolchain ... commands until projectless bootstrap is implemented separately.
    • D11 — Init defaults: desktop + bgfx; do not propose web or any other provider by default.
    • D12 — Studio: not a release gate for this train. Track its provider pin, doctor contract and removed toolchain entry points explicitly as follow-up migration work.
    • D13 — Compatibility: remove the CLI's wasm backend whitelist only after verifying equivalent manifest/assembler validation; add that validation if absent. The verification is mandatory even if no assembler code change is needed.

    D4 is not approved yet. The watch capability, running-session changes, artifact publication and cancellation semantics remain to be settled. Acceptance of D3/D6 does not implicitly approve the proposed watch design.

  5. apotema commented on Sep 27, 2026

    @apotema
    ContributorAuthor

    Owner decision: D4 approved for browser watch and live reload

    The owner confirmed: “Let’s keep it for the browsers.” This supersedes revision 3’s removal of watch mode and the previous comment leaving D4 open. D1–D13 are now accepted with the previously recorded review qualifications.

    Scope and responsibilities:

    • Keep labelle run --watch for browser applications through the web provider. Desktop/native watch support is outside this migration.
    • Core owns generic file watching, rebuild orchestration and successful-build notification. The web provider owns HTTP serving and browser reload; core must not regain web-specific policy.
    • Require explicit watch support on the run replacement in addition to the negotiated protocol version. Protocol compatibility alone does not imply watch support.
    • Code and asset changes rebuild. Allow atomic build-only replans; changes affecting the running server (provider/version/pin, backend, target, output location, replacement or its settings/capability) require a restart diagnostic rather than silently mixing configurations. Refresh prebuild steps and their ignore set together and replan before running prebuild steps for changed project configuration.
    • Preserve the last successful published output on failure. Publish a consistent completed build only after all build and after-build hooks succeed, then advance the generation notification and reload. A generation counter alone does not ensure consistent publication.
    • Server exit stops watching and cancels/reaps any active build. Command termination cleans up children, including during provisioning and after-build hooks. Preserve existing after-run semantics: only a clean status-zero exit runs those hooks; cleanup always happens.

    Restore the separately scoped watch-supervision work (A2), the web-provider live-reload work, and acceptance for failed-build output preservation, configuration changes during watch, and cancellation. Carry forward the prebuild ignore/replan fix described in #463. Legacy labelle wasm serve --watch may still be removed as part of the command migration; browser watch remains available through labelle run --watch.

    The RFC body still describes revision 3’s removal and needs reconciliation with this decision before implementation.

  6. apotema commented on Sep 27, 2026

    @apotema
    ContributorAuthor

    Owner note (2026-09-27): iOS is not needed now. Android is the only mobile platform at the moment. The §9 iOS follow-on phase is deferred, not planned. #467 keeps its small legacy-labelle ios refusal (added for the no-silent-bypass rule), and #468 is parked.

    https://claude.ai/code/session_01R2K5sJDL5Msn1uRTkybiDP

  7. apotema commented on Sep 28, 2026

    @apotema
    ContributorAuthor

    Full-agnostic phase planned in #471: iOS goes to labelle-ios, backend identity (#432), target names instead of the Platform enum, and SDL moves into a provider. The allowlist ends at just the guard file. Items marked NOW run in parallel with the web phase.

    https://claude.ai/code/session_01R2K5sJDL5Msn1uRTkybiDP

  8. apotema commented on Sep 28, 2026

    @apotema
    ContributorAuthor

    Closing: the web phase shipped. #483 (PR B) removed web/wasm from the core, labelle-web 0.3.x provisions emsdk and serves browser watch, Flying Platform migrated (PR F), and labelle-cli v3.0.0 was released (#500). The §5 clean-HOME acceptance passed on Windows against 3.0.0. The remaining acceptance coverage (the web-as-local: variant, the §8 items, and Linux/macOS clean-HOME runs) is tracked in #501.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions