- 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>
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>
- 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>
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>
- 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>
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>
- 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>
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>
Added null checks for user.name property to prevent SSR errors when
the property is undefined. Made name and avatar properties optional
in the User interface and added fallbacks to use email when name is
not available.
Fixes TypeError: Cannot read properties of undefined (reading 'split')
that was occurring during server-side rendering.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Fix artist_name null handling in header component with email fallback
- Fix authentication in recording endpoints to use req.accountability
- Change duration field type from integer to double precision for millisecond precision
- Add createRecording service function with proper authentication
- Update play page to use fetch API for recording saves
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Added null check in getUserInitials function to return "??" for undefined names
- Fixed logout button to handle undefined user.name when displaying first name
- Prevents 500 errors when rendering components for users without names
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Implemented complete infrastructure for recording, saving, and managing
buttplug device patterns with precise event timing.
**Phase 1: Backend & Infrastructure**
- Added Directus schema for sexy_recordings collection with all fields
(id, status, user_created, title, description, slug, duration, events,
device_info, tags, linked_video, featured, public)
- Created REST API endpoints in bundle extension:
* GET /sexy/recordings - list user recordings with filtering
* GET /sexy/recordings/:id - get single recording
* POST /sexy/recordings - create new recording with validation
* PATCH /sexy/recordings/:id - update recording (owner only)
* DELETE /sexy/recordings/:id - soft delete by archiving
- Added TypeScript types: RecordedEvent, DeviceInfo, Recording
- Created frontend services: getRecordings(), deleteRecording()
- Built RecordingCard component with stats, device info, and actions
- Added Recordings tab to /me dashboard page with grid layout
- Added i18n translations for recordings UI
**Phase 2: Recording Capture**
- Implemented recording state management in /play page
- Added Start/Stop Recording buttons with visual indicators
- Capture device events with precise timestamps during recording
- Normalize actuator values (0-100) for cross-device compatibility
- Created RecordingSaveDialog component with:
* Recording stats display (duration, events, devices)
* Form inputs (title, description, tags)
* Device information preview
- Integrated save recording API call from play page
- Added success/error toast notifications
- Automatic event filtering during recording
**Technical Details**
- Events stored as JSON array with timestamp, deviceIndex, deviceName,
actuatorIndex, actuatorType, and normalized value
- Device metadata includes name, index, and capability list
- Slug auto-generated from title for SEO-friendly URLs
- Status workflow: draft → published → archived
- Permission checks: users can only access own recordings or public ones
- Frontend uses performance.now() for millisecond precision timing
**User Flow**
1. User scans and connects devices on /play page
2. Clicks "Start Recording" to begin capturing events
3. Manipulates device sliders - all changes are recorded
4. Clicks "Stop Recording" to end capture
5. Save dialog appears with recording preview and form
6. User enters title, description, tags and saves
7. Recording appears in dashboard /me Recordings tab
8. Can play back, edit, or delete recordings
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>