- Replace inline mobile dropdown with sliding flyout panel from right
- Hide burger menu on lg breakpoint, desktop auth buttons use hidden lg:flex
- Add backdrop overlay with opacity transition
- Remove logo from flyout panel header
- Fix backdrop div accessibility with role="presentation"
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Create packages/types with shared TypeScript domain model interfaces (User, Video, Model, Article, Comment, Recording, etc.)
- Wire both frontend and backend packages to use @sexy.pivoine.art/types via workspace:*
- Update backend Pothos objectRef types to use shared interfaces instead of inline types
- Update frontend $lib/types.ts to re-export from shared package
- Fix all type errors introduced by more accurate nullable types (avatar/banner as string|null UUIDs, author nullable, events/device_info as object[])
- Add artist_name to comment user select in backend resolver
- Widen utility function signatures (getAssetUrl, getUserInitials, calcReadingTime) to accept null/undefined
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Root-level eslint.config.js (flat config): typescript-eslint,
eslint-plugin-svelte, eslint-config-prettier, @eslint/js
- Root-level prettier.config.js with prettier-plugin-svelte
- svelte-check added to frontend for Svelte/TS type checking
- lint, lint:fix, format, format:check, check scripts in root
and both packages
- All 60 lint errors fixed across backend and frontend:
- Consistent type imports
- Removed unused imports/variables
- Added keys to all {#each} blocks for Svelte performance
- Replaced mutable Set/Map with SvelteSet/SvelteMap
- Fixed useless assignments and empty catch blocks
- 64 remaining warnings are intentional any usages in the
Pothos/Drizzle GraphQL resolver layer
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
thumbnail (300x300 square) was double-cropping inside the wide h-48
container. preview (800px, aspect-ratio preserved) lets object-cover
do the only crop, matching the videos and model pages.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Files are stored as <UPLOAD_DIR>/<id>/<filename>. The previous static
serving attempted to serve <UPLOAD_DIR>/<id> (a directory) which failed.
Custom /assets/:id route now looks up filename from DB and uses sendFile.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Pass FastifyRequest/FastifyReply directly to yoga.handleNodeRequestAndResponse
per the official graphql-yoga Fastify integration docs. Yoga v5 uses req.body
(already parsed by Fastify) when available, avoiding the dead raw stream issue.
- Add proper TypeScript generics for server context including db and redis
- Wrap sendVerification/sendPasswordReset in try/catch so missing SMTP
does not crash register/requestPasswordReset mutations
- Fix migrate.ts path resolution to work with both tsx (src/) and compiled (dist/)
- Expose postgres:5432 and redis:6379 ports in compose.yml for local dev
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
graphql-yoga's handleNodeRequestAndResponse returns a Response with a
Web API ReadableStream body. Fastify's reply.send() requires a Node.js
Readable stream, causing all GraphQL requests to hang indefinitely.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
nanoid v5 is ESM-only and cannot be require()'d in a CommonJS module.
v3 is the last version with native CJS support.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Run pnpm install to update lockfile with packages/backend dependencies
- Add argon2 to root onlyBuiltDependencies (pnpm-workspace.yaml + package.json)
- Add explicit `pnpm rebuild argon2` in Dockerfile.backend to ensure native
bindings compile regardless of pnpm v10 build approval state
- Remove pnpm.onlyBuiltDependencies from packages/backend/package.json
(ineffective in workspace packages, warned by pnpm)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
All custom logic (endpoints, hooks, gamification) has been ported to
packages/backend. The Directus bundle is no longer needed.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Removes Directus 11 and replaces it with a lean, purpose-built backend:
- packages/backend/: Fastify v5 + GraphQL Yoga v5 + Pothos (code-first)
with Drizzle ORM, Redis sessions (session_token cookie), argon2 auth,
Nodemailer, fluent-ffmpeg, and full gamification system ported from bundle
- Frontend: @directus/sdk replaced by graphql-request v7; services.ts fully
rewritten with identical signatures; directus.ts now re-exports from api.ts
- Cookie renamed directus_session_token → session_token
- Dev proxy target updated 8055 → 4000
- compose.yml: Directus service removed, backend service added (port 4000)
- Dockerfile.backend: new multi-stage image with ffmpeg
- Dockerfile: bundle build step and ffmpeg removed from frontend image
- data-migration.ts: one-time script to migrate all Directus/sexy_ tables
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove outdated docs (COMPOSE.md, DOCKER.md, QUICKSTART.md, REBUILD_GUIDE.md)
- Remove build.sh, compose.production.yml, gamification-schema.sql, directus.yaml
- Simplify compose.yml for local dev (remove env var indirection)
- Add directus.yml schema snapshot and schema.sql from VPS
- Add schema:export and schema:import scripts to package.json
- Ignore .env files (vars set via compose environment)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Changed ?transform= to ?key= so Directus storage asset presets
(mini, thumbnail, preview, medium, banner) are actually applied.
Previously all images were served at full resolution.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
ButtplugMessageSorter never deleted entries from _waitingMsgs after
resolving, causing unsolicited DeviceList messages (with reused Ids)
to be swallowed. Also fix battery level not updating in UI by accessing
the device through the Svelte $state proxy array.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Unwrap DeviceList wrapper message before passing to parseDeviceList(),
and rename FeatureDescriptor to FeatureDescription to match Rust v10 serde output.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add play count display below video title with play icon
- Query actual plays count from sexy_video_plays table for accuracy
- Apply same pattern as likes_count for consistency
- Show singular/plural ("play" vs "plays") based on count
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Remove size="sm" from SharingPopupButton to use default button height (h-9)
instead of small size (h-8), ensuring consistent button heights across all
action buttons on video, model, and article pages.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
The likes_count field on videos was becoming inaccurate due to the manually
maintained counter getting out of sync with actual like records. Now we count
likes directly from the sexy_video_likes table for accurate counts.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Changed from sexy_model_photos to junction_directus_users_files
which is the actual table Directus uses to store the many-to-many
relationship between users and their photo files.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Changed from max-w-lg to max-w-3xl to accommodate 3-column grid
with better spacing and prevent cramped appearance.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Photos are returned from backend as { directus_files_id: fileObject }
but frontend was trying to access p.id directly. Updated to use
p.directus_files_id.id to properly display model photos.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>