From 837cdd1d0eac0e6d912008e7559951631ba2cc94 Mon Sep 17 00:00:00 2001 From: Antoine Toussaint <312536060+antoinetoussaint-byte@users.noreply.github.com> Date: Thu, 29 Oct 2026 10:43:09 -0400 Subject: [PATCH] fix(add): the playground builds the page's remote before running MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The backend answered 404 for /assets/mf-manifest.json — the manifest it advertises in its own /.well-known/solution.json — so a host could federate nothing and the page had no bundle to load. The cause was not the address. The solution runtime serves /assets/ from ASSETS_DIR, which already defaults to ../fe-remote/dist relative to the backend; there was simply never a build there. The fleet's playground runs its build-frontend.sh FIRST for exactly this reason, and the scaffolded Makefile did not build the remote at all. It now builds it and asserts the manifest exists before the run, so the failure is a build error at the top rather than a 404 an hour later. The shell installs the remote as a copy, so it needs it built too. Verified on a clean scaffold: dist/mf-manifest.json 1.7 kB backend /assets/mf-manifest.json 200 {"id":"notes","exposes":["Page"]} edge / 200 (signed in as alex) edge /v1/notes/overview 200 Co-Authored-By: Claude Opus 5 --- cmd/add/solution_playground.go | 15 +++++++++++++-- cmd/add/solution_test.go | 22 ++++++++++++++++++++++ docs/commands.md | 11 +++++++++-- 3 files changed, 44 insertions(+), 4 deletions(-) diff --git a/cmd/add/solution_playground.go b/cmd/add/solution_playground.go index cf3ee5fb..c67dbc78 100644 --- a/cmd/add/solution_playground.go +++ b/cmd/add/solution_playground.go @@ -191,7 +191,7 @@ FRONTEND=${endpoint:%s/web/http} // playgroundMakefile writes the run line, whose one non-obvious part is that // authority/edge is NAMED. Nothing starts it otherwise. func playgroundMakefile(name string) string { - return fmt.Sprintf(`.PHONY: playground + return fmt.Sprintf(`.PHONY: playground remote # %s on the development authority, as a solution runs in a cell with no saas # host: the authority's edge is the one origin the browser opens and signs a @@ -204,8 +204,19 @@ func playgroundMakefile(name string) string { # origin that can sign anyone in. PLAYGROUND_SERVICES = authority/edge %s/web %s/backend -playground: +# THE REMOTE IS BUILT FIRST, and that is not a convenience. The solution +# runtime serves /assets/ from ASSETS_DIR, which defaults to +# ../fe-remote/dist relative to the backend — so with no build there, the +# backend answers 404 for /assets/mf-manifest.json, the manifest it +# advertises in /.well-known/solution.json, and a host can federate nothing. +# The shell also installs the remote as a copy, so it needs it built. +playground: remote cd %s && codefly run service $(PLAYGROUND_SERVICES) --env local + +.PHONY: remote +remote: + cd services/fe-remote && npm install --no-audit --no-fund && npm run build + test -f services/fe-remote/dist/mf-manifest.json `, titleFromName(name), name, name, playgroundDir) } diff --git a/cmd/add/solution_test.go b/cmd/add/solution_test.go index 48df770d..903fd5a4 100644 --- a/cmd/add/solution_test.go +++ b/cmd/add/solution_test.go @@ -555,3 +555,25 @@ func TestTheContractDeclaresARoleBesidesInstaller(t *testing.T) { t.Fatalf("the contract must declare installer and viewer, got %v", names) } } + +// TestThePlaygroundBuildsTheRemoteFirst. The solution runtime serves /assets/ +// from ASSETS_DIR, which defaults to ../fe-remote/dist relative to the +// backend. With nothing built there the backend answers 404 for +// /assets/mf-manifest.json — the manifest it advertises in +// /.well-known/solution.json — and a host can federate nothing. The fleet's +// own playground builds the remote before the run for exactly this reason. +func TestThePlaygroundBuildsTheRemoteFirst(t *testing.T) { + makefile := playgroundMakefile("notes") + if !strings.Contains(makefile, "playground: remote") { + t.Fatalf("the playground must build the remote first:\n%s", makefile) + } + for _, required := range []string{ + "cd services/fe-remote && npm install", + "npm run build", + "test -f services/fe-remote/dist/mf-manifest.json", + } { + if !strings.Contains(makefile, required) { + t.Fatalf("the remote target must contain %q", required) + } + } +} diff --git a/docs/commands.md b/docs/commands.md index dde8b037..851e1047 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -2435,8 +2435,9 @@ What it creates: `services/fe-remote` — the `./Page` remote itself — is written by the solution agent, not by this command: it is an npm package, not a Codefly service. -**Running it.** `make playground` starts the development authority and this -solution, and prints the one address a browser opens: +**Running it.** `make playground` builds the page's remote, starts the +development authority and this solution, and prints the one address a browser +opens: ``` edge listening on :14581, routing 1 module(s) @@ -2450,6 +2451,12 @@ not behind its sign-in edge") and the authority's REST port (every browser path 404s). In a cell the host's gateway plays the edge's part; the playground is local only. +The remote is built **before** the run, and that is load-bearing: the solution +runtime serves `/assets/` from `ASSETS_DIR`, which defaults to +`../fe-remote/dist` relative to the backend. With nothing built there the +backend answers 404 for `/assets/mf-manifest.json` — the manifest it +advertises in `/.well-known/solution.json` — and a host can federate nothing. + Sign in as any of the fixture's acme people in a team — `maya`, `remi`, `alex`, `dev` — and the solution's route answers: