- 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>
Instead of relying on a manual `pnpm db:migrate` step (which was
connecting to a different postgres than the Docker container), the
backend now calls drizzle migrate() before the server starts. This
ensures migrations always run against the correct database on startup.
Also fixes the Dockerfile to copy migrations into dist/migrations so
the path resolves correctly in the compiled output.
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>
Non-null assert photo/achievement ids that are structurally non-null
due to FK constraints but nullable in Drizzle's leftJoin return type.
Add missing description field to enrichVideo model select and map.
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>
Requesting description on the article author caused a GraphQL error
which the page.server.ts caught as a 404.
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>
- Rename brand from SexyArt to Sexy throughout i18n locale
- Apply primary→accent gradient to SVG icon stroke
- Remove brand name text from logo, icon-only display
- Switch logo font to Noto Sans bold (default), drop Dancing Script
- Update favicons, app icons, webmanifest, and add logo.svg
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
awardPoints/checkAchievements were awaited inline, so any gamification error
(DB constraint, missing table, etc.) would propagate as INTERNAL_SERVER_ERROR
on comment creation, recording plays, etc. Now they run fire-and-forget with
error logging, so the core action always succeeds.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Sliding expiration: reset 24h TTL on every Redis session access
- SameSite=Strict on login and logout cookies (was Lax)
- Secure flag on logout cookie in production (was missing)
- Re-fetch user from DB on every request in buildContext so role/avatar/
admin changes take effect immediately without requiring re-login
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The edit page loaders were calling adminListVideos/adminListArticles with the
old pre-pagination signatures and filtering by ID client-side, which broke
after pagination limited results to 50. Now fetches the single item by ID
directly via new adminGetVideo and adminGetArticle backend queries.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Public pages (videos, magazine, models): URL-driven search, sort, category/duration
filters, and Prev/Next pagination (page size 24)
- Admin tables (videos, articles): search input, toggle filters, and pagination (page size 50)
- Tags page: tag filtering now done server-side via DB arrayContains query instead of
fetching all items and filtering client-side
- Backend resolvers updated for videos, articles, models with paginated { items, total }
responses and filter/sort/tag args
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add "admin" back to User.role union to fix backend TS build
- Replace SVG PeonyIcon with logo.png image in Logo component
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Replace static account card in mobile flyout with swipe-to-logout widget
- Remove redundant logout button from flyout bottom
- Make LogoutButton full-width via class prop and dynamic maxSlide
- Extract clean GraphQL error messages instead of raw JSON in all auth forms
- Add i18n keys for known backend errors (invalid credentials, email taken, invalid token)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace large gradient title with simple text-2xl font-bold heading,
matching the header pattern used across admin pages.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Mobile: remove horizontal padding so tables fill full width with top/bottom
borders only. Desktop: keep left padding, table extends to right edge.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add calendar + popover components and a custom DateTimePicker wrapper.
Video forms use date-only; article forms include a time picker.
Also add video player preview to the video edit form.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Initialize isOpen as false and only open in onMount if not yet verified,
instead of opening immediately and closing after localStorage check.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Separate admin identity from role: viewer|model + is_admin boolean flag
- DB migration 0001_is_admin: adds column, migrates former admin role users
- Update ACL helpers, auth session, GraphQL types and all resolvers
- Admin layout guard and header links check is_admin instead of role
- Admin users table: show Admin badge next to name, remove admin from role select
- Admin user edit page: is_admin checkbox toggle
- Install shadcn Badge component; use in admin users table
- Fix duplicate photo keys in adminGetUser resolver
- Replace confirm() in /me recordings with Dialog component
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>