Files
ulicraft-server-v1/plan/18-landing-rework.md
Oier Bravo Urtasun 037b1777d6 feat(landing): rename crew to "El valle" + source downloads from launcher.json
Rename all "crew" mentions (en/es/eu) to the untranslated proper name
"El valle" across hero, members, and features copy in i18n/ui.ts.

Rewire the homepage launcher download list to the custom-launcher build
output launcher.json (productName/version/files[]) instead of the
never-produced launcher-manifest.json shape. site.ts now reads launcher.json
and normalizes files[] -> internal builds[]; launcher.example.json is the
committed fallback. Drop the stale launcher-manifest.example/schema, update
.gitignore and docs (CLAUDE.md, plan/18).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 01:55:05 +02:00

10 KiB
Raw Blame History

Landing rework — join flow, player rosters, account, mod list, footer

Status: PLANNED — not started. Five landing workstreams (WS1WS5) 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 (410 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.

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:
{
  "schemaVersion": 1,
  "count": 76,
  "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.

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.
  • 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.