- SessionTimelineChart: adds explicit gap between the tooltip's device
label and its value via a custom formatter, instead of relying on
justify-between's leftover-space spacing, which collapsed to zero in
the narrow single-item tooltip
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BuWtppPBizG2NQRa31FCRa
- .bp-glass: recolors Card's default ring to the accent token
(ring-accent) instead of leaving it at foreground/10; border-accent
is currently a no-op since nothing sets a border-width utility
- Dashboard hero card: tightens vertical padding (py-8 -> py-4)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Lightens .bp-glass's shadow to shadow-xs (from shadow-xl). Card's own
default ring is no longer neutralized here, so it shows through on
glass surfaces again.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Simplifies .bp-glass: drops the border and hover state, and applies
shadow-xl by default. Also zeroes the ring/border Card's own base
styles would otherwise still contribute, so glass surfaces render
with no border/ring/outline at all.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds a shadcn Alert for the "no devices connected" hints on Control
and Replay, then reworks the navigation: an animated hamburger/close
toggle, a redesigned mobile drawer (brand header, icon nav rows,
theme/logout moved into the drawer footer), and a desktop tab strip
restyled to match the drawer's active-tab treatment.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Devices table's editable name field now matches the session
title/description underline style app-wide (hover/focus turn primary,
primary caret, default text color), and Devices moved before Stats in
the nav order.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WzyfRyA7h5rs5SCAahLAWd
Control view button layout fixes (buttons sit next to each other, wrap
responsively on narrow screens), Replay view streamlined to match the
Control view (title includes the session name, buttons below the
header, matching "no devices connected" hint card, Play icon on
"Match & replay"), and the session-status Badge color mismatch between
the sessions overview and detail view fixed.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WzyfRyA7h5rs5SCAahLAWd
Inline-editable session title/description (autosave, no Save button,
same pattern as device display names), status badge on the session
detail page, dashboard's Recent sessions restyled as a real table,
stats page tab switcher removed, and various card/nav polish.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WzyfRyA7h5rs5SCAahLAWd
Recordings were just a thin named pointer over an already-captured
session's events, so the whole separate feature (recordings table, API
routes, pages, UI) is gone: any completed session can now be named and
replayed directly. Replaying no longer creates a session or duplicates
events of its own - it just bumps the source session's playCount/lastPlayedAt.
Also renames play_sessions/playSession(s) to sessions/session(s) throughout
the schema, queries, API routes, and UI for consistency, and updates the
README to match the new flow.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds a Footer component (credit line, repo/buttplug.io links) below main
content on all app pages, with the layout shell now flex-column so it
sticks to the bottom on short pages. Also swaps the display font from
Space Grotesk to Sora (after trying Fraunces, a serif, which didn't fit).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Every app/api route handler now goes through withRouteLogging, which logs
a correlated reqId/method/path/status/duration for each request and turns
any uncaught error into a logged stack trace plus a clean JSON 500 instead
of Next's default opaque failure. proxy.ts logs rejected auth attempts, and
instrumentation.ts's onRequestError catches anything that still escapes a
route handler. Also adds a minimal not-found page matching the app's card
styling.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Fix device rename input collapsing on mobile (fixed width vs w-full
inside an auto-layout table column).
- Add cascade-delete confirmation for sessions with a saved recording.
- Fix header connection LED: derive state from scanning/device-count/
recording instead of the Buttplug client's raw connected flag; drop
the label text and hide the indicator entirely when idle.
- Add a per-device disconnect button (stop + remove from store, the
closest equivalent Buttplug's protocol allows per device).
- Fix replay ending early: duration now comes from the recording's
actual durationMs, not the last event's timestamp.
- Replay robustness: show which devices are being replayed to, reset
actuators to zero on start/play/pause, fully disconnect devices and
the whole client on stop/unmount, surface command failures via toast.
- Stop flagging the header LED red during replay - recording is only
for live sessions.
- Add page-number pagination to recordings/sessions/devices lists.
- Wordmark SEXY -> Sexy; add proper per-page <title>s including
dynamic titles for recording/session detail and replay pages.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
getSessionTimeline() grouped/ordered by a bare `bucket` identifier,
assuming SQLite would resolve it against the SELECT list's alias -
it doesn't in this generated query, so every /sessions/:id page and
/api/stats/sessions/:id/timeline request failed with "no such column:
bucket". Fixed by reusing the actual bucket expression object in
groupBy/orderBy instead of referencing it by name. Verified against a
seeded session - previously every request, now correct bucketed data.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W8WkFF5ppURBAB918593Eb
buttplug-wasm@3.0.0 still declares buttplug@^4.0.2 as its dependency,
and v5 changed the OutputCmd wire shape (Value: number[] -> number) -
a real protocol difference, not just a type mismatch. Confirmed by
testing against real Lovense hardware that device control still works
in practice, so the bump stands; the risk is documented at the
client.connect() cast in case a future device/build doesn't fare as
well.
Also fixes a Map vs ReadonlyMap mismatch in the version-agnostic
feature-type extraction (v4 returns Map, v5 returns ReadonlyMap) that
surfaced while making this change, and adds a battery indicator to each
connected device's card - reads once on connect, refreshes every 60s,
using device.hasInput("Battery") to detect support.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W8WkFF5ppURBAB918593Eb
manifest.ts uses icon.svg and the app's palette for background/theme
color. Excluded /manifest.webmanifest from the auth gate in proxy.ts,
same as icon.svg/favicon.ico - it must be publicly fetchable regardless
of login state.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W8WkFF5ppURBAB918593Eb
Next.js app with a browser-side buttplug/buttplug-wasm control layer
(server never touches real-time device commands), SQLite storage via
Drizzle, single-secret auth, recordings/replay with device remapping,
a usage stats dashboard, Docker deployment, and Gitea CI.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W8WkFF5ppURBAB918593Eb