Initial implementation of Bluetooth toy control app
CI / Static checks (push) Successful in 1m27s
CI / Build and push image (push) Skipped

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
This commit is contained in:
2026-08-25 07:51:38 +02:00
co-authored by Claude Sonnet 5
commit 1119c8eea0
112 changed files with 15522 additions and 0 deletions
+46
View File
@@ -0,0 +1,46 @@
import type { RecordingDeviceSlot } from "@/lib/db/schema";
import type { ConnectedDeviceInfo } from "./types";
export interface DeviceRemapEntry {
slotLabel: string;
recordedBleName: string;
matchedDeviceIndex: number | null;
}
/**
* Best-effort name match from a recording's saved device slots to the
* currently-connected devices. Web Bluetooth exposes no stable hardware id
* across sessions, so this is inherently approximate: two devices sharing an
* identical advertised name are indistinguishable and must be disambiguated
* manually in the remap UI - this isn't a bug, it's a hard BLE limitation.
*/
export function autoMapDeviceSlots(
slots: RecordingDeviceSlot[],
connectedDevices: ConnectedDeviceInfo[],
): DeviceRemapEntry[] {
const usedIndexes = new Set<number>();
return slots.map((slot) => {
const normalizedSlotName = slot.recordedBleName.trim().toLowerCase();
const exact = connectedDevices.find(
(d) => !usedIndexes.has(d.index) && d.name.trim().toLowerCase() === normalizedSlotName,
);
const partial =
exact ??
connectedDevices.find(
(d) =>
!usedIndexes.has(d.index) &&
(d.name.toLowerCase().includes(normalizedSlotName) ||
normalizedSlotName.includes(d.name.toLowerCase())),
);
if (partial) usedIndexes.add(partial.index);
return {
slotLabel: slot.slotLabel,
recordedBleName: slot.recordedBleName,
matchedDeviceIndex: partial?.index ?? null,
};
});
}