The first cloud pass was to subtle: only five, up to 190s per crossing,
and they froze off-screen under prefers-reduced-motion. Rework:
- Eight clouds across the top 60% of the viewport with visible drift
(28s–66s loops, staggered by negative delays so they are always mid
scene on load).
- Higher opacity/steeper size contrast in light mode; dark mode dims
them slightly.
- Reduced motion now freezes a static, evenly-spread cloud field across
the width instead of pushing the clouds off-screen.
- New src/lib/site-icons.ts: base64 data URIs for the 13 tiny classic
icons actually referenced in markup (100–2000 bytes), replacing extra
requests with inline payloads. home.png, dynamic flags and currency
sets stay on the filesystem.
- Default favicon is now served server-side as a base64 SVG data URI
(memoized), while a DB-configured custom favicon still takes priority.
- Icons render through <Image unoptimized>, so data URIs pass through
untouched on all affected pages (home, login, register, settings,
navigation, auth top bar, client loading).
Add a fixed, decorative layer of soft clouds that slowly float across
the Habbo sky behind the content:
- Five clouds at staggered sizes, heights, opacities and loop timings
(60s–190s) so the drift feels organic.
- Dimmed further in dark mode; frozen by prefers-reduced-motion.
- Pure decoration: aria-hidden, pointer-events: none, no color
utilities in markup (cloud shapes live in globals.css).
- Slow Ken Burns drift on the hero artwork for cinematic depth.
- Gentle breathing pulse on the brand glows behind Frank and the hero.
- Silkier reveal easing (cubic-bezier .22/1/.36/1, 0.55s) site-wide.
- Eased, longer hover transitions on the hero CTAs.
- Smooth page scrolling, all guarded by prefers-reduced-motion.
Give the home page a clear, professional information hierarchy:
- Add eyebrow labels + headings for Features, Live stats and Community
sections using reusable, theme-aware .eyebrow / .section-title styles.
- Loosen the vertical rhythm (gap-8/10) so each block breathes.
- New messages resolve via the existing English fallback for all locales.
Professional tidy-up of the landing experience:
- SurfaceCard: unified rounded-xl radius for a crisper, consistent look.
- Hero: matches the new card radius and gains a dual-direction title
shadow so the headline stays readable over the header artwork.
- Login/register: drop the duplicated "no account / have an account"
paragraphs — the forms already ship an inline footer, so one clear CTA
cluster remains and the side column is cleaner.
Refine the public UI for a cleaner, more professional and scannable
landing experience without leaving the classic Habbo style:
- SurfaceCard: softer layered shadow, gradient accent hairline on the top
edge and a bolder header title across all public cards.
- Home: gradient hotel-name in the hero headline, shine effect on the
primary CTA, hairline on the top bar.
- Login/register: consistent avatar tiles with rounded corners, subtle
borders and a gentle hover lift; uniform username sizing.
- Add reusable theme-aware .card-hairline and .gradient-text utilities.
Replace the premium dark-gaming redesign of home, login and register with
the original classic landing (Habbo sky background, AuthTopBar, SurfaceCard
layout). Keeps the theme background visible again and adds a subtle
theme-aware brand halo behind the hero/Frank plus a soft primary glow on
card hover.
Also upgrades dependencies: next 16.3.5, vite 8.3.0 (typescript 7.0.2 was
already latest). Temporarily lowers pnpm minimumReleaseAge to 60 min so the
fresh 16.3.5 release can be installed; restore to 1440 once it is 24h old.
Replaced the classic Habbo landing style on the home, login and register pages with a modern premium dark-gaming look: always-dark hero canvas with brand glows, grid overlay and ambient orbs, glass panels, gradient text and floating art. Adds shared LandingTopBar, AuthShell and BrandFrank components plus reusable premium CSS utilities. Build, typecheck and lint pass.
resolveNitroFrame now matches spritesheet frame keys that carry a .png
suffix or namespaced naming, and isScale/scaleName preserve that suffix.
Broken source sprites (missing frames or references to icon artwork) are
skipped and reported instead of aborting the whole generation, and the
studio UI surfaces the skipped count.
Adds a second mode to the organize-imports dialog: instead of creating
one new page per approved group (which could produce dozens of tiny
pages), the user can pick an existing destination page and have every
approved item moved into it. No catalog page is created in this mode.
- organizeImportFurni: groups accept destinationPageId; when set, the
existing page is reused, new offers append after its current highest
order, moved offers keep their original name, and the real page
caption is used for logging and results
- OrganizeImportsDialog: mode toggle (create pages / move into page),
searchable destination picker via /api/admin/catalog/tree?search=,
name/icon/layout editors hidden in move mode, button shows a move
count, and the success toast reports moved/added instead of pages
- en + nl translations for the new mode, destination, and move keys
The organize-imports route defaulted to a 500-item limit, silently
hiding offers beyond the first batch of imported pages. Remove the
effective cap (limit now means 'all', guarded only by a 50k lint cap)
so every offer already sitting in the import tree is returned and
grouped.
The original GET route used a correlated NOT EXISTS / FIND_IN_SET
subquery over the entire catalog_items table for every recent import
audit entry, causing server timeouts when the audit log or catalog
grew large. The per-item host-page validation inside the create
action also issued one SELECT + one UPDATE per moved offer.
Changes:
- GET /api/admin/import/organize: replace the correlated subquery
with a bounded candidate list and a JS-side placed-set check, then
resolve all needed base items in a single indexed SELECT. This
bounds the query cost regardless of catalog or audit log size.
- organizeImportFurni action: validate mover ids in one SELECT, then
batch every move per group into a single UPDATE with a CASE
expression instead of one UPDATE per item.
- OrganizeImportsDialog: add a 45-second abort timeout on the fetch
and a distinct load-error state so the UI never silently hangs.
- Add 'loadError' translation key (en + nl).
Adds a Studio 'Organize imports' dialog that groups recently imported
furniture and furniture already sitting in the auto-created import
pages into suggested catalog categories. Each group is presented with
its suggested name, icon, and layout which can be overridden before
approval; approved groups are turned into real catalog pages in a
single atomic export run. Offers already inside the import subtree are
moved to the new pages; brand-new furniture gets a fresh offer.
- groupSuggestedCategories: generic pure helper reusing the same
per-item label heuristic that drives suggestCategoryName; items with
no label land in a 'Other Furni' remainder bucket
- GET /api/admin/import/organize: returns items from the imported
furniture tree (catalog_items JOIN items_base via page id set from
the imported-furniture root) union audit-logged but not-yet-placed
recent imports; marks alreadyPlaced vs new
- organizeImportFurni server action: validates import-page membership
before moving any offer, creates pages + inserts/moves items in one
withCatalogExport snapshot, logs activity
- OrganizeImportsDialog: full-featured Studio dialog with price panel
(applies to new offers only), parent select, per-group approval,
editable name/icon/layout with suggestion reset chips, source tags
- import-pages.ts server helper: locates the imported-furniture tree
- Studio nav: 'Organize imports' button with FolderTree icon,
gated on CATALOG_EDIT, wired next to the catalog manager
- en + nl translations for organizeImports.* keys with ICU plurals
- groupSuggestedCategories unit tests (deterministic grouping,
remainder handling, size+alpha ordering, label consistency)
The catalog manager (with auto-category wizard) now lives inside the Studio
navigation for teams that manage furniture inline. The button is gated on
CATALOG_EDIT; only users with that permission see the launcher.
- Split server/client Studio layout to derive permissions server-side
- Add optional triggerLabel prop to CatalogManagerDialog for custom labels
- Wire the Catalog manager button in the Studio nav right cluster
- Keep existing /admin/catalog entry points unchanged
- Add AutoCategoryDialog: pick furni (or empty page), suggest caption/icon/layout, live preview
- Add createAutoCategory server action (page + offers in one export) with CatalogKind
- Extend furni search API with interactionType
- Share ShopTile and refactor inline-editor/items-shop-preview to use it
- Add suggestion heuristics (suggestCategoryName/dIcon/layout) with tests
- Add autoCategory translations (en/nl)
- Update all DragonflyDB references to Valkey in README and docker-compose.yml
- Update install instructions to use Valkey package repository and .deb download
- Update configuration paths from /etc/dragonfly/ to /etc/valkey/
- Update version requirement to Valkey 8.x+ (successor to Redis OSS)
The Arcturus errors "page hierarchy contains a cycle page 354 and 357" and
"sibling order 1 is used more than once (111 problems)" come from
catalog_pages, not catalog_items: pages 354/357 point at themselves, and
many parents have child pages sharing the same order_num. Extend the
emulator catalog scan + fix to detect both: pages whose parent chain loops
back get detached (parent_id = 0 on the highest cycle member) and every
affected parent's children are renumbered sequentially, preserving their
current relative order.
Add scripts/diag-emulator.ts to inspect the live catalog state.
Block invalid catalog_items writes at the API level (points currency
allowlist, non-negative prices, positive amount, limited stack >= sold
count, unique sibling order numbers) and auto-assign unique order numbers
on bulk create. Add a catalog-maintenance scan + transactional repair that
fixes pre-existing rows: resets unsupported points_type, clamps negative
costs, sets amount to 1, raises limited_stack, renumbers duplicate orders
and deletes offers with missing page/item references. Surface the issue
count and a fix button in the admin maintenance panel.
Also: add enabled/retired flag to clone sources, classify poster and
currency furniture in item-kind, and remove the obsolete update-Nitrov3.sh.
translateCatalogItems previously only patched the master FurnitureData.json
via patchFurniEntryNames but never updated the per-language files
(FurnitureData_nl.json, etc.). Custom/imported furniture translated through
the catalog Translate tab was therefore invisible in localized builds.
After patching the master file, the action now also calls
patchLocalizedFurniDataEntries so LibreTranslate translates the English
names into all 13 supported languages.
Exclude generated drizzle-kit snapshot artifacts from formatting checks (drizzle/drafts/meta), which made biome scan a 360KB generated JSON for 23s. Fix the pre-existing lint errors in error-monitor, article-form and the admin-search-permissions mock so pnpm biome:lint is green in CI.
Run vitest without coverage by default (pnpm test) and add pnpm test:coverage which enforces the coverage thresholds. CI keeps using the coverage run so thresholds are still enforced on every push.
Drop the unused knip dead-code check and the husky+lint-staged pre-commit hook pipeline. All checks remain covered by the CI workflow (lint, typecheck, i18n, tests).
Drop the leftover Playwright browser install and e2e smoke test from the deployment script, and update the deployment contract tests to cover the verify-deployed-release smoke check instead.
- Move the pnpm store, apk and .next caches into --mount=type=cache so
dependencies are shared across builds instead of duplicated in fresh
image layers (was the source of unbounded disk growth).
- Replace the deprecated --keep-storage prune flag in ci-deploy.sh with
the working --max-used-space=4g (buildx v0.37 renamed the flag). The
deprecated flag silently did nothing, so the BuildKit cache kept
growing unbounded (was 15.86GB); it is now capped at 4GB after every
deploy.
Runs the full catalog audit (runCatalogAudit) against the live hotel DB with
no repair/sql options — a pure read pass — and cross-checks the emitted
summary against independent DB + asset-dir measurements: row totals, duplicate
classname groups, orphaned catalog references and missing catalog / nitro /
icon counts must match exactly, with zero error events and a final
'batch_complete'.
Guarded by RUN_CATALOG_AUDIT_LIVE=1 so CI never runs it; loads the real .env
because vitest fakes DATABASE_URL.
generate-drizzle-schema.mjs imported 'dotenv/config' but dotenv is not a
dependency, failing knip and crashing 'pnpm db:schema:generate'. Load .env
with Node's built-in process.loadEnvFile like the other scripts do
(scripts/load-env.ts), never overriding vars already set in the shell.
- Create the runtime write targets (/app/storage, /app/public/nitro-assets,
/app/public/swf, /var/www/Gamedata) owned by UID/GID 33 in the runner image
so running without the bound volumes no longer hits ENOENT.
- Bake a HEALTHCHECK into the image so `docker run` (ci-deploy.sh) also reports
Docker-level health; compose can still override it with its own probe.
- Add the dockerfile:1 syntax pragma and ignore non-pnpm lockfiles so a stray
package-lock.json/yarn.lock can never taint the build context.
next.config.ts falls back to `git rev-parse HEAD`, but the build context has
no .git (excluded by .dockerignore), so every CI build printed fatal git
errors and stamped the release as "unknown". Pass the deploy commit sha as a
NEXT_DEPLOYMENT_ID build-arg so git is never invoked and the actual commit
reaches NEXT_PUBLIC_CMS_RELEASE and deploymentId.
The committed lockfile records overrides from pnpm-workspace.yaml. With the
narrower manifest COPY, pnpm install --frozen-lockfile ran without the
workspace file and failed with ERR_PNPM_LOCKFILE_CONFIG_MISMATCH in CI.
Copy package.json, pnpm-lock, pnpm-workspace and .npmrc together so the
override config present in the lockfile is also supplied at install time.
- Use floating node:alpine that tracks the latest supported LTS; pnpm
bootstrap follows package.json's packageManager pin.
- Drop corepack (removed from node:26), install pnpm via npm global.
- Add pnpm fetch + offline install for stable dependency-layer caching.
- Run as non-root nextjs (UID/GID 33 = host www-data) with tini as PID 1
for correct signal handling.
- Open node engines to >=20.9.0 so patches/minors float automatically.
- Add docker-preflight.sh (per-VPS checks incl. --fix) and gate docker-update.sh
so Node major upgrades require explicit review while patches deploy silently.
The site-settings loader kept an in-process map forever after a Redis miss
and promoted DEFAULTS (no logo/theme) to Redis on any DB error, so a build
that started before the DB was reachable stuck the site on the preset logo
and default theme until a manual reload or full restart.
- Redis miss now reloads from the database instead of the stale in-process map
- a DB failure returns defaults only as an in-process last resort and never
writes them to Redis, so the shared cache can't be poisoned by a transient
error at startup
- regression tests: DB re-read on Redis miss after cache expiry, defaults never
promoted to Redis, recovery from transient DB failure
- fix(studio): stop markBatchDone infinite recursion so batches complete
- refactor(api): merge api-response into api and drop the duplicate module
- refactor(media): extract shared media loader and URL validator
- refactor(ui): extract shared LoadingSpinner for site and admin groups
- refactor(dates): consolidate raw date formatting into formatDate util
- refactor(logs): share a single generic log-list loader across tables
- refactor(theme): merge both ColorField components and reuse contrast helpers
- refactor(import): extract shared ImportErrorBanner and SearchInput
- chore(): remove dead theme-editor-tabs after inlining tab components
- New buildDutchLanguage() function translates only 'nl' language
- Proper error handling with specific messages for network errors
- Safe template literal usage, guarded .find() for Dutch language
- Consistent with existing buildAllLanguages pattern
Remove unused admin-helpers.test.ts (dead code)
- Add retries for furnidata fetch (3 attempts, 1s delay)
- Better error messages for user (distinguish 502/400/other)
- Client-side: show detailed error from SSE stream when available
- Remove unused admin-helpers.test.ts
- Add e2e job (needs deploy, main/master only) that installs the
Playwright browser and smoke-tests the live container on :3002
- Keep deploy-job contract slice from bleeding into the e2e job
- Cover the e2e job in the CI workflow contract test
- Ignore Playwright output dirs (test-results, playwright-report,
blob-report)
Zonder dit loopt nieuwe code tegen een oud schema aan zodra een push
migraties bevat. Idempotent (toegepaste migraties worden overgeslagen),
draait op de host met de productie-.env, vóór de container-replace.
docker run --env-file behoudt letterlijke quotes (bewezen test),
waardoor DATABASE_URL ongeldig was en de container crashte. Nu wordt
.env gesourced en elke sleutel met -e doorgegeven: exact dezelfde
waarden als bij de build. Contract-test verbiedt --env-file.
- Deploy kopieert de productie-.env van de host in de build-context:
Next.js bakt NEXT_PUBLIC_* in en valideert DATABASE_URL/HOTEL_NAME
(SKIP_ENV_VALIDATION is verboden voor productie, zie src/env.ts).
- Dockerfile builder installeert git (next.config.ts deploymentId).
- Deploy-container krijgt --env-file + dezelfde volumes als compose,
stopt ook de oude compose-container (poort 3002) en rolt terug via
compose bij een falende health check.
De host heeft Docker iptables uitgeschakeld, dus build-containers op
bridge hebben geen outbound internet; 'npm install -g pnpm' in de
builder-stage hing daardoor. Met --network=host krijgt de build wel
registry-toegang. Contract-test vergrendelt de flag.
pnpm 11 has no --jobs option for install; the stray positional '1'
flipped the command into 'add' mode, which then rejected both
--frozen-lockfile and --jobs ('Unknown options', 'pnpm help add').
Reproduced locally, fixed, verified install succeeds. Add contract
regression test.
Root cause: de job-container (docker mode) heeft GEEN outbound
internet naar GitHub, waardoor actions/checkout@v4 faalde met
'Unable to clone ... i/o timeout'.
Oplossing: zowel check als deploy draaien nu op self-hosted (host)
waar Node 26.8.1 + pnpm 11.25.0 geïnstalleerd zijn en internet
beschikbaar is. Dit is de enige betrouwbare setup in deze omgeving.
Runner is tevens hernoemd naar 'Epic runner'.
- Runner hernoemd naar 'Epic runner'
- Env variabelen als global env (niet per step)
- fetch-depth: 1 voor snellere checkout
- Knip verwijderd (traag, niet kritiek)
- Docker BuildKit caching
- Health check opgeschoond