Phase 3 of the umbrella plan in #427.
Scope
Resurrect the landing-page seeding work from #276 / PR #303 onto the new portable generators (Phase 1 of #427). Now that accessTo/default are emitted relatively, seedServerRoot becomes portable by construction — no further design tension between its public-read root ACL and the single-user createRootPodStructure ACL.
Changes:
- New
src/ui/server-root.html — minimal landing-page template (operator-overridable).
- New
src/ui/server-root.js — renderer + seeder. Uses generatePublicReadAcl from src/wac/parser.js (Phase-1 portable form).
src/server.js — gains an onReady hook (skipped in read-only mode) that calls seedServerRoot with current server context (mode, IDP, enabled features).
- New
test/server-root.test.js.
Closes #303 / supersedes its branch (origin/issue-276-server-root-landing). The original PR was open since Apr 23 and went conflicting against gh-pages.
Why
Multi-user JSS deployments currently land on a raw container listing at /. New pod providers had to build their own landing page from scratch. Single-user deployments served index.html only when the operator wrote one — which fonstr does, but a bare jss start --single-user does not.
Behavior
- Skip-if-exists for
/index.html, /.acl, and /index.html.acl — operator customisation is never overwritten.
- Public read only at the root — no public write. Operators edit
/index.html on disk, not via the web. (A future --admin-webid could relax this.)
- Mode-aware rendering:
- multi-user + IDP → "Create a pod" + "Sign in"
- single-user + IDP → "Sign in" (no Create-a-pod)
- both → "Docs" link + version + mode + enabled-feature pills
- Read-only deployments skip the seeder entirely (no DATA_ROOT mutation).
- Failure isolation: HTML write failure aborts ACL seeding, so the server never ends up with a public root ACL but no index page.
Carry-over from PR #303 review
PR #303 had Copilot review pickups already addressed on its branch:
generatePublicReadAcl reuse (no inline ACL JSON)
- skip in read-only mode
- check
storage.write returns
- real IDP routes (
/idp/register, /idp — not the non-existent /.account/new and /idp/auth)
- full error logging (with stack)
- terminal feature flag
- resilient
package.json read
- restore
process.env.DATA_ROOT after tests
One remaining nit from that review thread: String.prototype.replace with a string interprets $&, $1 etc. as substitution patterns. Interpolated values like the singleUserName-derived subtitle could in principle contain $ characters. Phase 3 will switch the template substitutions to the function form (replace(/{{key}}/g, () => value)) to side-step this entirely.
Tests
Carry forward + adapt the tests from PR #303:
seeds /index.html so GET / serves HTML — fresh test server, GET /
landing page is publicly readable — direct GET /index.html
does not overwrite operator-provided /index.html — pre-write a custom file, assert preservation
renderServerRoot — mode-specific output (5 tests) — multi-user+IDP, single-user+IDP, multi-user-no-IDP, single-user subtitle includes pod name, feature listing
interpolates version + escapes version to prevent injection
Plus one new test for the $& regression: renderServerRoot should emit a singleUserName containing $ characters intact.
Acceptance
Out of scope
- Cross-host owner write (Phase 4 — separate design issue).
--admin-webid / web-edit access for the landing page (future iteration).
Refs #427.
Phase 3 of the umbrella plan in #427.
Scope
Resurrect the landing-page seeding work from #276 / PR #303 onto the new portable generators (Phase 1 of #427). Now that
accessTo/defaultare emitted relatively,seedServerRootbecomes portable by construction — no further design tension between its public-read root ACL and the single-usercreateRootPodStructureACL.Changes:
src/ui/server-root.html— minimal landing-page template (operator-overridable).src/ui/server-root.js— renderer + seeder. UsesgeneratePublicReadAclfromsrc/wac/parser.js(Phase-1 portable form).src/server.js— gains anonReadyhook (skipped in read-only mode) that callsseedServerRootwith current server context (mode, IDP, enabled features).test/server-root.test.js.Closes #303 / supersedes its branch (
origin/issue-276-server-root-landing). The original PR was open since Apr 23 and went conflicting against gh-pages.Why
Multi-user JSS deployments currently land on a raw container listing at
/. New pod providers had to build their own landing page from scratch. Single-user deployments servedindex.htmlonly when the operator wrote one — which fonstr does, but a barejss start --single-userdoes not.Behavior
/index.html,/.acl, and/index.html.acl— operator customisation is never overwritten./index.htmlon disk, not via the web. (A future--admin-webidcould relax this.)Carry-over from PR #303 review
PR #303 had Copilot review pickups already addressed on its branch:
generatePublicReadAclreuse (no inline ACL JSON)storage.writereturns/idp/register,/idp— not the non-existent/.account/newand/idp/auth)package.jsonreadprocess.env.DATA_ROOTafter testsOne remaining nit from that review thread:
String.prototype.replacewith a string interprets$&,$1etc. as substitution patterns. Interpolated values like thesingleUserName-derived subtitle could in principle contain$characters. Phase 3 will switch the template substitutions to the function form (replace(/{{key}}/g, () => value)) to side-step this entirely.Tests
Carry forward + adapt the tests from PR #303:
seeds /index.html so GET / serves HTML— fresh test server, GET/landing page is publicly readable— direct GET/index.htmldoes not overwrite operator-provided /index.html— pre-write a custom file, assert preservationrenderServerRoot — mode-specific output(5 tests) — multi-user+IDP, single-user+IDP, multi-user-no-IDP, single-user subtitle includes pod name, feature listinginterpolates version+escapes version to prevent injectionPlus one new test for the
$®ression:renderServerRootshould emit asingleUserNamecontaining$characters intact.Acceptance
src/ui/server-root.{html,js},test/server-root.test.js) and theonReadyhook insrc/server.js.$®ression.Out of scope
--admin-webid/ web-edit access for the landing page (future iteration).Refs #427.