- Add BullMQ to backend; mail jobs (verification, password reset) now enqueued instead of sent inline
- Mail worker processes jobs with 3-attempt exponential backoff retry
- Admin GraphQL resolvers: adminQueues, adminQueueJobs, adminRetryJob, adminRemoveJob, adminPauseQueue, adminResumeQueue
- Admin frontend page at /admin/queues: queue cards with counts, job table with status filter, retry/remove/pause actions
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Use Button component for photo remove, editor tab toggle, and model
pill buttons across admin/users, admin/articles, admin/videos
- Remove vite-plugin-wasm from frontend devDependencies (no longer
needed since WASM is served by the buttplug nginx container)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add Dockerfile.buttplug: builds Rust/WASM + TS, serves via nginx
- Add nginx.buttplug.conf: serves /dist and /wasm with correct MIME types
- Add .gitea/workflows/docker-build-buttplug.yml: path-filtered CI workflow
- Strip Rust toolchain and buttplug build from frontend Dockerfile
- Move buttplug to devDependencies (types only at build time)
- Remove vite-plugin-wasm from frontend (WASM now served by nginx)
- Add /buttplug proxy in vite.config (dev: localhost:8080)
- Add buttplug service to compose.yml
- Load buttplug dynamically in play page via runtime import
- Fix faq page: suppress no-unnecessary-state-wrap for reassigned SvelteSet
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The variable is fully reassigned in an \$effect, so \$state is required
for reactivity. Suppress the no-unnecessary-state-wrap lint rule with a
comment explaining the reason.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Replace \$state + \$effect pattern with writable \$derived (Svelte 5.25+)
for all searchValue instances across list pages — cleaner and lint-compliant
- Remove now-unused untrack imports from those files
- Drop \$state() wrapper around SvelteMap in device-mapping-dialog
(SvelteMap is already reactive)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- image-viewer: replace backdrop div with button for a11y
- file-drop-zone: wrap prop check in \$effect to avoid state_referenced_locally
- about: use \$derived for stats array
- magazine: use \$derived for featuredArticle
- play: add role/keyboard support to seek bar slider; fix \$state on SvelteMap in device-mapping-dialog
- admin/videos/[id]: add <track kind="captions"> to video element
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces bare \$state(data.x) initialisers (which only capture the
initial value) with \$state + \$effect pairs so that state stays in sync
whenever page data is invalidated or the URL changes. Affects all list
pages (searchValue) and all edit/detail pages (form fields).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a `photo` field to the users table (and a migration) that serves
as a dedicated profile/card image for models. This is now used in model
cards and on the model single page, while `avatar` is reserved for
comments, article authors, and the user profile page.
- DB: `photo` column on `users` with FK to `files`
- GraphQL: exposed on ModelType, UserType, AdminUserDetailType; photoId arg on adminUpdateUser
- Services: photo field in MODELS_QUERY, MODEL_BY_SLUG_QUERY, ADMIN_GET/UPDATE_USER
- Frontend: model cards and single page use `photo ?? avatar` fallback
- Admin: model photo upload section in user edit page
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Mobile nav now scrolls horizontally with hidden scrollbar; nav items
don't shrink and show icon-only on xs, icon+label on sm and up.
Added scrollbar-none utility to app.css.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Backend resolvers: typed enrichArticle/enrichVideo/enrichModel with DB
and $inferSelect types, SQL<unknown>[] for conditions arrays, proper
enum casts for status/role fields, $inferInsert for .set() updates,
typed raw SQL result rows in gamification, ReplyLike interface for
ctx.reply in auth. Frontend: typed catch blocks with Error/interface
casts, isActiveLink param, adminGetUser response, tags filter callback.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove unused `or` import in comments resolver
- Remove unused `users` import in recordings resolver
- Add index keys to pagination {#each} loops in videos, models, magazine
- Remove stale svelte-ignore comment in header (a11y warnings no longer fired)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add description field to ARTICLE_BY_SLUG_QUERY and render it in the
author bio card below the name.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace generic file drop zone + tiny thumbnail with a 96px circular
avatar that shows a camera overlay on hover, upgrades preview to
thumbnail quality, and adds a compact remove button.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Make model/video cards fully clickable on homepage, models, videos, magazine, and tags pages
- Replace inline blob divs with SexyBackground component on magazine and play pages
- Replace magazine hero section with PageHero component for consistency
- Remove redundant action buttons from cards (cards are now the link targets)
- Fix nested anchor/button invalid HTML in magazine featured article
- Convert inner overlay anchors to aria-hidden divs to avoid nested <a> elements
- Add bg-muted skeleton placeholder to all card images
- Update magazine pagination to smart numbered style with ellipsis (matching videos/models)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Fix login spinner (isLoading never set to true before await)
- Extract PageHero component, replace copy-pasted hero sections on videos/models/tags pages
- Replace inline plasma blobs with SexyBackground on videos/models/tags pages
- Make video/model/tag cards fully clickable (wrap in <a>), remove redundant Watch/View Profile buttons
- Convert inner overlay anchors to divs to avoid nested <a> elements
- Fix home page model avatar preset: mini → thumbnail (correct size for 96px display)
- Reduce home hero height: min-h-screen → min-h-[70vh]
- Remove dead hideName prop from Logo, simplify component
- Add brand name to mobile flyout panel header with gradient styling
- Remove dead _relatedVideos array, isBookmarked state, _handleBookmark from video detail page
- Clean up commented-out code blocks in video detail and models pages
- Note: tag card inner tag links converted to spans to avoid nested anchors
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add POST /upload and DELETE /assets/:id routes to backend (session auth via session_token cookie)
- Add avatar arg to updateProfile GraphQL mutation and resolver
- Fix frontend to pass avatarId correctly on save, preserve existing avatar when unchanged
- Ignore 404 on file delete (already gone is fine)
- Remove broken folder lookup (getFolders is a stub, backend has no folder concept)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Removes the drag-to-logout widget in favour of a clean inline layout:
- Desktop: avatar (links to /me), artist name, and a logout icon button
- Mobile flyout: user card with avatar, name, email, and logout button
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>