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
- Add framer-motion and tailwindcss-animate for transitions
- Add page transitions via AnimatePresence in the site layout
- Convert nav dropdown and mobile menu to animated motion components
- Add entry animation to the radio player widget
- Add reveal/stagger animations to the home page views
- Add shared motion utilities and reusable animated components
- Add panel_border_color to THEME_COLOR_KEYS and base palettes
- Register panel-border-color CSS variable
- Fetch and inject panel_border_color in ThemeVars
- Add Panel border field to admin theme page
- Replace hardcoded #e9b124 with var(--panel-border-color) in UserView
- Change default public imager URL from habbo.com to /imaging
- /imaging and /api/imaging/avatar now proxy from upstream (habbo.com) instead of redirecting
- Add resolveUpstreamBase() to separate public URL from upstream URL
- Update admin settings default and description
- Mobile nav: uses <details>/<summary> for toggle, no useState, no useEffect
- Nav dropdown: uses <details>/<summary>, no useState, no event listeners
- Desktop: separate div with desktop nav items (hidden on mobile)
- Mobile: hamburger dropdown with absolute positioned panel
- CSS: removed ::after pseudo-element with transform, replaced hover
with background-color only, added details[open] CSS rules
- No backdrop-filter, no will-change, no transition-all, no GPU hacks
- Works on every device without JS state management
- mobile-nav: pure block/hidden toggle, no transforms, no transition-all
- nav-dropdown: clean block/hidden toggle, no CSS animation hacks
- navigation: removed shadow-sm, no will-change, no GPU compositing
- top-header: consistent mobile layout, clean details/summary dropdowns
- admin-mobile-wrapper: inline transition instead of CSS class for sidebar
- globals.css: removed transition-all from nav-item/dropdown-item, replaced
with specific color/background-color transitions only, added hover bg
- Type run()'s action param as Promise<unknown> and cast result (Biome strips void from unions)
- Remove unused queryCount field increment in DbService (dead code)
- Reformat theme-contrast test pair assertions
- Move pnpm.onlyBuiltDependencies/overrides from package.json to pnpm-workspace.yaml (clears pnpm WARN)
- Allow useServerAction run() to accept actions returning void
- Make adminAction/authAction input optional so no-schema actions can be called without args
- Return ActionResult from updateBcPage
- Fix categoryPageMap value type (number | undefined)
- Declare DbService.queryCount field
- Use definite assignment for release in withFurniDataLock
- Narrow pair type in theme-contrast test
- Strong, obvious active state: accent-tinted background, bold text,
accent icon and left accent border so the current section is unmistakable
- Inactive items stay fully readable (white on dark) with a clear hover
- Separate nav sections with dividers and bolder uppercase headers
- Fix invisible mobile hamburger hover (bg-black/10 -> accent tint)
- Remap shared shadcn semantic tokens (--color-primary, --color-popover,
--color-card, --color-border, --color-ring, --color-muted-foreground,
--color-destructive, ...) onto the admin palette for any page scoped with
body:has([data-admin]); this themes every embedded Button, Badge, Input,
Select, Card, Table, Tabs, Dialog, Switch, Checkbox with the admin theme
and guaranteed contrast, without editing component files. Gated so the
public site is untouched and Radix portals (dialogs/selects) are covered.
- Tag the admin layout/sidebar with data-admin and give the admin content
area the admin canvas background so the whole HK is one cohesive dark UI.
- Fix hardcoded colors in catalog shop preview and favicon form to use
admin variables; fix white text on a light warning tint (low contrast).
- Set muted sidebar text equal to the readable sidebar text so inactive
nav items and section labels are never dimmed
- Active item remains distinguished by its accent background and border
- Derive sidebar text color from the main admin text against the actual
sidebar background (not canvas/surface), guaranteeing contrast
- Brighten muted sidebar text to 82% of the readable text color so
inactive nav items and section labels stay clearly visible
- New client ColorField component shows a live 'Aa' text preview on the
relevant background and a WCAG contrast ratio badge (✓ / ⚠)
- Flags low-contrast (<4.5:1) text fields with a red border and a
one-click 'Use readable color' fix
- Map each text color to the background it sits on (body text -> surface,
button text -> button color, navbar text -> navbar, admin text -> canvas)
- Add a description to every color field explaining what it affects
- Regroup colors into Page & text / Buttons & links / Gradients with
explanatory section intros for both light and dark mode
- Add section intros for light, dark, and admin (HK) modes
- Widen color field layout and show descriptions under each label
- Add 6 new admin color DB keys (admin_canvas, admin_surface, admin_text,
admin_text_muted, admin_border, admin_sidebar_bg) that override the
derived admin palette
- Extract adminPaletteCss() from themePaletteCss() for reuse
- Generate admin CSS variables in both :root and html.dark with overrides
- Persist admin color settings via saveTheme action
- Add Admin panel (HK) section to /admin/theme with color pickers
- Remove duplicate home link in mobile nav
- Remove overflow-hidden clipping main content
- Improve mobile menu scrolling and spacing
- Make top-header currencies wrap on small screens
- Reduce site-header height on mobile
- Add global mobile CSS overrides for tables, padding, fonts
The method wraps Prisma's which trusts the caller
to use ? placeholders. The Unsafe suffix is a naming convention
that signals 'review caller for parameterization'.
All user-supplied string values from FormData now go through
String.prototype.normalize('NFC') to prevent Unicode homoglyph
attacks and canonicalization bypasses. NFC is idempotent for
already-normalized strings, so this is a pure security improvement
with zero behavioral change for legitimate users.
- Make @/lib/safe-action re-export from foundation layer so all 13+
existing server actions instantly get request tracing, rate limiting,
and structured error handling without any code changes
- Add HSTS, CSP, X-Frame-Options, X-Content-Type-Options to edge proxy
(src/proxy.ts) — ran at Cloudflare/Vercel edge for all non-asset routes
- Fix rate-limit.ts race condition: compute newCount before assignment
to shrink the read-modify-write window; add memory-key prefix to
avoid collisions with Redis keys
- Add request body size limit (10 MB default) to api-handler.ts with
per-route override via maxBodyBytes option
- Remove unused imports and clean up backward-compat types
- Replace CBC+HMAC with GCM (built-in authentication via authTag)
- Remove createHmac and timingSafeEqual imports (no longer needed)
- Remove Snyk-ignore comments (no longer suppressible findings)
- Update test: tampered MAC test -> tampered auth tag test
- Add one-time migration script for existing CBC-encrypted 2FA secrets
- Replace hardcoded test secrets with crypto-generated values in laravel-encrypter.test.ts and totp.test.ts
- Add 'secure' attribute to locale cookie in language-switcher.tsx
- Validate image URLs before rendering in media-grid.tsx and media-picker.tsx (XSS prevention)
- Validate redirect URL is HTTPS before window.location assignment in TopUpForm.tsx (open redirect prevention)
- Document intentional MD5 usage for legacy PHP compatibility in password.ts
- Document HMAC integrity protection for CBC cipher in laravel-encrypter.ts
- Remove production DB dump (db_backup_*.sql) and update.log from git tracking
- Add DB backups to .gitignore
- Replace all console.log/console.error with structured logger module
- Translate Dutch error messages to English (link-discord.ts)
- Remove dead code blocks (register-form.tsx false && pattern)
- Add ESLint flat config with TypeScript, React, Next.js, jsx-a11y, and security plugins
- Add Prettier config
- Add eslint-plugin-security for security-aware linting
- Fix all 119+ ESLint warnings across the codebase:
- Resolve security/detect-object-injection with safe access patterns
- Resolve security/detect-non-literal-fs-filename with path traversal validation
- Replace <img> with next/image <Image> component
- Remove unused variables and imports
- Replace non-null assertions with proper type guards
- Replace <a> with <Link> for internal navigation
- Use next/script Script component for external scripts
- Fix setState-in-useEffect anti-patterns (navbar-color-picker, logo-generator, theme-switcher)
- Add lint and format scripts to package.json
All checks: typecheck ✓, tests 58/58 ✓, lint 0 errors 0 warnings ✓
- Run renderer and client builds in parallel (background + wait) for
faster updates
- Cache detected git branches in interactive menu (avoid re-running
git branch -r on every redraw); re-cache after switching branches
- Add health check after emulator restart (poll until active or retries
exhausted)
- Add service_active() helper with systemd/service/pgrep fallback for
non-systemd systems; replace all systemctl is-active calls
- Add 30s read timeout (-t 30) on all interactive prompts to prevent
hanging on non-terminal stdin
- Add set -E for ERR trap inheritance in subshells