Files
ulicraft-server-v1/plan/18-landing-rework.md
T
oierandClaude Opus 4.8 7164371d34 docs(mods): sync docs to the 164-jar kitchen-sink pack
The pack grew 76 -> 164 jars (133 both / 31 client) without any doc
update. Create + ~18 addons filled the tech gap, ars_nouveau + ars_creo
the magic gap, and the full YUNG's/Moog's/TerraBlender set the worldgen
gap, which also resolved the ponderjs orphan.

- 17-mod-shortlist: rewritten around the current pack; gap doc marked
  done, remaining gaps narrowed to map mod, Necronomicon orphan, C2ME.
- 04-mods: jar counts updated; the 24/52 cutover split is now labelled
  historical alongside the current 31/133.
- CLAUDE.md: WS6 partly done, stale "65 default both" line dropped, and
  the ~50-100 mod ceiling flagged as knowingly exceeded in both places.
  MAX_MEMORY 10G was sized for that old assumption - noted for re-check.
- 18-landing-rework: mods.json count 76 -> 164, "50+" stat tile noted
  stale.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 21:29:43 +02:00

210 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Landing rework — join flow, player rosters, account, mod list, footer
> **Status: PLANNED — not started.** Five landing workstreams (WS1–WS5) plus the
> mod-list component from `17-mod-shortlist.md` (WS6). Each is independent; suggested
> order: **WS5 → WS1 → WS2/3 → WS4 → WS6** (trivial first; rosters share avatar
> plumbing; account reuses register's skin JS; mods last).
The landing is an **Astro static site** (`landing/`, pnpm, output → `../www/`), built
on the host with `.env` sourced. Three locales (en/es/eu) in `i18n/ui.ts`, identical
shape; non-translatable config in `data/site.ts`. Runtime data (status, rosters, Drasl
API) is fetched client-side; everything else is SSG.
## New env keys (`.env` + `.env.example`)
| Key | Consumer | Purpose |
|---|---|---|
| `AVATAR_MODE` (default `headiso`) | landing build → `site.ts` | NMSR render mode for avatar tiles |
| `DRASL_ADMIN_USERNAME` / `DRASL_ADMIN_PASSWORD` | **mc-status** (compose env) | admin login for the registered-players roster — **never** reaches the browser |
Use a **dedicated, rotatable** Drasl admin account for these creds (Drasl has no
read-only role; full-admin, but isolates blast radius from your personal admin).
---
## WS1 — Join flow off packwiz (homepage = UlicraftLauncher, 3 steps)
The modpack now ships via **UlicraftLauncher** (rebranded HeliosLauncher in the
`custom-launcher` repo) — it installs mods (packwiz), auth (Drasl, preconfigured) and
all files itself. So the homepage join flow drops the packwiz step and the manual
authlib step.
**Homepage steps (3):**
1. **Register** — create your Ulicraft account (link `/register`).
2. **Download the Ulicraft Launcher** — per-OS download (from manifest); log in with
your credentials. (No authlib URL — launcher is preconfigured.)
3. **Join the server** — launch, connect to `site.serverAddress` (IP copy chip).
**Launcher list (delivery = synced local file, option B):**
- Source of truth: the `custom-launcher` build output `launcher.json` (shape
`productName/version/baseUrl/files[]`, each file `{name,os,arch,ext,size,url}`),
published to `https://distribution.${BASE}/launcher/launcher.json`.
- The release flow syncs it into `landing/src/data/launcher.json` (gitignored,
generated), same pattern as `sync-server-mods.sh`. `launcher.example.json`
committed as the documented fallback shape.
- Landing reads it at build (`site.ts` `loadLauncherManifest`), normalizes
`files[]` → internal `builds[]`, renders per-OS buttons. Missing file → hide
the buttons (build must not fail).
**Fjord — alternative path, OWN page, off the homepage:** new `/fjord` (+ `/es`,
`/eu`). Download Fjord (existing `/launcher/` mirror), add authlib-injector account
(`site.authlibUrl` chip), import `manifest.fjordPack` (`.mrpack`), connect.
Tasks:
- [ ] `site.ts`: delete `packwizUrl`; replace `launcher` (Fjord hardcode) with the
manifest import; rename product display to "Ulicraft Launcher".
- [ ] `ui.ts`: rewrite `join` block (3 steps) in **all three** locales; eyebrow "3
Steps"; reword the `features` item that name-drops packwiz.
- [ ] `[...lang].astro`: collapse the 4-step join markup → 3; remove packwiz `CopyChip`,
keep server-IP chip; render download buttons from manifest.
- [ ] New `[...lang]/fjord.astro` + `ui.ts` `fjord` block + `LANG_FJORD_PATH`.
- [ ] Document the synced-manifest step in `12-build-order.md` / `14-deploy.md`.
---
## WS2 — Live server status: online players with names + 3D avatars
`ServerCard.astro` already fetches mc-status (`/api/mcstatus/v2/status/java/${BASE_DOMAIN}`,
mcstatus.io v2 JSON: `players.online/max/list[{uuid,name_clean}]`) and renders NMSR
`headiso` heads (filters the zero-UUID placeholder). Enhancement: labeled avatar tiles
(name + avatar) using `site.avatarMode`.
Tasks:
- [ ] `ServerCard.astro`: per online player render a **shared avatar tile** (name +
`${avatarUrl}/${avatarMode}/<uuid>?size=N`). Keep count + fill bar.
- [ ] `main.css`: `.roster`/tile styles (shared with WS3).
---
## WS3 — Registered players roster (live, via mc-status)
`GET /drasl/api/v2/players` (list all) is **admin-only** → cannot be a browser fetch.
Serve it **live** through mc-status (the trusted internal proxy that already emits
sanitized public JSON). **Coupling accepted** (4–10 friend server) with two mitigations:
isolate the roster fetch (own timeout + cache; the status path never blocks on Drasl),
and use a dedicated rotatable admin account.
mc-status (`docker/mc-status/main.go`) gains a `/players` endpoint:
- On request (cache TTL ~60s): if no cached token, `POST http://drasl:25585/drasl/api/v2/login`
with admin creds → cache `apiToken` (re-login on 401). `GET …/players` (Bearer) →
map to `[{uuid,name}]` → cache → serve JSON, CORS `*` (like the status route).
- Caddy already `handle /api/mcstatus/*` → mc-status, so the public path is
`/api/mcstatus/players` (browser fetches **same-origin**, no CORS, no admin token in
the page).
`PlayerRoster.astro` (new) fetches `/api/mcstatus/players` client-side → live grid of
shared avatar tiles. Roster is **public** (uuid+name) — accepted.
Tasks:
- [ ] `main.go`: `/players` handler (Drasl login + list, token cache, TTL cache,
isolated from the status path).
- [ ] `docker-compose.yml`: pass `DRASL_ADMIN_*` env to mc-status.
- [ ] New `PlayerRoster.astro` + a "Members" section in `[...lang].astro` + `ui.ts`
keys (3 langs).
---
## WS4 — Player account page (skin CRUD)
New `/account` (+ `/es`, `/eu`), browser-direct Drasl (proven by `/register`; CORS
already scoped to the apex). **Skins only** (capes out). One player per user
(`players[0]`, no switcher). Token **in-memory only** (refresh = re-login; no
localStorage → no XSS token theft).
- **Read:** `POST /login {username,password}` → `{apiToken, user.players[0]{uuid,name,skinUrl}}`.
- **Create/Update:** `PATCH /players/{uuid} {skinBase64, skinModel}` (Bearer).
- **Delete:** `PATCH /players/{uuid} {deleteSkin:true}` → reverts to default.
UI: login form → reveal skin panel (current avatar via NMSR `site.avatarMode`, model
radio classic/slim, file picker, [Upload], [Reset to default]). After upload, NMSR lags
~15m → show the **just-picked file** locally (FileReader dataURL) as instant
confirmation, not a fresh NMSR render.
Tasks:
- [ ] New `[...lang]/account.astro` — lift skin JS near-verbatim from
`register.astro:250-307` (`readBase64`, PATCH, `apiMessage`).
- [ ] `ui.ts`: `account` block (reuse `register.skin*`) + `LANG_ACCOUNT_PATH`.
- [ ] `main.css`: reuse `.reg-*` tokens.
- [ ] `register.astro`: repoint the "Manage it here" footer link from the Drasl web UI
to `/account`.
---
## WS5 — Footer status link
Add a link to `https://status.${BASE_DOMAIN}` in every footer. Nav unchanged.
Tasks:
- [ ] `site.ts`: add `statusUrl`.
- [ ] `ui.ts`: add `footer.status` (3 langs).
- [ ] Add `<a target="_blank" rel="noopener">` in `.footer-links` of `[...lang].astro`,
`register.astro`, and the new `account.astro` / `fjord.astro` footers.
---
## WS6 — Mod-list component (auto-generated, see `17-mod-shortlist.md`)
Shows the **installed pack** (full client set from the distribution forgemods), flat
alphabetical, pretty names + extracted logos, no categories, no external links.
Build-time baked.
New `tooling/build-modlist.py` (mirrors `classify-mods.py`: `tomllib` + `zipfile`,
offline) reads `$DISTRIBUTION_WEB_ROOT/servers/ulicraft-1.21.1/forgemods/required/*.jar`:
- Per jar: parse `neoforge.mods.toml`, take the primary `[[mods]]` → `displayName`
(fallback modId), `version`, `description`, `authors`; extract `logoFile` PNG → write
`landing/public/mod-logos/<modid>.png` (skip if absent → `logo: null`).
- Write `landing/src/data/mods.json` (gitignored, generated), sorted by `name`:
```json
{
"schemaVersion": 1,
"count": 164,
"mods": [
{ "id": "jei", "name": "Just Enough Items (JEI)", "version": "19.27.0.340",
"authors": "mezz", "description": "…", "logo": "/mod-logos/jei.png" }
]
}
```
`ModList.astro` (new) imports `mods.json` → flat grid (logo + name + version). The
homepage stat tile (`site.ts` `stats[0].key "50+"`) is fed from `mods.json.count`.
(The hardcoded `"50+"` fallback is stale — the pack is 164 jars as of 2026-07-20;
bump it to `"150+"` when wiring the tile.)
Tasks:
- [ ] `tooling/build-modlist.py` (jar parse + logo extract + `mods.json`).
- [ ] New `ModList.astro` + a "Mods" section in `[...lang].astro` + `ui.ts` heading
keys (3 langs).
- [ ] `site.ts`: feed `stats[0]` count from `mods.json`.
- [ ] Run the generator pre-`pnpm build`; document in `12-build-order.md` /
`14-deploy.md`. `.gitignore`: `landing/src/data/mods.json`,
`landing/src/data/launcher.json`, `landing/src/data/players.json` (if
any baked), `landing/public/mod-logos/`.
---
## Verification (whole rework)
- **Build:** `cd landing && set -a && . ../.env && set +a && pnpm run build` then
`pnpm check`. Must pass offline even if `launcher.json` / `mods.json` are
absent (buttons/list hidden).
- `grep -ri packwiz landing/` → no live join references remain (Fjord page may mention
its own import).
- **Local preview** (`pnpm dev`): all three locales — 3-step join, online roster, members
grid, account page, footer status link, mod grid, `/fjord` page.
- **Rosters (running stack):** online tiles appear with players on; `/api/mcstatus/players`
returns `[{uuid,name}]`; the members grid renders avatars.
- **Account (running stack):** login → upload PNG → instant local preview → in-game/NMSR
reflects after ~15m → reset reverts to default.
- Deploy via the `deploy` skill; the manifest sync, `build-modlist.py`, and landing
rebuild are **host** steps (not compose) — fold into `14-deploy.md`.
## Related
- `09-landing.md` — landing stack, i18n, theme.
- `15-mc-status.md` — the mc-status service (now also serves the roster).
- `16-landing-registration.md` — the browser-direct Drasl pattern WS4 reuses.
- `17-mod-shortlist.md` — the mod gap doc + the `mods.json` dataset.
- `launcher.json` — the `custom-launcher` build output (download list); landing
syncs it to `landing/src/data/launcher.json`, `launcher.example.json` committed.