991328ff0ec3876fa6546b715175eb9813fc153d
1749
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
991328ff0e |
test: verify runner picks up Gitea CI
CI / check (push) Canceled after 0s
CI / tests-unit (push) Canceled after 0s
CI / tests-integration (push) Canceled after 0s
Gitea Actions Runner Test / test-job (push) Canceled after 0s
CI / tests-ui (push) Canceled after 0s
CI / preflight (push) Canceled after 0s
CI / deploy (push) Canceled after 0s
Gitea Runner Clean Test / test-job (push) Canceled after 0s
|
||
|
|
1835221096 |
fix: remove .gitlab-ci.yml (using Gitea CI instead)
CI / check (push) Canceled after 0s
CI / tests-unit (push) Canceled after 0s
CI / tests-integration (push) Canceled after 0s
Gitea Actions Runner Test / test-job (push) Canceled after 0s
CI / tests-ui (push) Canceled after 0s
CI / preflight (push) Canceled after 0s
CI / deploy (push) Canceled after 0s
Gitea Runner Clean Test / test-job (push) Canceled after 0s
|
||
|
|
e7a6245ac2 |
test: add test line to README
CI / check (push) Canceled after 0s
CI / tests-unit (push) Canceled after 0s
CI / tests-integration (push) Canceled after 0s
Gitea Actions Runner Test / test-job (push) Canceled after 0s
CI / tests-ui (push) Canceled after 0s
CI / preflight (push) Canceled after 0s
CI / deploy (push) Canceled after 0s
Gitea Runner Clean Test / test-job (push) Canceled after 0s
|
||
|
|
da43b6c80c |
fix: use explicit runner label self-hosted:host to match registered v5-runner
Gitea Actions Runner Test / test-job (push) Canceled after 0s
CI / check (push) Canceled after 0s
CI / tests-unit (push) Canceled after 0s
CI / tests-integration (push) Canceled after 0s
Gitea Runner Clean Test / test-job (push) Canceled after 0s
CI / tests-ui (push) Canceled after 0s
CI / preflight (push) Canceled after 0s
CI / deploy (push) Canceled after 0s
|
||
|
|
eb67424a1c |
test: add verification comment to .gitlab-ci.yml, triggering workflow run
Gitea Actions Runner Test / test-job (push) Canceled after 0s
CI / check (push) Canceled after 0s
CI / tests-unit (push) Canceled after 0s
CI / tests-integration (push) Canceled after 0s
Gitea Runner Clean Test / test-job (push) Canceled after 0s
CI / tests-ui (push) Canceled after 0s
CI / preflight (push) Canceled after 0s
CI / deploy (push) Canceled after 0s
|
||
|
|
3776475db4 |
fix: ci-workflow-contract test expectations updated to runs-on: []
Gitea Actions Runner Test / test-job (push) Canceled after 0s
CI / check (push) Canceled after 0s
CI / tests-unit (push) Canceled after 0s
CI / tests-integration (push) Canceled after 0s
Gitea Runner Clean Test / test-job (push) Canceled after 0s
CI / tests-ui (push) Canceled after 0s
CI / preflight (push) Canceled after 0s
CI / deploy (push) Canceled after 0s
|
||
|
|
54b9186e52 |
fix: test.yml runs-on ubuntu-latest → [] for host runner compatibility
Gitea Actions Runner Test / test-job (push) Canceled after 0s
CI / check (push) Canceled after 0s
CI / tests-unit (push) Canceled after 0s
CI / tests-integration (push) Canceled after 0s
Gitea Runner Clean Test / test-job (push) Canceled after 0s
CI / tests-ui (push) Canceled after 0s
CI / preflight (push) Canceled after 0s
CI / deploy (push) Canceled after 0s
|
||
|
|
fe684faf70 |
fix: change Gitea Actions runner from self-hosted to host runner, preventing stuck jobs when self-hosted runners are offline
Gitea Actions Runner Test / test-job (push) Canceled after 0s
CI / check (push) Canceled after 0s
CI / tests-unit (push) Canceled after 0s
CI / tests-integration (push) Canceled after 0s
Gitea Runner Clean Test / test-job (push) Canceled after 0s
CI / tests-ui (push) Canceled after 0s
CI / preflight (push) Canceled after 0s
CI / deploy (push) Canceled after 0s
|
||
|
|
22f834ef45 |
fix: add explicit runner tags to .gitlab-ci.yml jobs, ensuring shared runners are used and 'no matching online runner' error is prevented
Gitea Actions Runner Test / test-job (push) Canceled after 0s
CI / check (push) Canceled after 0s
CI / tests-unit (push) Canceled after 0s
CI / tests-integration (push) Canceled after 0s
Gitea Runner Clean Test / test-job (push) Canceled after 0s
CI / tests-ui (push) Canceled after 0s
CI / preflight (push) Canceled after 0s
CI / deploy (push) Canceled after 0s
|
||
|
|
7b249a7222 |
P1-P3 admin cleanup: rank guard, SSRF guard, count caching, dead code removal, pagination rewrite
CI / check (push) Canceled after 0s
CI / tests-unit (push) Canceled after 0s
CI / tests-integration (push) Canceled after 0s
Gitea Actions Runner Test / test-job (push) Canceled after 0s
CI / tests-ui (push) Canceled after 0s
CI / preflight (push) Canceled after 0s
CI / deploy (push) Canceled after 0s
Gitea Runner Clean Test / test-job (push) Canceled after 0s
- P1: new src/lib/admin/rank-guard.ts with tryGuardRank/guardRank + RankGuardTarget/Result; extracted from users.ts; applied to admin-bans (create/lift + rank redirect) and admin-badges (caps + rank guard + error surfacing); import-url-safety.ts SSRF guard wired into clone save+icon fetch; logAudit await in help-tickets; antiddos mutations SETTINGS_EDIT; markAllAlertsRead NOTIFICATIONS_EDIT; rankError/saveError in all 25 message files; tests rew/new for bans/badges/rank-guard/import-url-safety - P2: ticket-inbox merged pagination reworked to single UNION ALL SQL with LIMIT/OFFSET (avoids O(offset) materialization); getCachedAdminCount with cacheFiltered option applied to devops/errors/photos/items/guilds/help-tickets/moderation pages; admin-theme catches→?error=1+banner+25 locales; logger in silent catches (rare-values/writeable-boxes); catalog-items-loader CAST→plain equality; count caching helper extended with opt-in cacheFiltered - P3: removed dead modules is-staff/notice/recent-furni-resync/log-filters plus load-user-sanctions/user-sanction-timeline; removed orphaned CATALOG_PAGE_COLUMNS re-export; removed getFurniIconUrl from studio-furni-icons Build: lint 0, typecheck 0, 3480 tests pass, i18n:check exit 0, colors exit 0. Circular chunk warning (9452/10535/66463) is webpack code-splitting artifact; build succeeds. |
||
|
|
57529fd1ea |
Fix: badge API now reads from Gamedata/c_images/album1584
CI / check (push) Successful in 30s
Gitea Actions Runner Test / test-job (push) Successful in 2s
CI / tests-unit (push) Successful in 1m46s
CI / tests-integration (push) Successful in 1m38s
Gitea Runner Clean Test / test-job (push) Successful in 2s
CI / tests-ui (push) Successful in 2m26s
CI / preflight (push) Skipped
CI / deploy (push) Successful in 3m18s
The badge GIF files exist at /var/www/Gamedata/c_images/album1584/ but the /api/imaging/badge endpoint only looked in /swf/c_images/album1584/ (relative to process.cwd()). Added GEDATA_IMAGES_BASE candidate and fetchCandidate handler so badges render correctly in the admin UI and on the site. |
||
|
|
acbb833eca |
Fix: make live test describe blocks conditional on RUN env vars (skip when not set)
CI / check (push) Successful in 32s
Gitea Actions Runner Test / test-job (push) Successful in 2s
CI / tests-unit (push) Successful in 1m25s
CI / tests-integration (push) Successful in 1m17s
Gitea Runner Clean Test / test-job (push) Successful in 3s
CI / tests-ui (push) Successful in 2m9s
CI / preflight (push) Skipped
CI / deploy (push) Successful in 2m46s
|
||
|
|
6912669754 |
Remove skip directives from live test files
CI / check (push) Successful in 36s
Gitea Actions Runner Test / test-job (push) Successful in 2s
CI / tests-unit (push) Failing after 1m47s
CI / tests-integration (push) Successful in 1m28s
Gitea Runner Clean Test / test-job (push) Successful in 2s
CI / tests-ui (push) Successful in 2m11s
CI / preflight (push) Skipped
CI / deploy (push) Skipped
|
||
|
|
7388ad3451 |
Fix: admin badges formatting (biome)
CI / check (push) Successful in 34s
Gitea Actions Runner Test / test-job (push) Successful in 1s
CI / tests-unit (push) Successful in 1m33s
CI / tests-integration (push) Successful in 1m22s
Gitea Runner Clean Test / test-job (push) Successful in 2s
CI / tests-ui (push) Successful in 2m22s
CI / preflight (push) Skipped
CI / deploy (push) Successful in 3m58s
|
||
|
|
b13c9f47be |
Fix admin-badges: handle non-positive userId, truncate badge code to 32 chars, only revalidate on grant
CI / check (push) Failing after 23s
CI / tests-unit (push) Skipped
CI / tests-integration (push) Skipped
Gitea Actions Runner Test / test-job (push) Successful in 3s
CI / tests-ui (push) Skipped
CI / preflight (push) Skipped
CI / deploy (push) Skipped
Gitea Runner Clean Test / test-job (push) Successful in 2s
|
||
|
|
f14a0e5fe7 |
Fix i18n type errors: add colIcon to Dutch locale and type casts
CI / check (push) Successful in 31s
Gitea Actions Runner Test / test-job (push) Successful in 2s
CI / tests-unit (push) Failing after 1m22s
CI / tests-integration (push) Successful in 1m19s
Gitea Runner Clean Test / test-job (push) Successful in 2s
CI / tests-ui (push) Successful in 2m12s
CI / preflight (push) Skipped
CI / deploy (push) Skipped
|
||
|
|
505ed2bfe2 |
fix(lint): remove duplicate colIcon key in en.json
CI / check (push) Failing after 30s
CI / tests-unit (push) Skipped
CI / tests-integration (push) Skipped
Gitea Actions Runner Test / test-job (push) Successful in 2s
CI / tests-ui (push) Skipped
CI / preflight (push) Skipped
CI / deploy (push) Skipped
Gitea Runner Clean Test / test-job (push) Successful in 2s
|
||
|
|
548ae541b2 |
fix(i18n): add colIcon keys for badges and rareValues
CI / check (push) Failing after 24s
CI / tests-unit (push) Skipped
CI / tests-integration (push) Skipped
Gitea Actions Runner Test / test-job (push) Successful in 3s
CI / tests-ui (push) Skipped
CI / preflight (push) Skipped
CI / deploy (push) Skipped
Gitea Runner Clean Test / test-job (push) Successful in 2s
|
||
|
|
05883b1ee6 |
chore: make workflow file anonymous and secure
CI / check (push) Failing after 24s
CI / tests-unit (push) Skipped
CI / tests-integration (push) Skipped
CI / preflight (push) Skipped
Gitea Actions Runner Test / test-job (push) Successful in 2s
CI / tests-ui (push) Skipped
CI / deploy (push) Skipped
Gitea Runner Clean Test / test-job (push) Successful in 3s
|
||
|
|
ad015e1e9c |
fix(lint): remove duplicate i18n key and cleanup unused biome suppressions
CI / check (push) Failing after 24s
CI / tests-unit (push) Skipped
CI / tests-integration (push) Skipped
Gitea Actions Runner Test / test-job (push) Successful in 2s
CI / tests-ui (push) Skipped
CI / preflight (push) Skipped
CI / deploy (push) Skipped
Gitea Runner Clean Test / test-job (push) Successful in 2s
|
||
|
|
62e3e49ad1 |
fix(i18n): add colIcon translation to en.json
CI / check (push) Failing after 22s
CI / tests-unit (push) Skipped
CI / tests-integration (push) Skipped
Gitea Actions Runner Test / test-job (push) Successful in 2s
CI / tests-ui (push) Skipped
CI / preflight (push) Skipped
CI / deploy (push) Skipped
Gitea Runner Clean Test / test-job (push) Successful in 2s
|
||
|
|
338591691f |
chore: schone start test-workflow voor v5 runner
CI / check (push) Failing after 24s
CI / tests-unit (push) Skipped
CI / tests-integration (push) Skipped
Gitea Actions Runner Test / test-job (push) Successful in 2s
CI / tests-ui (push) Skipped
CI / preflight (push) Skipped
CI / deploy (push) Skipped
Gitea Runner Clean Test / test-job (push) Successful in 12s
|
||
|
|
f56109836e |
chore: trigger v5 runner
CI / check (push) Failing after 23s
CI / tests-unit (push) Skipped
CI / tests-integration (push) Skipped
Gitea Actions Runner Test / test-job (push) Successful in 0s
test.yml (push) Invalid workflow file
CI / tests-ui (push) Skipped
CI / preflight (push) Skipped
CI / deploy (push) Skipped
|
||
|
|
b1a45902b4 |
Test Gitea v5 runner
CI / check (push) Failing after 24s
CI / tests-unit (push) Skipped
CI / tests-integration (push) Skipped
Gitea Actions Runner Test / test-job (push) Successful in 1s
test.yml (push) Invalid workflow file
CI / tests-ui (push) Skipped
CI / preflight (push) Skipped
CI / deploy (push) Skipped
|
||
|
|
7d8874c8cd |
fix: biome lint suppressions + admin-badges.ts formatting
Gitea Actions Runner Test / test-job (push) Successful in 2s
CI / check (push) Failing after 23s
CI / tests-unit (push) Skipped
CI / tests-integration (push) Skipped
CI / tests-ui (push) Skipped
CI / preflight (push) Skipped
CI / deploy (push) Skipped
|
||
|
|
a06db72c65 |
Update: admin panel fixes, badge icons, studio improvements, gitea runner v5
Gitea Actions Runner Test / test-job (push) Successful in 2s
CI / check (push) Failing after 21s
CI / tests-unit (push) Skipped
CI / tests-integration (push) Skipped
CI / tests-ui (push) Skipped
CI / preflight (push) Skipped
CI / deploy (push) Skipped
|
||
|
|
afc8909d3f |
fix(studio): the furniture list was rendered at opacity 0 and never painted
Gitea Actions Runner Test / test-job (push) Successful in 2s
CI / check (push) Successful in 33s
CI / tests-integration (push) Successful in 2m2s
CI / tests-unit (push) Successful in 2m3s
CI / tests-ui (push) Successful in 2m39s
CI / preflight (push) Skipped
CI / deploy (push) Successful in 2m40s
The Studio furniture pane was present in the DOM the whole time and still
looked empty. Every row existed, had real dimensions, and its image loaded
with a 200 — and none of it was visible.
The pane was wrapped in a `motion/react-m` element declared with
`initial={{ opacity: 0 }}` and `animate={{ opacity: 1 }}`. That minimal entry
renders the element but never runs the animation, so the inline style stayed at
opacity 0 and the content was painted transparently forever. Measured on the
live release: the table sat at opacity 1 directly inside a wrapper pinned at
`style="opacity: 0"`.
It went unnoticed because playwright.ui.config.ts sets reducedMotion to
"reduce", under which the animation is skipped and the element lands straight
on its final value. The existing Studio specs therefore passed while the real
browser showed nothing. That also means the perf win from the minimal entry
was never actually delivering a working fade — it only hid the breakage.
The decorative 150ms fade is now a CSS animation (.studio-list-fade-in). A CSS
animation cannot strand content this way: if it never runs, the element is
simply opaque. motion/react-m had exactly one usage in the app and is gone;
the four files that use the full motion/react are untouched and unaffected.
Adds e2e/ui/studio-visibility.spec.ts, which opts out of reduced motion and
asserts no ancestor of a furniture row is faded below 0.9. Verified it fails
on the old code with `Received: 0` and passes on the new, so this cannot
regress silently again.
|
||
|
|
990ebdb158 |
fix(pwa): retire the service worker instead of just fixing it
Gitea Actions Runner Test / test-job (push) Successful in 2s
CI / check (push) Successful in 35s
CI / tests-integration (push) Successful in 1m58s
CI / tests-unit (push) Successful in 2m22s
CI / tests-ui (push) Successful in 2m50s
CI / preflight (push) Skipped
CI / deploy (push) Successful in 2m50s
The previous commit stopped the worker from caching build output, which removed the cause of the blank Catalog Studio but left the worker itself serving a purpose worth one offline fallback: three public endpoints (/api/home, /api/online, /api/radio/config). Behind Cloudflare, for a site whose visitors are online, that fallback rarely fires and goes stale exactly where it is most likely to matter. So the worker goes away rather than staying as a mostly-inert layer that every future release still has to keep correct. public/sw.js is not deleted, because deleting it would leave every existing registration alive and still in control of the page, caching as it did before. It becomes the opposite of what it was: on activate it deletes every cache, unregisters itself and reloads open clients so they stop being controlled. Browsers that already installed a worker therefore uninstall it on their next visit; browsers that never had one are unaffected. src/components/pwa-register.tsx is removed and unmounted from the root layout, so nothing registers a worker any more and the kill switch only ever runs for the visitors who need it. The manifest is deliberately untouched. src/app/manifest.ts is an ordinary Next.js route, independent of any worker, and Chrome installs from a manifest alone, so the site stays installable on a phone. Verified nothing depended on it: no other navigator.serviceWorker or caches.* reference exists in the app, the theme runs from the plain /scripts/ theme-init.js script, and no Studio, catalog or import module touches a worker. Typecheck and lint clean, 2326 unit tests and 72 UI tests green, including every Studio and catalog spec. |
||
|
|
1a4888df50 |
fix(pwa): stop the service worker replaying chunks from a previous release
Gitea Actions Runner Test / test-job (push) Successful in 3s
CI / check (push) Successful in 32s
CI / tests-integration (push) Successful in 2m11s
CI / tests-unit (push) Successful in 2m16s
CI / tests-ui (push) Successful in 2m54s
CI / preflight (push) Skipped
CI / deploy (push) Successful in 2m47s
The Catalog Studio rendered as a blank white page after a deploy, while the server was serving it correctly: /admin/studio/furni answered 200 with 110KB of HTML and every API it calls answered 200 with data. No script chunk 404ed during the session and no JavaScript threw, so the failure was entirely in what the browser chose to execute. The service worker wrapped /assets/ and /_next/static/ in a Cache Storage entry served cache-first, under a hardcoded name (atom-v3) with no build id and no revalidation. A release therefore could not invalidate it: the browser kept replaying the previous release's chunks against the new HTML, and React never hydrated. The chunk branch also had no .catch(), unlike the API branch below it, so a rejected fetch silently dropped the <script> instead of surfacing a network error the browser could retry. That cache also bought nothing. nginx already sends /_next/static/ and /assets/ as `max-age=31536000, immutable`, and those filenames are content-hashed, so the HTTP cache is both sufficient and safe — a changed file gets a new name. The worker was the only layer able to go stale across a deploy, and it was the one doing it. Both prefixes now fall through to the network and are left to the HTTP cache. The API offline fallback and network-first navigations are unchanged, and the cache names move to v4 so an existing worker is replaced. SW_VERSION moves with them: it is part of the registration URL, so without the bump the browser never refetches sw.js and the old worker keeps running. Unrelated but confirmed while tracing this: /assets/images/themes/arctic-ice.png is requested by the browser and 404s, but that string exists nowhere in the code, the database or the build. It was a stale reference served out of this same cache, not a missing asset. |
||
|
|
2978483ea9 |
fix(assets): serve furniture icons from the gamedata tree, add stack-height SQL generator
Gitea Actions Runner Test / test-job (push) Successful in 1s
CI / check (push) Successful in 30s
CI / tests-integration (push) Successful in 1m52s
CI / tests-unit (push) Successful in 2m3s
CI / tests-ui (push) Successful in 2m40s
CI / preflight (push) Skipped
CI / deploy (push) Successful in 3m47s
Commit the two changes that were live in the working tree but never recorded. The nginx change adds a location for /swf/dcr/hof_furni/icons/ that reads /var/www/Gamedata/icons/ off disk and falls back to the CMS bundle for anything missing. The CMS built its icon URLs from that path, but the icons actually live in the gamedata tree: of the 16,263 classnames in items_base, 15,338 are present there under the safe name, against 11,267 under public/swf/dcr/hof_furni/icons. The swf tree also keeps colour variants behind a literal "*" in the filename, so every request using the safe name missed, and hof.furni.url already points the Nitro client at the gamedata tree. A miss is a no-store 404, never a cached one — same reasoning as @gamedata_missing, since add_header without "always" says nothing about a 404 and Cloudflare would otherwise apply the zone TTL. scripts/generate-stack-height-sql.ts emits a SQL file that repairs items_base dimensions and interaction columns from the logic inside each bundle, which is where they actually live rather than in furnidata. It writes a file and touches no database, so the result is reviewed before it is run. |
||
|
|
43742e8d99 |
fix(catalog): decode WebP bundle textures so furniture icons resolve again
Every write path normalises a bundle's texture to WebP Lossless, so the catalog icon was being read back with a PNG-only decoder. decodePng throws on anything that is not a PNG, the callers caught that and returned null, and the user-visible result was "no icon (not in source or bundle)" for every furniture whose source does not serve a standalone icon. Measured against the production asset tree, all 18,505 bundles were WebP; icon extraction succeeded on 0 of them. src/lib/services/imager/ decode-texture.ts keeps PNG on the dependency-free decoder and routes WebP through sharp, which is already a dependency and already encodes these textures. extractFurniIconPng and getPetIconPng become async; the five call sites (upload, clone import, furni import, icon repair and both icon routes) already awaited their surrounding work. Same root cause, second bug: the spritesheet frame key. Converters disagree on packing — some keep a trailing ".png", and some lowercase the whole key while leaving the bundle name mixed-case, so "LTD_fashionistaf" looks up frame "LTD_fashionistaf_LTD_fashionistaf_icon_a" and never finds "ltd_fashionistaf_ltd_fashionistaf_icon_a". Any mixed-case classname could therefore never match, which is most of the catalogue. findFrame tries the two exact spellings, then falls back to one case-insensitive pass. Extraction now succeeds on 18,483 of 18,505 bundles (99.88%); the 22 remainder are data, not code — 9 bundles ship no icon asset, 11 do not parse. Third: three catalogue icons exist only as .gif while catalogueIconUrl hardcoded .png, so the picker offered icons that could only ever 404, and 291 icons that ship as both formats were listed twice. The API now dedupes per id and the two renderers retry with .gif before falling back to the placeholder, matching what catalog-image-picker already did. Verified live: /gamedata/.../icon_1542.png returns 404 while icon_1542.gif returns 200. Separately, close the last hole in the memory cap. Every script in package.json routes through scripts/with-memory-cap.sh, but invoking the builder directly — from a terminal, an IDE or an agent — skipped the wrapper and ran unbounded, on a host with no swap where the OOM killer picks its victim across the whole machine. next.config.ts now refuses a production build that the wrapper has not marked, before anything allocates. next dev and next start are deliberately unaffected. README gains a Memory-capped commands section covering the per-script ceilings, the backends and the ulimit -v trap, and its stale version and script tables are corrected. |
||
|
|
6793f77733 |
fix(deploy): stub git status in the simulation harness
Gitea Actions Runner Test / test-job (push) Successful in 1s
CI / check (push) Successful in 33s
CI / tests-integration (push) Successful in 1m37s
CI / tests-unit (push) Successful in 1m39s
CI / tests-ui (push) Successful in 2m20s
CI / preflight (push) Skipped
CI / deploy (push) Successful in 3m6s
The clean-tree guard in ci-deploy.sh aborts the release when
`git status --porcelain` prints anything. The harness stubs `git` as a
shell function that only special-cases `rev-parse`; every other
subcommand fell through to its `ls-remote`-shaped printf, so `git status`
emitted a fake refs/heads/main line and the guard failed on every
scenario.
Introduced in
|
||
|
|
adffac7360 |
feat(catalog): store furniture bundles as .hab instead of .nitro
Gitea Actions Runner Test / test-job (push) Successful in 1s
CI / check (push) Successful in 29s
CI / tests-unit (push) Failing after 1m37s
CI / tests-integration (push) Successful in 1m37s
CI / tests-ui (push) Successful in 2m17s
CI / preflight (push) Skipped
CI / deploy (push) Skipped
Every bundle the CMS writes — upload, clone, sync, repair and the pet / effect / figure importers — now lands as `<classname>.hab`, the extension this deployment's renderer asks for. `.hab` and `.nitro` are the same container, so an upload of either extension is accepted. Resolution goes through one module, src/lib/furni/bundle-file.ts, so nothing has to know the extension twice. Every existence check probes `.hab` first and falls back to `.nitro`: the on-disk asset set is still predominantly `.nitro`, and without the fallback Studio would report every imported item as missing and the cleanup scan would classify 18k live bundles as fake leftovers. Downloads are unchanged — Habbo's CDN and every configured clone source still serve `.nitro`, so the conversion happens on write, not on request. Deliberately unchanged: the staged-attachment store in furni-attachment.ts keys on a UUID and never reaches the client, so renaming it would break in-flight recovery jobs. Adds scripts/migrate-nitro-to-hab.ts to rename the existing asset set. It refuses to run without --dry-run or --yes, never overwrites an existing .hab, never deletes, and is idempotent. Note: renderer-config.json lives outside this repo and was patched to .hab separately; that file is served with a 30-day max-age, so returning clients need a cms-client cache purge to pick the change up. |
||
|
|
fdb7af7ef5 |
fix(deploy): unblock every rebuild on BuildKit's host-network refusal
Gitea Actions Runner Test / test-job (push) Successful in 1s
CI / check (push) Successful in 33s
CI / tests-unit (push) Failing after 2m4s
CI / tests-integration (push) Successful in 2m6s
CI / tests-ui (push) Successful in 2m43s
CI / preflight (push) Skipped
CI / deploy (push) Skipped
Docker 29.1.3 ships BuildKit v0.26, which refuses to grant a build host networking unless each caller passes --allow=network.host. All three rebuild paths asked for it, and `docker compose build` has no flag to grant it, so a rebuild failed immediately with "additional privileges requested". The live container was never replaced, which is exactly the reported symptom: the site kept serving the previous release after a rebuild. Nothing in the build actually needs host networking. It uses the network only for apk, pnpm and next/font/google — all outbound internet, which the default bridge provides. Verified by building both the full runner image and the migrations stage with --no-cache after dropping the flag. Runtime `network_mode: host` stays: blue/green needs per-release host ports (3002/3003) and nginx reaches each slot over 127.0.0.1. The second gap is how a rebuild could still ship the wrong code. ci-deploy.sh stamped every image with HEAD's revision label, and verify-deployed-release.mjs only re-checks that same label, so a dirty working tree produced an image that claimed to be release $sha while containing uncommitted code. docker-update.sh already refused this; ci-deploy.sh now does too, before any build work. |
||
|
|
48291ab641 |
fix: correct the ACL revoke migration and update the login redirect e2e
Gitea Actions Runner Test / test-job (push) Successful in 1s
CI / check (push) Successful in 30s
CI / tests-integration (push) Successful in 1m56s
CI / tests-unit (push) Successful in 2m8s
CI / tests-ui (push) Successful in 2m44s
CI / preflight (push) Skipped
CI / deploy (push) Successful in 2m51s
The deploy gate found two defects in the previous commits, both mine.
0034_acl_midrank_revoke.sql never applied: it joined `acl_roles` on
`ar.model_type`, a column that table does not have (only
`acl_model_permissions` does). It now joins on the id and keeps the
`model_type` check where it belongs.
That hid a second, worse bug. The rank was extracted with
`SUBSTRING(slug, 7)`, but MySQL's SUBSTRING is 1-based and the digits start at
position 6, right after `rank_`. rank_10 therefore parsed as 0 and rank_7 as an
empty string, so every rank >= 7 would have lost exactly the grants the
migration exists to preserve — the ACL repair would have made things worse, not
better. Now reads from position 6.
Verified against a real MariaDB with a fixture covering rank_1, rank_6, rank_7,
rank_9, rank_10 and a non-rank slug: only the sub-7 roles lose their non-view
admin.* grants, the multi-digit and higher ranks keep everything, and the
non-rank slug is untouched. The mail index was checked the same way — it
applies idempotently and EXPLAIN confirms `users_mail_index` with rows: 1.
news.spec.ts expected to land on /me after signing in. That expectation predates
the `?from=` honouring added in
|
||
|
|
6b34293cd3 |
fix: apply the 44px button target only to coarse pointers
Gitea Actions Runner Test / test-job (push) Successful in 2s
CI / check (push) Successful in 27s
CI / tests-integration (push) Successful in 1m35s
CI / tests-unit (push) Successful in 1m38s
CI / tests-ui (push) Successful in 2m20s
CI / preflight (push) Skipped
CI / deploy (push) Failing after 2m55s
The UI screenshot suite caught a regression from the previous commit: the admin article form grew 3px and its baseline no longer matched. The cause was the blanket `.btn` min-height bump from 40px to 44px. That was the wrong way round — 40px already clears WCAG 2.2 AA, which asks for 24px, and 44px is a touch-target guideline. Growing every button for mouse users only made each admin dialog and table 4px taller for no benefit. The 44px floor now sits behind `@media (pointer: coarse)`, so finger input gets the comfortable target and desktop keeps its density. A hybrid laptop still uses the desktop metrics for its trackpad, which is the behaviour the previous attempt got wrong in both directions. |
||
|
|
5cb42c8dfb |
fix: stop the commandocentrum audit call leaking an unhandled rejection
Gitea Actions Runner Test / test-job (push) Successful in 2s
CI / check (push) Successful in 27s
CI / tests-unit (push) Successful in 1m35s
CI / tests-integration (push) Successful in 1m37s
CI / tests-ui (push) Failing after 2m25s
CI / preflight (push) Skipped
CI / deploy (push) Skipped
The full suite passed but exited non-zero, which fails CI: three unhandled rejections came out of commandocentrum's fire-and-forget audit call. The cause is a real defect, not a test artefact. `auditAction` wrapped `logAudit(...)` in a try/catch to honour "auditing must never fail the command it describes", but logAudit is async, so the catch can never see its rejection. A failing audit insert therefore surfaced as an unhandled rejection instead of being swallowed — in production that is a request taking down over a logging failure. The catch is now on the promise itself. The test now mocks the audit service explicitly instead of leaning on the fake db lacking `insert`, and asserts both that an entry is logged and that a rejecting audit still lets the command succeed. |
||
|
|
759ae91745 |
perf: cache search and news archive, drop motion/react from public pages
Gitea Actions Runner Test / test-job (push) Successful in 2s
CI / check (push) Successful in 26s
CI / tests-unit (push) Failing after 1m37s
CI / tests-integration (push) Successful in 1m38s
CI / tests-ui (push) Failing after 2m24s
CI / preflight (push) Skipped
CI / deploy (push) Skipped
Closes the four remaining LOW items. Search and news archive caching - A leading-wildcard LIKE cannot use an index, so every /search section cost a COUNT(*) scan plus an ordered page fetch, and /news did the same for its archive. Both now cache: search per section for 30s, the archive for 60s under the existing news revision so publishing an article drops it at once. - Sections are cached independently, so one slow query cannot hold up the rest and a failure is not cached as a result. - The cached value is passed through cacheSafe() so the Redis path and the in-process path return the same types; without it a cache hit would hand the events grid a string where a miss hands it a Date, and it calls toISOString() on that field. Dates are revived on the way out so the public signatures of loadNewsArchive and loadPublicSearch are unchanged. - Archive entries are keyed on the REQUESTED page rather than the clamped one, so two requests that clamp onto the same page cannot alias each other. motion/react out of the public bundle - Converted the six public-facing users: the radio player, the typewriter text (a motion.span with no animation props at all), the photo lightbox, the animated counter, the footer CMS-info popup and the scroll reveal. That was the actual entry points — the counter and the popup reach the public home page and footer through static imports, so removing only the three originally named would have left the library in the bundle anyway. - Each animation moved to a CSS class, and the two that animate on exit now hold the element for the length of the fade, which is what AnimatePresence used to do. - motion/react now only ships with /admin and the two already-lazy nav panels. - Two safety fixes came out of this: the scroll reveal starts at opacity 0, so it is forced visible under prefers-reduced-motion and via a <noscript> rule in the root layout; and it now emits the .motion-reveal class, which the theme panel's "Scroll Reveal" toggle selects and which previously matched nothing. - The CMS-info backdrop became a real button in a pointer-transparent layer instead of a handler on a static element, so click-outside-to-dismiss is reachable by keyboard. Fewer duplicate router refreshes - Next.js re-renders the current route as part of a server action's own response when that action revalidates, and applies it with a seeded navigation; the router only skips its own update when the action did NOT revalidate. So the refresh after such an action fetched the same tree twice. - useServerAction takes an opt-in `revalidated` flag that skips it. It is opt-in per call rather than derived from an action name, since a rename would silently change behaviour. Applied to the two user-facing call sites whose actions were verified to revalidate their own route. Touch targets - .btn was the one shared control at 40px; it and the lightbox and CMS-info close buttons are now 44px, as is the password toggle (the auth input already reserved 44px for it). The remaining 32px icon buttons pass WCAG 2.2 AA, which only asks for 24px; enlarging those inside inputs and overlays was left alone because it risks visual breakage that cannot be checked from here. |
||
|
|
179484642f |
feat: per-account login lockout, mail index, resend captcha, i18n scoping
Gitea Actions Runner Test / test-job (push) Successful in 2s
CI / check (push) Successful in 28s
CI / tests-integration (push) Successful in 1m42s
CI / tests-unit (push) Failing after 1m45s
CI / tests-ui (push) Successful in 2m29s
CI / preflight (push) Skipped
CI / deploy (push) Skipped
Closes the four HIGH/MEDIUM items left open after the previous pass. Login lockout - The only login limits were keyed on the client IP, so a distributed attempt could grind on one account indefinitely. Added a per-account lockout with a budget of 8 failures per 15 minutes. - The bucket is keyed on the RESOLVED account id, not on the submitted string: users may sign in with either username or e-mail and neither the lookup nor the input normaliser folds case, so an input-keyed bucket would hand out a fresh budget per spelling of the same account. - precheckLogin and NextAuth's authorize share the bucket, so the pre-check cannot be used to buy extra attempts and a client that skips it entirely is still bounded. Both check the lockout BEFORE verifying the password: the success path clears the counter, which would otherwise walk a locked account straight back in on the right password. - A successful login clears the failures, which needs two new primitives in rate-limit.ts: peekRateLimit (read-only, does not consume a unit) and clearRateLimit. - Fixed a latent inconsistency while doing so: the in-process bucket capped its counter at the limit while Redis' INCR kept climbing, so the two backends disagreed about how far over the limit a key was. Both now track the true count. Mail lookup index - Added an index on users.mail (0035). Password reset, e-mail verification and the resend cooldown all resolve a single account from a submitted address and were full table scans of `users`. Deliberately non-unique: legacy rows can hold the same address more than once, so a unique index would fail to apply. Resend captcha - /verify's resend form triggers real outbound mail and was reachable with only a cooldown. It now runs the configured captcha before the account lookup and before any send. Client message payload - The root layout serialised the whole catalogue into every page. pages.admin and admin are ~177 KB of the ~235 KB and are unreachable from the public route group, so that layout now installs its own provider with the staff namespaces removed. Nested providers replace rather than merge, which is why this has to live in the segment layout. /admin, /mod, /client and /admin-next keep the full set; a guard test fails if a public page ever references a staff namespace. |
||
|
|
6cc45d7413 |
feat: harden atoms-nexst against review findings (37 items)
Gitea Actions Runner Test / test-job (push) Successful in 2s
CI / check (push) Successful in 30s
CI / tests-integration (push) Successful in 1m42s
CI / tests-unit (push) Failing after 1m49s
CI / tests-ui (push) Successful in 2m31s
CI / preflight (push) Skipped
CI / deploy (push) Skipped
Second review pass covering security, performance, admin tooling and the public/room flows. All HIGH and MEDIUM findings from the audit are resolved; nothing in this commit changes the visible feature set. Authentication & session security - CSP is now set on the request headers in the proxy, which is what Next.js uses to derive the render nonce, so the nonce is effective. - 2FA: an already-enabled user cannot re-enroll, the setup endpoint is rate-limited per account, and confirmed codes are persisted so the second secret no longer silently never applies. - Password reset revokes the ticket, authTicket and all personal access tokens, and bumps the token version so existing sessions die. The same revocation is now wired into the staff-side password reset. - /reset and /verify return a stable error code instead of raw text; the mail lookups are ordered by id so duplicates cannot vary between runs. - Resending the verification mail gets a per-address cooldown on top of the per-user limit. - Issue API tokens with the narrower radio/ticket ability set instead of "*". Authorization & input handling - Mid-rank staff can no longer keep dynamically granted non-view admin.* permissions: existing grants are revoked by migration and the grant lookup is restricted to "%.view". Rank guards use the dynamic super-admin check. - Alerting a user is permission-checked and audited like the other tools. - Material mutations (giveCredits/giveDuckets/giveDiamonds, the admin user actions route, bulk user actions) are capped and rank-guarded, and bulk ids are bounded. - updateRoom / updateRoomItem write through a field allowlist, and items may only be edited through their own room. - Classnames reaching the filesystem are validated before use so a crafted value cannot escape the asset directories. - The word filter now also covers offline mails, guild forum threads and replies, and user mottos. - Media uploads are validated by magic bytes, /api/media requires the page edit permission, APP_URL must be configured once mail is enabled, and the diagnostics error route checks the fetch site header. Admin tooling - Secret settings render masked and cannot be overwritten with a blank or an arbitrary raw key; radio credentials are new password inputs. - Commandocentrum balance changes are audited. - Admin list pagination reads the caller's per-page instead of the max, and the log exporter caps offset and search length. Performance - Catalog translations are cached per module, with a cheap revision hash; the public online count uses a stale window instead of hammering the DB. - The cache warmup now primes the payload the home route actually reads. - TopHeader batches its queries into one round trip, and LCP avatars load eagerly. - motion/react and sonner are no longer part of the root layout; the nav dropdown and mobile nav panels are lazy client chunks. Anonymous visitors again get the navigation chrome, and public pages get an edge cacheable response. Accessibility - Nested <main> elements in phase pages became <section>; the page entrance and route progress animations are pure CSS that respect reduced motion. |
||
|
|
3933214953 |
feat(auth): implement all 16 homepage/login/register review items
Gitea Actions Runner Test / test-job (push) Successful in 1s
CI / check (push) Successful in 33s
CI / tests-integration (push) Successful in 1m42s
CI / tests-unit (push) Successful in 1m47s
CI / tests-ui (push) Successful in 2m30s
CI / preflight (push) Skipped
CI / deploy (push) Failing after 2m56s
- add countArticles() (published-only, mirrors news-list) and warm total_articles - localize homepage metadata; bind articleCount to both stats; unique photo alts - drop duplicate news date and the mascot preload priorities - extract shared AuthPageFrame/AuthUsersCards used by /login and /register - login: localized noindex metadata, session redirect via safeRedirectPath, ?from passthrough from proxy, unified auth roster cache keys, registered notice - register: localized metadata, session redirect to /me, unified cache keys - add resend-verification flow on /verify with rate-limited non-enumerable action - add safeRedirectPath() with unit tests - register form: live requirements checklist + password mismatch guard - login form: unverified state with resend-link CTA - honour prefers-reduced-motion in TypewriterText - add 6 translations across all 25 locales |
||
|
|
8561c3f85e |
fix(ci): accept any runtime the engines range supports
Gitea Actions Runner Test / test-job (push) Successful in 1s
CI / check (push) Successful in 42s
CI / tests-unit (push) Successful in 1m42s
CI / tests-integration (push) Successful in 1m47s
CI / tests-ui (push) Successful in 2m28s
CI / preflight (push) Skipped
CI / deploy (push) Successful in 3m12s
The active-runtime assertion required process.versions.node to equal .nvmrc exactly, so any Node.js patch release broke `toolchain:check` and the act CI run even though package.json engines (>=26.10.0 <27) supports the newer runtime. Keep .nvmrc and the Docker base image exactly pinned for reproducibility (both still asserted), but validate the running runtime against the engines range instead. Verified: toolchain:check, lint, typecheck, i18n:check, hk:matrix:check and all 3391 tests pass. |
||
|
|
d2350a6427 |
fix(ci): install bash in the Docker build stage
Gitea Actions Runner Test / test-job (push) Successful in 2s
CI / check (push) Failing after 17s
CI / tests-unit (push) Skipped
CI / tests-integration (push) Skipped
CI / tests-ui (push) Skipped
CI / preflight (push) Skipped
CI / deploy (push) Skipped
The build script now runs every heavy command through scripts/with-memory-cap.sh, which is bash (arrays, BASH_REMATCH). Alpine's node image ships busybox ash, not bash, so the builder stage failed with `sh: bash: not found` (exit 127): ci-deploy.sh could not build the image. Verified: full docker build passes and compiles all 279 routes. |
||
|
|
0845f80768 |
fix(ops): run heavy commands under a hard memory cap to stop host OOM kills
Gitea Actions Runner Test / test-job (push) Successful in 1s
CI / check (push) Successful in 35s
CI / tests-integration (push) Successful in 2m5s
CI / tests-unit (push) Successful in 2m30s
CI / preflight (push) Skipped
CI / tests-ui (push) Successful in 3m3s
CI / deploy (push) Failing after 44s
The host runs with vm.overcommit_memory=0 and no swap, so a process that
grows past free memory makes the kernel OOM-kill across the whole machine
-- the Turbopack build (commit
|
||
|
|
3265c149da |
style(landing): add micro-interactions and polish public pages
Gitea Actions Runner Test / test-job (push) Successful in 1s
CI / check (push) Successful in 29s
CI / tests-unit (push) Successful in 1m48s
CI / tests-integration (push) Successful in 1m52s
CI / tests-ui (push) Successful in 2m35s
CI / preflight (push) Skipped
CI / deploy (push) Successful in 3m7s
|
||
|
|
57710a7fe3 |
style(landing): polish the public index, login and register screens
Gitea Actions Runner Test / test-job (push) Successful in 1s
CI / check (push) Failing after 26s
CI / tests-unit (push) Skipped
CI / tests-integration (push) Skipped
CI / tests-ui (push) Skipped
CI / preflight (push) Skipped
CI / deploy (push) Skipped
- add theme-aware helpers: btn-brand, btn-glass-dark, auth-input, auth-label, auth-alert, explore-pill, avatar-tile, aurora blobs and a hero scroll cue - reorder backdrop-filter declarations so the glass blur survives the production CSS optimizer in modern Chromium - hero: aurora glow, frosted recent-users chip, premium CTA buttons and cue - explore nav: icon pills with hover arrows; features: gradient icon tiles - stats: single glass panel with column dividers; join CTA: aurora + ring - auth forms: visible labels, icon inputs, eye/password toggle, gradient submit and pill footer links - auth pages: gradient card frame, aurora accents on the intro panel and hover-lift avatar tiles; unified pill-shaped top bar - drop the hard-coded register banner image in favor of the framed card |
||
|
|
5b2eb91c5c |
fix(ops): stop a compose replica from blocking the blue/green release
Gitea Actions Runner Test / test-job (push) Successful in 1s
CI / check (push) Successful in 30s
CI / tests-integration (push) Successful in 1m57s
CI / tests-unit (push) Successful in 2m3s
CI / tests-ui (push) Successful in 2m49s
CI / preflight (push) Skipped
CI / deploy (push) Successful in 2m48s
The deploy failed after the build, the migrations and the browser gate:
"Port 3002 is already in use". The holder was `epicnext-cms`, a compose
replica of release
|
||
|
|
11ad6d4376 |
fix(ci): measure route bundles from webpack manifests
Gitea Actions Runner Test / test-job (push) Successful in 1s
CI / check (push) Successful in 31s
CI / tests-integration (push) Successful in 1m41s
CI / tests-unit (push) Successful in 1m44s
CI / preflight (push) Skipped
CI / tests-ui (push) Successful in 2m31s
CI / deploy (push) Failing after 2m25s
The performance report measured nothing. It only read `entryJSFiles` from
each route's client-reference manifest, a field Turbopack emits and webpack
does not. When the build moved to webpack (
|
||
|
|
6c3d81920e |
fix(ops): supervise the job worker and stop the health probe from lying
Gitea Actions Runner Test / test-job (push) Successful in 2s
CI / check (push) Successful in 32s
CI / tests-unit (push) Successful in 1m49s
CI / tests-integration (push) Successful in 1m50s
CI / tests-ui (push) Successful in 2m33s
CI / preflight (push) Skipped
CI / deploy (push) Failing after 2m26s
Four production defects, all found by auditing the running host rather than the code. Each one had a signature that looked like a network or permissions problem and was actually a configuration or ordering bug. jobs-worker never ran `import "./load-env"` sat on line 3 of scripts/jobs-worker.ts, but ESM evaluates a module's imports in source order and the first import reaches `@/env`, which validates process.env at import time. The ZodError on DATABASE_URL therefore fired before load-env ever executed, so the worker could only start from a shell that had already exported the configuration. Nothing supervised it either, so scheduled articles, catalog export, JAR and database backups, disk alerts and the ops health probe have all been dead; `cms:jobs-worker:heartbeat` did not exist. Moved the import to the top and added deployment/systemd/cms-jobs-worker.service with Restart=always. The JAR backup additionally pointed at './emulator/Arcturus.jar', which does not exist and would go stale on the next emulator upgrade. resolveEmulatorJar now accepts a file, a directory or a wildcard and picks the newest JAR, the same way emulator.service picks its build, and reports an unresolvable path once instead of logging an opaque copyFile ENOENT every night. /api/health answered 200 with the database down The route documented this as intentional, and ci-deploy.sh worked around it by grepping the body for '"database":true'. The container healthcheck did not, so Docker reported containers healthy while every page 500'd. The status is now load-bearing: 503 when the database is unreachable, 200 otherwise. Redis and the emulator deliberately do not fail the container — both have in-process fallbacks, so failing them would trade a slow site for an outage. The runtime had no V8 heap cap NODE_OPTIONS existed only in the builder stage. With no cap, V8 sized its heap from host memory (23.5 GB) while the container was limited to 4 GB, so the kernel OOM-killed the process mid-request — the same failure mode as the 14 host-wide `next-build` kills. docker-start.mjs now reads the cgroup limit (v2 with a v1 fallback) and sets 70% of it, respecting an explicit override. Storage ownership was only repaired for one path ci-deploy.sh chowned storage/imaging and nothing else, so storage/catalog-git/hotel-status.json kept coming back root:root and /api/admin/catalog/status kept throwing EACCES. All eight writable storage paths are repaired now. The silent-failure mode is the reason this mattered: these writes sit inside try/catch, so a wrong owner looks like a slow page rather than an error. nginx: robots.txt was a guaranteed 404, and TLS never resumed `index index.html` without a `root` left every try_files resolving against /etc/nginx/html, which sits behind a 0750 directory — the worker got EACCES on each stat and nginx logs a failed stat at crit, which is where 149 crit lines per scan came from. robots.txt answered from that same broken location, so crawlers were pointed at a file they could never read while sitemap.xml kept advertising it. Added `root`, proxied robots.txt to the CMS, added ssl_session_cache (there was no session resumption at all), and set Restart=on-failure in a systemd override, since the packaged unit ships Restart=no and nginx is the only thing serving the site. Verified against the running host: 3379 tests, typecheck and biome clean, nginx -t passes, health returns 200 with every check green, and the worker has run for hours at NRestarts=0 with a heartbeat refreshing each minute. |
||
|
|
108c6ce03d |
fix(ci): make the lint gate fail for real and stop byparr leaking disk
Gitea Actions Runner Test / test-job (push) Successful in 2s
CI / check (push) Successful in 36s
CI / tests-integration (push) Successful in 2m3s
CI / tests-unit (push) Successful in 2m18s
CI / preflight (push) Skipped
CI / tests-ui (push) Successful in 3m6s
CI / deploy (push) Failing after 3m14s
The CI lint step was `biome check . || true`, so it could never fail: 14 real
violations were passing unnoticed. Drop the `|| true` and fix what it found.
Lint fixes, none of which change behaviour:
- give list items their natural identity instead of the array index
(key={c} / key={char}, key={`skeleton-${i}`})
- document the two useEffect dependency lists that must keep their
function-declaration handlers, with the reasoning that dropping them broke
the tree and save-on-Ctrl+S once already (
|