feat(landing): join-flow rework, player rosters, account page, mod list
Execute plan/18 (WS1-6) + plan/17.
- WS1: homepage join = 3 steps off packwiz (register -> UlicraftLauncher
-> join); per-OS downloads from a synced launcher-manifest.json (schema +
example committed); Fjord moved to its own /fjord page. Drop packwizUrl.
- WS2/3: mc-status gains an isolated /players endpoint (admin login ->
Drasl GET /players, own client+TTL+token cache, never blocks the status
path); online + registered rosters render shared name+avatar tiles;
AVATAR_MODE env (default headiso).
- WS4: /account skin CRUD via browser-direct Drasl (login -> upload ->
reset), in-memory token, players[0]; register relinks "Manage it here".
- WS5: footer link to status.${BASE_DOMAIN} in every footer.
- WS6: tooling/build-modlist.py generates mods.json + extracted logos from
the distribution forgemods; ModList.astro renders a flat list; stat tile
fed from the count.
Manifest/mods loaders read at build with a process.cwd() fallback (the
import.meta.url-only path does not resolve in Astro's bundled build).
New env: AVATAR_MODE, DRASL_ADMIN_USERNAME, DRASL_ADMIN_PASSWORD (mc-status
only). Generated mods.json / launcher-manifest.json / logos are gitignored;
.example.json files document the shapes. Build: 12 pages; mc-status builds.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -144,6 +144,8 @@ and uptime-kuma.
|
||||
- `12-build-order.md` — commit-per-task build sequence
|
||||
- `14-deploy.md` — redeploy to the production host
|
||||
- `15-letsencrypt.md` — Let's Encrypt (OVH DNS-01) + host nginx ingress
|
||||
- `17-mod-shortlist.md` — kitchen-sink gap doc + landing mod-list dataset (PLANNED)
|
||||
- `18-landing-rework.md` — join flow / rosters / account / mod list / footer (PLANNED)
|
||||
|
||||
## Boot/dependency order
|
||||
|
||||
|
||||
75
plan/17-mod-shortlist.md
Normal file
75
plan/17-mod-shortlist.md
Normal file
@@ -0,0 +1,75 @@
|
||||
# Mod shortlist + landing mod-list component (WS6)
|
||||
|
||||
> **Status: PLANNED — not started.** Two separable deliverables: (1) a curated
|
||||
> kitchen-sink gap doc (this file), (2) an auto-generated `mods.json` + logos that
|
||||
> feed a flat mod-list component on the landing page (see also `18-landing-rework.md`).
|
||||
|
||||
## TL;DR
|
||||
|
||||
- Current pack (76 jars, `mods-sides.json`) is a polished **vanilla+ QoL/perf** base,
|
||||
**not yet kitchen-sink**. Strong perf + QoL + storage + FTB + voice; **empty** on
|
||||
tech and magic, **thin** on worldgen, **no** map mod.
|
||||
- Jars live in the **external distribution repo** (`$DISTRIBUTION_WEB_ROOT/servers/
|
||||
ulicraft-1.21.1/forgemods/required/*.jar`, Nebula-managed). Adding mods happens
|
||||
there, **not** in this repo. This doc is a shortlist only; depth = **light** (names
|
||||
by area, verify NeoForge 1.21.1 at add-time — no per-mod version/link research).
|
||||
- The landing component shows the **installed pack** (full client set), auto-generated
|
||||
from the jars — a different dataset from this shortlist. Flat alphabetical, pretty
|
||||
names, extracted logos, no categories, no Modrinth/CF links.
|
||||
|
||||
## Current pack — what's already in (76 jars)
|
||||
|
||||
- **Perf:** sodium, sodium-extra, iris, immediatelyfast, entityculling, ferritecore,
|
||||
lithium, spark.
|
||||
- **QoL:** JEI, Jade(+addons), JER, JEProfessions, appleskin, MouseTweaks, Controlling,
|
||||
Searchables, inventoryessentials, polymorph, rightclickharvest, simplemagnets,
|
||||
fastleafdecay, torchmaster, trashcans, corpse(+curioscompat), lootr, comforts,
|
||||
BetterF3, tagtooltips, cardboardboxes.
|
||||
- **Storage:** sophisticatedbackpacks(+core), functionalstorage(+more).
|
||||
- **Farm/decor/build:** FarmersDelight, FramedBlocks, handcrafted.
|
||||
- **FTB utils:** chunks, essentials, library, teams, ultimine.
|
||||
- **Voice:** Simple Voice Chat (`voicechat`).
|
||||
- **Scripting/guide:** KubeJS(+rhino, +delight), ponderjs, Patchouli.
|
||||
- **Libs:** architectury, balm, cloth-config, configured, curios, resourcefullib,
|
||||
supermartijn642 core/config, jamlib, baguettelib, titanium, utilitarian, YungsApi,
|
||||
Necronomicon, yacl.
|
||||
|
||||
## Gaps vs kitchen-sink (tech + magic + exploration + QoL)
|
||||
|
||||
- [ ] **Tech — empty.** Candidates: Create, Mekanism, Immersive Engineering, Applied
|
||||
Energistics 2, Thermal Series, Powah. (Pick a subset; Create is the anchor.)
|
||||
- [ ] **Magic — empty.** Candidates: Ars Nouveau, Botania, Iron's Spells 'n Spellbooks,
|
||||
Eidolon.
|
||||
- [ ] **Worldgen — thin** (only `YungsApi` + `ExplorersCompass`). Candidates: Terralith,
|
||||
Tectonic, YUNG's structure set (Better Dungeons/Mineshafts/Strongholds…),
|
||||
Repurposed Structures, William Wythers' Overhauled Overworld.
|
||||
- [ ] **Map — none.** Candidate: Xaero's Minimap + Worldmap.
|
||||
- [ ] **Server-side perf (optional):** ModernFix, C2ME.
|
||||
|
||||
## Orphaned dependencies (cleanup)
|
||||
|
||||
- [ ] **`ponderjs`** present but **no Create** — ponder is Create's. Add Create or drop.
|
||||
- [ ] **`Necronomicon`** present but **no Iron's Spells** — it's Iron's Spells' lib.
|
||||
Add Iron's Spells or drop.
|
||||
|
||||
## Curation tradeoff
|
||||
|
||||
Adding the tech+magic+worldgen anchors pushes 76 → 100+ jars, near the stated
|
||||
50–100 ceiling, and raises the RAM/stability bar (`INIT_MEMORY 4G`/`MAX_MEMORY 10G`).
|
||||
Curate deliberately; verify each candidate's NeoForge **1.21.1** build + the
|
||||
`21.1.233` loader pin at add-time.
|
||||
|
||||
## Execution (external repo, when ready)
|
||||
|
||||
1. Add jars in the distribution repo (Nebula `forgemods/required/`), keep the NeoForge
|
||||
1.21.1 builds matching the `21.1.233` pin.
|
||||
2. `release-distro.sh` regenerates `distribution.json` + uploads.
|
||||
3. Back in this repo: `tooling/classify-mods.py` (seed sides) → hand-edit new
|
||||
client-cosmetic entries to `client` → `tooling/sync-server-mods.sh`.
|
||||
4. `tooling/build-modlist.py` (new, see `18-landing-rework.md`) regenerates
|
||||
`landing/src/data/mods.json` + logos → rebuild landing.
|
||||
|
||||
## Related
|
||||
|
||||
- `04-mods.md` — distribution-fed mod system, classify/sync, sides.
|
||||
- `18-landing-rework.md` — the landing mod-list component + `mods.json` generator.
|
||||
204
plan/18-landing-rework.md
Normal file
204
plan/18-landing-rework.md
Normal file
@@ -0,0 +1,204 @@
|
||||
# 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 manifest (delivery = synced local file, option B):**
|
||||
- Contract: `landing/src/data/launcher-manifest.schema.json` (already written;
|
||||
JSON-Schema draft 2020-12).
|
||||
- The `custom-launcher` release flow **produces** `launcher-manifest.json` and syncs
|
||||
it into `landing/src/data/launcher-manifest.json` (gitignored, generated), same
|
||||
pattern as `sync-server-mods.sh`.
|
||||
- Landing **imports** it at build → renders per-OS buttons from `builds[]`. 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": 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-manifest.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-manifest.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.
|
||||
- `landing/src/data/launcher-manifest.schema.json` — the launcher manifest contract.
|
||||
Reference in New Issue
Block a user