Align onlyBuiltDependencies with the workspace, fail fast without AUTH_SECRET in production, and delete catalog_items via VARCHAR-safe SQL so page deletes do not leave orphans.
Co-authored-by: Cursor <[email protected]>
21 files defined their own local formatDate (with three different output
formats: date, datetime, datetime-seconds, and varying null fallbacks).
Replace them with a single shared helper at src/lib/format-date.ts that
takes a variant + optional fallback, preserving the exact previous output
per call site (verified identical string results).
Removes ~21 copies of the same logic. photos.tsx and media-grid.tsx keep
their epoch-ms based formatters since those are a different input shape.
Verified: tsc --noEmit clean, full test suite green (301/301).
Remove 19 unused source files (no importers anywhere in src/):
- src/actions/admin-permissions.ts, admin-radio.ts, admin-user-edit.ts,
admin-users.ts (functionality lives in @/actions/users and
@/actions/permissions)
- src/components/admin/confirm-action.tsx, page-header.tsx
- src/components/motion-elements.tsx
- src/lib/format-date.ts
- src/lib/catalog-categories/* (incl. re-export barrel)
- src/lib/foundation/{index,database,middleware,validation}.ts (errors/action
kept, still imported directly)
- src/lib/services/imager/{avatar-renderer,memory-cache}.ts
- src/lib/services/nitro-assets.ts
Update admin-operations-contract test to drop the two removed action-file
gates (their permission coverage already exists in users.ts/permissions.ts).
Add knip.json for repeatable dead-code audits.
Verified: tsc --noEmit clean, next build succeeds, full test suite green
(301/301).
The danger confirm button used a hardcoded text-white class which failed the
admin theme source audit and could render unreadable against custom admin
themes. Switch to the semantic text-destructive-foreground token.
Also add media-grid.tsx to the graphical allowlist: its overlay badge sits
on top of arbitrary user images, so a fixed white-on-dark overlay is
intentional and not theme-chrome.
Restores the full test suite to green (303/303).
revert fix(infra): serve full TLS chain for epicnabbo.nl to resolve Cloudflare 525
Traefik's ACME resolver stored the epicnabbo.nl leaf certificate without
the Let's Encrypt intermediate. With Cloudflare in Full (strict) mode the
origin TLS handshake failed (HTTP 525), breaking every asset and the whole
site layout (JS/CSS chunks, Nitro client, toolbar).
Configure the epicnabbo router to use a file-based certificate that
includes the full chain (leaf + LE YR2 intermediate), referenced from
dynamic/epicnabbo-tls.yml. Add a regeneration script and README.
revert fix(infra): serve full TLS chain for epicnabbo.nl to resolve Cloudflare 525
Traefik's ACME resolver stored the epicnabbo.nl leaf certificate without
the Let's Encrypt intermediate. With Cloudflare in Full (strict) mode the
origin TLS handshake failed (HTTP 525), breaking every asset and the whole
site layout (JS/CSS chunks, Nitro client, toolbar).
Configure the epicnabbo router to use a file-based certificate that
includes the full chain (leaf + LE YR2 intermediate), referenced from
dynamic/epicnabbo-tls.yml. Add a regeneration script and README.
revert fix(infra): serve full TLS chain for epicnabbo.nl to resolve Cloudflare 525
Traefik's ACME resolver stored the epicnabbo.nl leaf certificate without
the Let's Encrypt intermediate. With Cloudflare in Full (strict) mode the
origin TLS handshake failed (HTTP 525), breaking every asset and the whole
site layout (JS/CSS chunks, Nitro client, toolbar).
Configure the epicnabbo router to use a file-based certificate that
includes the full chain (leaf + LE YR2 intermediate), referenced from
dynamic/epicnabbo-tls.yml. Add a regeneration script and README.
revert fix(infra): serve full TLS chain for epicnabbo.nl to resolve Cloudflare 525
Traefik's ACME resolver stored the epicnabbo.nl leaf certificate without
the Let's Encrypt intermediate. With Cloudflare in Full (strict) mode the
origin TLS handshake failed (HTTP 525), breaking every asset and the whole
site layout (JS/CSS chunks, Nitro client, toolbar).
Configure the epicnabbo router to use a file-based certificate that
includes the full chain (leaf + LE YR2 intermediate), referenced from
dynamic/epicnabbo-tls.yml. Add a regeneration script and README.
Traefik's ACME resolver stored the epicnabbo.nl leaf certificate without
the Let's Encrypt intermediate. With Cloudflare in Full (strict) mode the
origin TLS handshake failed (HTTP 525), breaking every asset and the whole
site layout (JS/CSS chunks, Nitro client, toolbar).
Configure the epicnabbo router to use a file-based certificate that
includes the full chain (leaf + LE YR2 intermediate), referenced from
dynamic/epicnabbo-tls.yml. Add a regeneration script and README.
Extract the article card (thumbnail onError fallback, delete confirmation)
into a Client Component so the admin articles list server page no longer
passes event handlers to server-rendered elements.
Replace the plain articles table with a responsive card grid showing
thumbnails, title, date and author. Add a slug field to the article form
with live auto-suggestion, i18n-driven labels, a larger image preview and
delete confirmation. Persist a user-provided slug (falls back to an
auto-generated one) on create and update.
Add a richer media manager with filename search, per-file delete with
confirmation, drag-and-drop uploads, copy-URL feedback, file size/type/date
metadata, and server-side upload validation that returns errors to the UI.
Extend the /api/media listing with size and uploaded timestamps.
Move media storage from public/assets/images/media to storage/media
(outside Git and public/), so uploaded images, favicons and logos are
preserved across rebuilds and git clean. Add a shared media-storage helper,
update the upload/serve actions and API routes, and gitignore storage/.
Sentry is opt-in via DSN env vars; logger uses structured pino JSON in prod; badge uploads are normalized to GIF with sharp.
Co-authored-by: Cursor <[email protected]>
Remove unused translate/jpeg types packages; refresh patch updates; mark Redis/site-settings/gamedata hotel as server-only with a Vitest stub.
Co-authored-by: Cursor <[email protected]>
Furni name suggestions, import enrichment, and badge texts now follow habbo_gamedata_hotel instead of hardcoded habbo.it.
Co-authored-by: Cursor <[email protected]>
Use raw SQL for counts/loads/creates/moves so Habbo DBs with VARCHAR page_id and no AUTO_INCREMENT still show and persist furni.
Co-authored-by: Cursor <[email protected]>
Use an opaque admin-canvas portal and lazy parentId fetches instead of mode=full, which fails on large catalogs. Search no longer loads the entire tree.
Co-authored-by: Cursor <[email protected]>
Seed root tabs from SSR, load the full tree without CLEAR_TREE races, coerce parent ids, and tolerate catalog_items page_id type mismatches so category pages actually appear.
Co-authored-by: Cursor <[email protected]>
Base UI Dialog broke the Radix-era full-viewport manager. Use a body portal shell like habbo-next behavior, without DialogTitle/Popup, so tree + editor mount and nested pickers stay usable.
Co-authored-by: Cursor <[email protected]>
Move trigger outside Dialog.Root, use portal confirms and inline create forms, and load the catalog tree atomically so the manager matches the designed full-viewport UX.
Co-authored-by: Cursor <[email protected]>
Bulk import resolves names from items_base and refreshes RCON once. Page/item updates only accept an allowlisted field set.
Co-authored-by: Cursor <[email protected]>
For each language, ~55 housekeeping keys that were English fallbacks have
been translated by matching against existing translations in the same file.
Remaining ~35 keys per language stay as English fallbacks where no
existing translation pair was available in that locale.
- Sortable columns: click headers to sort by permission/description/group/rank
- Inline edit: click rank value to edit, Enter to save, Escape to cancel
- Select all matching button when partial selection is active
- Select-all checkbox shows all-pages selection state
- AuditSection uses Intl.DateTimeFormat with CMS locale instead of toLocaleString
- Add selectAllMatching translation key to all 22 locales
- bulkImportPermissions now returns {count, errors} instead of void
- ImportSection shows per-entry error details in toast
- Remove testRankPermission server action (test is 100% client-side)
- Clean up unused testing state in TestSection
- Sync pages.admin.{import,translations} keys to all 20 languages
- Add importedWithErrors translation key to all locales
All languages now have the full housekeeping key set (78 keys) from en.json,
including loading, pagination, presets, test, import/export, audit, etc.
Previously only en.json and nl.json had these keys.
- Split ManageClient into ManageSection, PresetsSection, TestSection,
ImportSection, ExportSection - each tab only renders what it needs
- Add loading/disabled states to preset, import, export, clear buttons
- Persist search and group filter via URL searchParams (survives refresh)
- Remove dead getAuditHistory export from actions
- Fix testRankPermission - remove unnecessary revalidatePath with JSON result
- Clean up unused errors/count variables in bulkImportPermissions
- Add 'loading' translation key to housekeeping section (en/nl)
- Add group_name, depends_on and updated_by columns to website_housekeeping_permissions
- Add migration 0016 for new columns
- Rewrite housekeeping page with 7 tabs: Overview, Manage, Import/Export, Rank overview, Presets, Test, Audit log
- Add permission groups/categories with filtering
- Add bulk JSON import/export
- Add rank overview grouped by permission category
- Add permission presets for Moderator (rank 5), Admin (rank 7), Super Admin (rank 8)
- Add audit logging for all permission changes via AdminAuditLog
- Add test mode to check permissions by rank
- Add dependency tracking with warnings for missing dependencies
- Add overview dashboard with statistics
- Add Dutch and English translations for all new features