@hadoku/watchparty-ui 0.43.17 → 0.43.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (20) hide show
  1. package/dist/chunks/{AdminPanel-Cv1k8xMS.js → AdminPanel-D9TccD0Q.js} +4 -4
  2. package/dist/chunks/{AdminPanel-Cv1k8xMS.js.map → AdminPanel-D9TccD0Q.js.map} +1 -1
  3. package/dist/chunks/{CharacterModal-D-G2VdZx.js → CharacterModal-D5zv4le2.js} +2 -2
  4. package/dist/chunks/{CharacterModal-D-G2VdZx.js.map → CharacterModal-D5zv4le2.js.map} +1 -1
  5. package/dist/chunks/{GameModule-CplsrOUf.js → GameModule-BzHJYaGz.js} +4 -4
  6. package/dist/chunks/{GameModule-CplsrOUf.js.map → GameModule-BzHJYaGz.js.map} +1 -1
  7. package/dist/chunks/{MapEditor-ByJMZjWK.js → MapEditor-LWBaRIhv.js} +2 -2
  8. package/dist/chunks/{MapEditor-ByJMZjWK.js.map → MapEditor-LWBaRIhv.js.map} +1 -1
  9. package/dist/chunks/{MediaDrawer-BBcFCxsD.js → MediaDrawer-CwO398a6.js} +4 -4
  10. package/dist/chunks/{MediaDrawer-BBcFCxsD.js.map → MediaDrawer-CwO398a6.js.map} +1 -1
  11. package/dist/chunks/{PlaylistPanel-BWjgMqdL.js → PlaylistPanel-Be12d3DY.js} +3 -3
  12. package/dist/chunks/{PlaylistPanel-BWjgMqdL.js.map → PlaylistPanel-Be12d3DY.js.map} +1 -1
  13. package/dist/chunks/{Room-Bana0IVO.js → Room-5-BsgWgy.js} +4 -4
  14. package/dist/chunks/{Room-Bana0IVO.js.map → Room-5-BsgWgy.js.map} +1 -1
  15. package/dist/chunks/{index-BcsCCk6O.js → index-BLI7w-vZ.js} +3 -3
  16. package/dist/chunks/{index-BcsCCk6O.js.map → index-BLI7w-vZ.js.map} +1 -1
  17. package/dist/chunks/{submissionsApi-G64fVNHM.js → submissionsApi-CzOfHXTJ.js} +2 -2
  18. package/dist/chunks/{submissionsApi-G64fVNHM.js.map → submissionsApi-CzOfHXTJ.js.map} +1 -1
  19. package/dist/index.js +1 -1
  20. package/package.json +3 -3
@@ -1,8 +1,8 @@
1
1
  import { jsxs as n, jsx as t } from "react/jsx-runtime";
2
- import { h as R, aK as M, W as E } from "./index-BcsCCk6O.js";
2
+ import { h as R, aK as M, W as E } from "./index-BLI7w-vZ.js";
3
3
  import { useState as f, useRef as A, useCallback as F, useEffect as I } from "react";
4
- import { l as $, a as w, m as P, n as B, r as T, o as D, p as C } from "./submissionsApi-G64fVNHM.js";
5
- import { c as O, B as U } from "./Room-Bana0IVO.js";
4
+ import { l as $, a as w, m as P, n as B, r as T, o as D, p as C } from "./submissionsApi-CzOfHXTJ.js";
5
+ import { c as O, B as U } from "./Room-5-BsgWgy.js";
6
6
  const H = 5e3, K = 8e3;
7
7
  function q() {
8
8
  const [s, r] = f(null), [d, m] = f(null), [c, a] = f({}), [i, p] = f(null), [x, b] = f(null), [v, h] = f(""), [y, L] = f(() => Date.now()), g = A(!0), _ = F(async () => {
@@ -224,4 +224,4 @@ function W({ client: s, initial: r }) {
224
224
  export {
225
225
  Z as default
226
226
  };
227
- //# sourceMappingURL=AdminPanel-Cv1k8xMS.js.map
227
+ //# sourceMappingURL=AdminPanel-D9TccD0Q.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"AdminPanel-Cv1k8xMS.js","sources":["../../src/components/SubmissionQueue.tsx","../../src/components/AdminPanel.tsx"],"sourcesContent":["/**\n * The operator's moderation queue — every `pending` submission, with approve\n * and reject per row. Service tier only (the routes are `serviceAuth`; the\n * caller gates rendering on `isPrivileged()`, and a refusal is shown rather\n * than hidden if the gate and the server ever disagree).\n *\n * A row the server says has already moved (409, or 404 once it is gone) is not\n * an error in the click — a stale queue page is the ordinary way to meet it —\n * so it stays in place carrying the outcome, then drops off after\n * `NOTE_LINGER_MS`. Dropping it at once would make the click look like it did\n * nothing.\n *\n * Buttons carry real classes (`.drawer-btn`): the unlayered `button` reset\n * discards every Tailwind colour/font utility on a `<button>`.\n */\nimport { useCallback, useEffect, useRef, useState } from 'react';\nimport type { TSubmission } from '@watchparty/shared';\n/** `TSubmission` plus the `series` field the shared schema gains for magnets\n * (packages/shared/src/submissions.ts) — a cast rather than a schema edit,\n * since this file does not own that schema; harmless once the real field\n * lands, since the shapes agree. */\ntype SubmissionWithSeries = TSubmission & { series?: string };\nimport {\n approveSubmission,\n decisionFailureLabel,\n decisionMeansRowMoved,\n fetchPendingSubmissions,\n formatSubmissionAge,\n rejectSubmission,\n submissionFailureLabel,\n type SubmissionFailure,\n} from '../lib/submissionsApi';\n\nconst POLL_MS = 5000;\nconst NOTE_LINGER_MS = 8000;\n\ninterface Note { row: TSubmission; message: string; tone: 'info' | 'error' }\n\nexport default function SubmissionQueue() {\n const [rows, setRows] = useState<TSubmission[] | null>(null);\n const [listFailure, setListFailure] = useState<SubmissionFailure | null>(null);\n const [notes, setNotes] = useState<Record<string, Note>>({});\n const [busyId, setBusyId] = useState<string | null>(null);\n const [rejectingId, setRejectingId] = useState<string | null>(null);\n const [reason, setReason] = useState('');\n const [now, setNow] = useState(() => Date.now());\n const liveRef = useRef(true);\n\n const refresh = useCallback(async () => {\n const res = await fetchPendingSubmissions();\n if (!liveRef.current) return;\n setNow(Date.now());\n if (res.ok) { setRows(res.value); setListFailure(null); }\n else { setListFailure(res.failure); }\n }, []);\n\n useEffect(() => {\n liveRef.current = true;\n // Deferred a tick: the first read is a subscription callback, not a\n // synchronous setState in the effect body.\n const first = setTimeout(() => { void refresh(); }, 0);\n const timer = setInterval(() => { void refresh(); }, POLL_MS);\n return () => { liveRef.current = false; clearTimeout(first); clearInterval(timer); };\n }, [refresh]);\n\n function leaveNote(row: TSubmission, message: string, tone: Note['tone']) {\n setNotes(prev => ({ ...prev, [row.id]: { row, message, tone } }));\n setTimeout(() => {\n if (!liveRef.current) return;\n setNotes(prev => Object.fromEntries(Object.entries(prev).filter(([id]) => id !== row.id)));\n }, NOTE_LINGER_MS);\n }\n\n async function decide(row: TSubmission, verb: 'approve' | 'reject') {\n if (busyId) return;\n setBusyId(row.id);\n const res = verb === 'approve'\n ? await approveSubmission(row.id)\n : await rejectSubmission(row.id, reason);\n setBusyId(null);\n if (!liveRef.current) return;\n if (res.ok) {\n setRejectingId(null);\n setReason('');\n setRows(prev => (prev ?? []).filter(r => r.id !== row.id));\n leaveNote(row, res.value.state, 'info');\n } else if (decisionMeansRowMoved(res.failure)) {\n setRejectingId(null);\n setRows(prev => (prev ?? []).filter(r => r.id !== row.id));\n leaveNote(row, decisionFailureLabel(res.failure), 'error');\n } else {\n leaveNote(row, decisionFailureLabel(res.failure), 'error');\n }\n void refresh();\n }\n\n const shown = [\n ...(rows ?? []).map(r => ({ row: r, note: notes[r.id] })),\n ...Object.values(notes)\n .filter(n => !(rows ?? []).some(r => r.id === n.row.id))\n .map(n => ({ row: n.row, note: n })),\n ];\n\n // Nothing to moderate is shown as nothing — no header, no \"(none)\". A list\n // failure still renders, since that IS state the leader needs to see.\n if (!listFailure && shown.length === 0) return null;\n\n return (\n <section className=\"border-t border-border pt-2\" data-testid=\"submission-queue\">\n <div className=\"flex items-center px-1 pb-1\">\n <span className=\"text-[10px] uppercase tracking-wider text-text-muted\">Submissions</span>\n <span className=\"ml-1 text-[10px] text-text-muted\" data-testid=\"submission-queue-count\">\n {rows?.length ?? 0}\n </span>\n </div>\n {listFailure && (\n <div className=\"px-2 py-1 text-[11px] bg-danger-bg text-on-danger-bg rounded\">{submissionFailureLabel(listFailure)}</div>\n )}\n <ul className=\"list-none m-0 p-0\">\n {shown.map(({ row, note }) => {\n const decided = note && !(rows ?? []).some(r => r.id === row.id);\n return (\n <li\n key={row.id}\n className=\"px-1 py-1.5 border-b border-border/50 flex flex-col gap-1\"\n data-submission-id={row.id}\n data-submission-state={decided ? 'decided' : row.state}\n data-submission-kind={row.kind}\n data-submission-series={(row as SubmissionWithSeries).series ?? ''}\n >\n <div className=\"flex items-center gap-2 text-[11px] min-w-0\">\n <span className=\"text-[10px] uppercase tracking-wider text-text-muted shrink-0\">{row.kind}</span>\n <span className=\"text-text truncate flex-1\" title={row.source}>\n {(row as SubmissionWithSeries).series\n ? `${(row as SubmissionWithSeries).series} — ${row.title || row.source}`\n : (row.title || row.source)}\n </span>\n <span className=\"text-text-muted shrink-0\">{formatSubmissionAge(row.createdAt, now)}</span>\n </div>\n <div className=\"text-[10px] text-text-muted truncate\" title={row.ownerUserId}>{row.ownerUserId}</div>\n {note && (\n <div\n data-testid=\"submission-decision-note\"\n className={`text-[11px] px-1.5 py-0.5 rounded ${note.tone === 'error' ? 'bg-danger-bg text-on-danger-bg' : 'bg-success-bg text-on-success-bg'}`}\n >\n {note.message}\n </div>\n )}\n {!decided && rejectingId === row.id ? (\n <div className=\"flex items-center gap-1\">\n <input\n value={reason}\n onChange={e => setReason(e.target.value)}\n onKeyDown={e => { if (e.key === 'Enter') void decide(row, 'reject'); }}\n aria-label=\"Reject reason\"\n placeholder=\"reason\"\n className=\"flex-1 min-w-0 bg-bg-alt border border-border outline-none px-2 py-0.5 rounded text-[11px]\"\n />\n <button\n className=\"drawer-btn drawer-btn-danger\"\n aria-label=\"Confirm reject\"\n disabled={busyId === row.id}\n onClick={() => { void decide(row, 'reject'); }}\n >\n Reject\n </button>\n <button\n className=\"drawer-btn\"\n aria-label=\"Cancel reject\"\n onClick={() => { setRejectingId(null); setReason(''); }}\n >\n Cancel\n </button>\n </div>\n ) : !decided && (\n <div className=\"flex items-center gap-1\">\n <button\n className=\"drawer-btn drawer-btn-primary\"\n aria-label=\"Approve submission\"\n disabled={busyId === row.id}\n onClick={() => { void decide(row, 'approve'); }}\n >\n Approve\n </button>\n <button\n className=\"drawer-btn\"\n aria-label=\"Reject submission\"\n disabled={busyId === row.id}\n onClick={() => { setRejectingId(row.id); setReason(''); }}\n >\n Reject\n </button>\n </div>\n )}\n </li>\n );\n })}\n </ul>\n </section>\n );\n}\n","import { useAppStore } from '../lib/store';\nimport { BUFFER_AHEAD_MS, type TClientHealth } from '@watchparty/shared';\nimport { isPrivileged } from '../lib/auth';\nimport SubmissionQueue from './SubmissionQueue';\nimport { bufferHealthLevel, BUFFER_HEALTH_LABEL } from './bufferHealth';\n\n/**\n * Health monitor panel — renders as sidebar content (replaces chat when active).\n * Shows a card per connected client with live health metrics.\n */\nexport default function AdminPanel() {\n const healthSnapshot = useAppStore(s => s.healthSnapshot);\n\n const clients = healthSnapshot?.clients ?? [];\n // Slowest download first — easiest to spot struggling clients\n const sorted = [...clients].sort((a, b) => a.avg_download_rate_bps - b.avg_download_rate_bps);\n\n const initials = computeInitials(sorted);\n // Moderation is service tier, like the routes behind it.\n const queue = isPrivileged() ? <SubmissionQueue /> : null;\n\n if (clients.length === 0) {\n return (\n <div className=\"flex-1 overflow-y-auto p-2 flex flex-col\">\n <div className=\"flex-1 flex items-center justify-center p-4\">\n <p className=\"text-text-muted text-xs text-center\">\n No health data yet.<br />Clients report once media is loaded.\n </p>\n </div>\n {queue}\n </div>\n );\n }\n\n return (\n <div className=\"flex-1 overflow-y-auto p-2 space-y-2\">\n {sorted.map(c => <ClientCard key={c.client_id} client={c} initial={initials[c.client_id]} />)}\n {queue}\n </div>\n );\n}\n\n/* ------------------------------------------------------------------ */\n\nfunction formatTime(ms: number): string {\n const totalSecs = Math.floor(ms / 1000);\n const mins = Math.floor(totalSecs / 60);\n const secs = totalSecs % 60;\n return `${mins}:${secs.toString().padStart(2, '0')}`;\n}\n\n// Initials are first alphanumeric of display name; on conflict, advance to next\n// unused alphanumeric letter in the same name. Stable across renders via client_id sort.\nfunction computeInitials(clients: TClientHealth[]): Record<string, string> {\n const out: Record<string, string> = {};\n const taken = new Set<string>();\n const ordered = [...clients].sort((a, b) => a.client_id.localeCompare(b.client_id));\n for (const c of ordered) {\n const name = c.name ?? '';\n let chosen = '?';\n for (const ch of name) {\n if (!/[\\p{L}\\p{N}]/u.test(ch)) continue;\n const up = ch.toUpperCase();\n if (!taken.has(up)) { chosen = up; taken.add(up); break; }\n }\n out[c.client_id] = chosen;\n }\n return out;\n}\n\nfunction ClientCard({ client, initial }: { client: TClientHealth; initial: string }) {\n const dlKbps = Math.round(client.avg_download_rate_bps / 1024);\n const dlMbps = (dlKbps / 1024).toFixed(1);\n const bufferS = (client.buffered_ahead_ms / 1000).toFixed(1);\n const rtt = Math.round(client.rtt_ms);\n\n // Idle: not playing, no meaningful position — don't alarm about \"Critical\".\n // Near end-of-stream is exempt from the buffered-ahead thresholds (see\n // bufferHealth.ts) so the final seconds of every episode don't false-Critical.\n const level = bufferHealthLevel(client);\n // A connected client whose reports stopped arriving: the figures below are\n // its last known state, and the silence itself is the status.\n const silent = client.stale_ms !== undefined;\n const statusLabel = silent ? `Silent ${Math.round(client.stale_ms! / 1000)}s` : BUFFER_HEALTH_LABEL[level];\n const isRed = silent || level === 'critical' || level === 'buffering';\n const statusClasses = isRed\n ? 'text-danger bg-danger/10'\n : level === 'idle'\n ? 'text-text-muted bg-bg-alt/40'\n : level === 'warning'\n ? 'text-on-neutral-bg bg-neutral/10'\n : 'text-success bg-success/10';\n\n const barColor = isRed ? 'bg-danger' : level === 'warning' ? 'bg-neutral-bg' : 'bg-success';\n const barPct = Math.min(100, (client.buffered_ahead_ms / BUFFER_AHEAD_MS) * 100);\n\n return (\n <div className=\"rounded-lg border border-border/50 bg-bg-alt/20 p-2.5\" data-silent={silent || undefined}>\n {/* Row 1: Name + Status */}\n <div className=\"flex items-center justify-between mb-2\">\n <div className=\"flex items-center gap-2 min-w-0\">\n <div className=\"w-5 h-5 rounded-full bg-bg-alt flex items-center justify-center text-[9px] font-semibold shrink-0\">\n {initial}\n </div>\n <span className=\"text-sm font-medium text-text truncate\">{client.name}</span>\n </div>\n <span className={`inline-flex items-center px-1.5 py-0.5 rounded text-[10px] font-medium shrink-0 ${statusClasses}`}>\n {statusLabel}\n </span>\n </div>\n\n {/* Row 2: Stats grid */}\n <div className={`grid grid-cols-3 gap-x-2 gap-y-1 text-[11px] ${silent ? 'opacity-50' : ''}`}>\n <div>\n <span className=\"text-text-muted\">DL</span>\n <span className=\"ml-1 font-mono text-text\">{dlMbps}</span>\n <span className=\"text-text-muted ml-0.5\">Mbps</span>\n </div>\n <div>\n <span className=\"text-text-muted\">Ping</span>\n <span className=\"ml-1 font-mono text-text\">{rtt}</span>\n <span className=\"text-text-muted ml-0.5\">ms</span>\n </div>\n <div>\n <span className=\"text-text-muted\">Pos</span>\n <span className=\"ml-1 font-mono text-text-secondary\">{formatTime(client.playhead_ms)}</span>\n </div>\n\n {/* Buffer row spans full width */}\n <div className=\"col-span-2 flex items-center gap-1.5\">\n <span className=\"text-text-muted\">Buf</span>\n <span className=\"font-mono text-text\">{bufferS}s</span>\n <div className=\"flex-1 h-1 bg-bg-alt rounded-full overflow-hidden\">\n <div className={`h-full rounded-full transition-all ${barColor}`} style={{ width: `${barPct}%` }} />\n </div>\n </div>\n <div className=\"flex gap-1 font-mono justify-end\">\n <span className=\"bg-success-bg text-on-success-bg px-1 rounded\">{client.fragments_loaded}L</span>\n <span className=\"text-text-muted\">{client.fragments_pending}P</span>\n {client.fragments_error > 0 && <span className=\"bg-danger-bg text-on-danger-bg px-1 rounded\">{client.fragments_error}E</span>}\n </div>\n </div>\n </div>\n );\n}\n"],"names":["POLL_MS","NOTE_LINGER_MS","SubmissionQueue","rows","setRows","useState","listFailure","setListFailure","notes","setNotes","busyId","setBusyId","rejectingId","setRejectingId","reason","setReason","now","setNow","liveRef","useRef","refresh","useCallback","res","fetchPendingSubmissions","useEffect","first","timer","leaveNote","row","message","tone","prev","id","decide","verb","approveSubmission","rejectSubmission","r","decisionMeansRowMoved","decisionFailureLabel","shown","n","jsxs","jsx","submissionFailureLabel","note","decided","e","AdminPanel","clients","useAppStore","s","sorted","b","initials","computeInitials","queue","isPrivileged","c","ClientCard","formatTime","ms","totalSecs","mins","secs","out","taken","ordered","a","name","chosen","ch","up","client","initial","dlMbps","bufferS","rtt","level","bufferHealthLevel","silent","statusLabel","BUFFER_HEALTH_LABEL","isRed","statusClasses","barColor","barPct","BUFFER_AHEAD_MS"],"mappings":"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"}
1
+ {"version":3,"file":"AdminPanel-D9TccD0Q.js","sources":["../../src/components/SubmissionQueue.tsx","../../src/components/AdminPanel.tsx"],"sourcesContent":["/**\n * The operator's moderation queue — every `pending` submission, with approve\n * and reject per row. Service tier only (the routes are `serviceAuth`; the\n * caller gates rendering on `isPrivileged()`, and a refusal is shown rather\n * than hidden if the gate and the server ever disagree).\n *\n * A row the server says has already moved (409, or 404 once it is gone) is not\n * an error in the click — a stale queue page is the ordinary way to meet it —\n * so it stays in place carrying the outcome, then drops off after\n * `NOTE_LINGER_MS`. Dropping it at once would make the click look like it did\n * nothing.\n *\n * Buttons carry real classes (`.drawer-btn`): the unlayered `button` reset\n * discards every Tailwind colour/font utility on a `<button>`.\n */\nimport { useCallback, useEffect, useRef, useState } from 'react';\nimport type { TSubmission } from '@watchparty/shared';\n/** `TSubmission` plus the `series` field the shared schema gains for magnets\n * (packages/shared/src/submissions.ts) — a cast rather than a schema edit,\n * since this file does not own that schema; harmless once the real field\n * lands, since the shapes agree. */\ntype SubmissionWithSeries = TSubmission & { series?: string };\nimport {\n approveSubmission,\n decisionFailureLabel,\n decisionMeansRowMoved,\n fetchPendingSubmissions,\n formatSubmissionAge,\n rejectSubmission,\n submissionFailureLabel,\n type SubmissionFailure,\n} from '../lib/submissionsApi';\n\nconst POLL_MS = 5000;\nconst NOTE_LINGER_MS = 8000;\n\ninterface Note { row: TSubmission; message: string; tone: 'info' | 'error' }\n\nexport default function SubmissionQueue() {\n const [rows, setRows] = useState<TSubmission[] | null>(null);\n const [listFailure, setListFailure] = useState<SubmissionFailure | null>(null);\n const [notes, setNotes] = useState<Record<string, Note>>({});\n const [busyId, setBusyId] = useState<string | null>(null);\n const [rejectingId, setRejectingId] = useState<string | null>(null);\n const [reason, setReason] = useState('');\n const [now, setNow] = useState(() => Date.now());\n const liveRef = useRef(true);\n\n const refresh = useCallback(async () => {\n const res = await fetchPendingSubmissions();\n if (!liveRef.current) return;\n setNow(Date.now());\n if (res.ok) { setRows(res.value); setListFailure(null); }\n else { setListFailure(res.failure); }\n }, []);\n\n useEffect(() => {\n liveRef.current = true;\n // Deferred a tick: the first read is a subscription callback, not a\n // synchronous setState in the effect body.\n const first = setTimeout(() => { void refresh(); }, 0);\n const timer = setInterval(() => { void refresh(); }, POLL_MS);\n return () => { liveRef.current = false; clearTimeout(first); clearInterval(timer); };\n }, [refresh]);\n\n function leaveNote(row: TSubmission, message: string, tone: Note['tone']) {\n setNotes(prev => ({ ...prev, [row.id]: { row, message, tone } }));\n setTimeout(() => {\n if (!liveRef.current) return;\n setNotes(prev => Object.fromEntries(Object.entries(prev).filter(([id]) => id !== row.id)));\n }, NOTE_LINGER_MS);\n }\n\n async function decide(row: TSubmission, verb: 'approve' | 'reject') {\n if (busyId) return;\n setBusyId(row.id);\n const res = verb === 'approve'\n ? await approveSubmission(row.id)\n : await rejectSubmission(row.id, reason);\n setBusyId(null);\n if (!liveRef.current) return;\n if (res.ok) {\n setRejectingId(null);\n setReason('');\n setRows(prev => (prev ?? []).filter(r => r.id !== row.id));\n leaveNote(row, res.value.state, 'info');\n } else if (decisionMeansRowMoved(res.failure)) {\n setRejectingId(null);\n setRows(prev => (prev ?? []).filter(r => r.id !== row.id));\n leaveNote(row, decisionFailureLabel(res.failure), 'error');\n } else {\n leaveNote(row, decisionFailureLabel(res.failure), 'error');\n }\n void refresh();\n }\n\n const shown = [\n ...(rows ?? []).map(r => ({ row: r, note: notes[r.id] })),\n ...Object.values(notes)\n .filter(n => !(rows ?? []).some(r => r.id === n.row.id))\n .map(n => ({ row: n.row, note: n })),\n ];\n\n // Nothing to moderate is shown as nothing — no header, no \"(none)\". A list\n // failure still renders, since that IS state the leader needs to see.\n if (!listFailure && shown.length === 0) return null;\n\n return (\n <section className=\"border-t border-border pt-2\" data-testid=\"submission-queue\">\n <div className=\"flex items-center px-1 pb-1\">\n <span className=\"text-[10px] uppercase tracking-wider text-text-muted\">Submissions</span>\n <span className=\"ml-1 text-[10px] text-text-muted\" data-testid=\"submission-queue-count\">\n {rows?.length ?? 0}\n </span>\n </div>\n {listFailure && (\n <div className=\"px-2 py-1 text-[11px] bg-danger-bg text-on-danger-bg rounded\">{submissionFailureLabel(listFailure)}</div>\n )}\n <ul className=\"list-none m-0 p-0\">\n {shown.map(({ row, note }) => {\n const decided = note && !(rows ?? []).some(r => r.id === row.id);\n return (\n <li\n key={row.id}\n className=\"px-1 py-1.5 border-b border-border/50 flex flex-col gap-1\"\n data-submission-id={row.id}\n data-submission-state={decided ? 'decided' : row.state}\n data-submission-kind={row.kind}\n data-submission-series={(row as SubmissionWithSeries).series ?? ''}\n >\n <div className=\"flex items-center gap-2 text-[11px] min-w-0\">\n <span className=\"text-[10px] uppercase tracking-wider text-text-muted shrink-0\">{row.kind}</span>\n <span className=\"text-text truncate flex-1\" title={row.source}>\n {(row as SubmissionWithSeries).series\n ? `${(row as SubmissionWithSeries).series} — ${row.title || row.source}`\n : (row.title || row.source)}\n </span>\n <span className=\"text-text-muted shrink-0\">{formatSubmissionAge(row.createdAt, now)}</span>\n </div>\n <div className=\"text-[10px] text-text-muted truncate\" title={row.ownerUserId}>{row.ownerUserId}</div>\n {note && (\n <div\n data-testid=\"submission-decision-note\"\n className={`text-[11px] px-1.5 py-0.5 rounded ${note.tone === 'error' ? 'bg-danger-bg text-on-danger-bg' : 'bg-success-bg text-on-success-bg'}`}\n >\n {note.message}\n </div>\n )}\n {!decided && rejectingId === row.id ? (\n <div className=\"flex items-center gap-1\">\n <input\n value={reason}\n onChange={e => setReason(e.target.value)}\n onKeyDown={e => { if (e.key === 'Enter') void decide(row, 'reject'); }}\n aria-label=\"Reject reason\"\n placeholder=\"reason\"\n className=\"flex-1 min-w-0 bg-bg-alt border border-border outline-none px-2 py-0.5 rounded text-[11px]\"\n />\n <button\n className=\"drawer-btn drawer-btn-danger\"\n aria-label=\"Confirm reject\"\n disabled={busyId === row.id}\n onClick={() => { void decide(row, 'reject'); }}\n >\n Reject\n </button>\n <button\n className=\"drawer-btn\"\n aria-label=\"Cancel reject\"\n onClick={() => { setRejectingId(null); setReason(''); }}\n >\n Cancel\n </button>\n </div>\n ) : !decided && (\n <div className=\"flex items-center gap-1\">\n <button\n className=\"drawer-btn drawer-btn-primary\"\n aria-label=\"Approve submission\"\n disabled={busyId === row.id}\n onClick={() => { void decide(row, 'approve'); }}\n >\n Approve\n </button>\n <button\n className=\"drawer-btn\"\n aria-label=\"Reject submission\"\n disabled={busyId === row.id}\n onClick={() => { setRejectingId(row.id); setReason(''); }}\n >\n Reject\n </button>\n </div>\n )}\n </li>\n );\n })}\n </ul>\n </section>\n );\n}\n","import { useAppStore } from '../lib/store';\nimport { BUFFER_AHEAD_MS, type TClientHealth } from '@watchparty/shared';\nimport { isPrivileged } from '../lib/auth';\nimport SubmissionQueue from './SubmissionQueue';\nimport { bufferHealthLevel, BUFFER_HEALTH_LABEL } from './bufferHealth';\n\n/**\n * Health monitor panel — renders as sidebar content (replaces chat when active).\n * Shows a card per connected client with live health metrics.\n */\nexport default function AdminPanel() {\n const healthSnapshot = useAppStore(s => s.healthSnapshot);\n\n const clients = healthSnapshot?.clients ?? [];\n // Slowest download first — easiest to spot struggling clients\n const sorted = [...clients].sort((a, b) => a.avg_download_rate_bps - b.avg_download_rate_bps);\n\n const initials = computeInitials(sorted);\n // Moderation is service tier, like the routes behind it.\n const queue = isPrivileged() ? <SubmissionQueue /> : null;\n\n if (clients.length === 0) {\n return (\n <div className=\"flex-1 overflow-y-auto p-2 flex flex-col\">\n <div className=\"flex-1 flex items-center justify-center p-4\">\n <p className=\"text-text-muted text-xs text-center\">\n No health data yet.<br />Clients report once media is loaded.\n </p>\n </div>\n {queue}\n </div>\n );\n }\n\n return (\n <div className=\"flex-1 overflow-y-auto p-2 space-y-2\">\n {sorted.map(c => <ClientCard key={c.client_id} client={c} initial={initials[c.client_id]} />)}\n {queue}\n </div>\n );\n}\n\n/* ------------------------------------------------------------------ */\n\nfunction formatTime(ms: number): string {\n const totalSecs = Math.floor(ms / 1000);\n const mins = Math.floor(totalSecs / 60);\n const secs = totalSecs % 60;\n return `${mins}:${secs.toString().padStart(2, '0')}`;\n}\n\n// Initials are first alphanumeric of display name; on conflict, advance to next\n// unused alphanumeric letter in the same name. Stable across renders via client_id sort.\nfunction computeInitials(clients: TClientHealth[]): Record<string, string> {\n const out: Record<string, string> = {};\n const taken = new Set<string>();\n const ordered = [...clients].sort((a, b) => a.client_id.localeCompare(b.client_id));\n for (const c of ordered) {\n const name = c.name ?? '';\n let chosen = '?';\n for (const ch of name) {\n if (!/[\\p{L}\\p{N}]/u.test(ch)) continue;\n const up = ch.toUpperCase();\n if (!taken.has(up)) { chosen = up; taken.add(up); break; }\n }\n out[c.client_id] = chosen;\n }\n return out;\n}\n\nfunction ClientCard({ client, initial }: { client: TClientHealth; initial: string }) {\n const dlKbps = Math.round(client.avg_download_rate_bps / 1024);\n const dlMbps = (dlKbps / 1024).toFixed(1);\n const bufferS = (client.buffered_ahead_ms / 1000).toFixed(1);\n const rtt = Math.round(client.rtt_ms);\n\n // Idle: not playing, no meaningful position — don't alarm about \"Critical\".\n // Near end-of-stream is exempt from the buffered-ahead thresholds (see\n // bufferHealth.ts) so the final seconds of every episode don't false-Critical.\n const level = bufferHealthLevel(client);\n // A connected client whose reports stopped arriving: the figures below are\n // its last known state, and the silence itself is the status.\n const silent = client.stale_ms !== undefined;\n const statusLabel = silent ? `Silent ${Math.round(client.stale_ms! / 1000)}s` : BUFFER_HEALTH_LABEL[level];\n const isRed = silent || level === 'critical' || level === 'buffering';\n const statusClasses = isRed\n ? 'text-danger bg-danger/10'\n : level === 'idle'\n ? 'text-text-muted bg-bg-alt/40'\n : level === 'warning'\n ? 'text-on-neutral-bg bg-neutral/10'\n : 'text-success bg-success/10';\n\n const barColor = isRed ? 'bg-danger' : level === 'warning' ? 'bg-neutral-bg' : 'bg-success';\n const barPct = Math.min(100, (client.buffered_ahead_ms / BUFFER_AHEAD_MS) * 100);\n\n return (\n <div className=\"rounded-lg border border-border/50 bg-bg-alt/20 p-2.5\" data-silent={silent || undefined}>\n {/* Row 1: Name + Status */}\n <div className=\"flex items-center justify-between mb-2\">\n <div className=\"flex items-center gap-2 min-w-0\">\n <div className=\"w-5 h-5 rounded-full bg-bg-alt flex items-center justify-center text-[9px] font-semibold shrink-0\">\n {initial}\n </div>\n <span className=\"text-sm font-medium text-text truncate\">{client.name}</span>\n </div>\n <span className={`inline-flex items-center px-1.5 py-0.5 rounded text-[10px] font-medium shrink-0 ${statusClasses}`}>\n {statusLabel}\n </span>\n </div>\n\n {/* Row 2: Stats grid */}\n <div className={`grid grid-cols-3 gap-x-2 gap-y-1 text-[11px] ${silent ? 'opacity-50' : ''}`}>\n <div>\n <span className=\"text-text-muted\">DL</span>\n <span className=\"ml-1 font-mono text-text\">{dlMbps}</span>\n <span className=\"text-text-muted ml-0.5\">Mbps</span>\n </div>\n <div>\n <span className=\"text-text-muted\">Ping</span>\n <span className=\"ml-1 font-mono text-text\">{rtt}</span>\n <span className=\"text-text-muted ml-0.5\">ms</span>\n </div>\n <div>\n <span className=\"text-text-muted\">Pos</span>\n <span className=\"ml-1 font-mono text-text-secondary\">{formatTime(client.playhead_ms)}</span>\n </div>\n\n {/* Buffer row spans full width */}\n <div className=\"col-span-2 flex items-center gap-1.5\">\n <span className=\"text-text-muted\">Buf</span>\n <span className=\"font-mono text-text\">{bufferS}s</span>\n <div className=\"flex-1 h-1 bg-bg-alt rounded-full overflow-hidden\">\n <div className={`h-full rounded-full transition-all ${barColor}`} style={{ width: `${barPct}%` }} />\n </div>\n </div>\n <div className=\"flex gap-1 font-mono justify-end\">\n <span className=\"bg-success-bg text-on-success-bg px-1 rounded\">{client.fragments_loaded}L</span>\n <span className=\"text-text-muted\">{client.fragments_pending}P</span>\n {client.fragments_error > 0 && <span className=\"bg-danger-bg text-on-danger-bg px-1 rounded\">{client.fragments_error}E</span>}\n </div>\n </div>\n </div>\n );\n}\n"],"names":["POLL_MS","NOTE_LINGER_MS","SubmissionQueue","rows","setRows","useState","listFailure","setListFailure","notes","setNotes","busyId","setBusyId","rejectingId","setRejectingId","reason","setReason","now","setNow","liveRef","useRef","refresh","useCallback","res","fetchPendingSubmissions","useEffect","first","timer","leaveNote","row","message","tone","prev","id","decide","verb","approveSubmission","rejectSubmission","r","decisionMeansRowMoved","decisionFailureLabel","shown","n","jsxs","jsx","submissionFailureLabel","note","decided","e","AdminPanel","clients","useAppStore","s","sorted","b","initials","computeInitials","queue","isPrivileged","c","ClientCard","formatTime","ms","totalSecs","mins","secs","out","taken","ordered","a","name","chosen","ch","up","client","initial","dlMbps","bufferS","rtt","level","bufferHealthLevel","silent","statusLabel","BUFFER_HEALTH_LABEL","isRed","statusClasses","barColor","barPct","BUFFER_AHEAD_MS"],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  import { jsx as r, jsxs as s } from "react/jsx-runtime";
2
2
  import { useState as m, useEffect as b } from "react";
3
- import { aa as A, h as k, aw as N, ab as P, w as F, e as $, R as B, ax as O, s as R, ay as C, d as f, az as T } from "./index-BcsCCk6O.js";
3
+ import { aa as A, h as k, aw as N, ab as P, w as F, e as $, R as B, ax as O, s as R, ay as C, d as f, az as T } from "./index-BLI7w-vZ.js";
4
4
  const p = O();
5
5
  function G({ open: n, onClose: a, roomKey: c, tierlist: u }) {
6
6
  const j = A(), { name: x, sprite: i, setIdentity: M } = k(), h = k((e) => !!(e.me.clientId && e.roster[e.me.clientId]?.registryName)), [o, v] = m(i), [L, w] = m(/* @__PURE__ */ new Set()), [l, d] = m(() => N(p, i));
@@ -155,4 +155,4 @@ function S({ direction: n, hidden: a, onClick: c }) {
155
155
  export {
156
156
  G as default
157
157
  };
158
- //# sourceMappingURL=CharacterModal-D-G2VdZx.js.map
158
+ //# sourceMappingURL=CharacterModal-D5zv4le2.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"CharacterModal-D-G2VdZx.js","sources":["../../src/components/CharacterModal.tsx"],"sourcesContent":["import React, { useEffect, useState } from 'react';\nimport { useAppStore } from '../lib/store';\nimport { sendIdentityUpdate } from '../lib/socket';\nimport { characterPages, pageIndexFor, spriteUrlFor, displayNameFor } from '../lib/characters';\n\nconst PAGES = characterPages();\nimport { useConfig, getServerOrigin } from '../lib/config';\nimport { debug, error, errorFields, warn } from '../lib/debug';\n\ninterface Props {\n open: boolean;\n onClose: () => void;\n roomKey?: string;\n /** The global tierlist, when this page has one. The room chrome mounts this\n * modal around a host app with no tierlist, so it passes nothing and the\n * button is absent. Passed in rather than imported so the chrome bundle\n * never reaches the tierlist client. */\n tierlist?: { open: () => void; warm: () => void };\n}\n\nexport default function CharacterModal({ open, onClose, roomKey, tierlist }: Props) {\n const config = useConfig();\n const { name, sprite, setIdentity } = useAppStore();\n // An account's name is its key-registry name, shown READ-ONLY. It is not\n // renamed from here yet: hadoku_site's POST /session/name rewrites one\n // registry row and splits the key's active/standby rotation pair, so the\n // name reverts at the next swap (hadoku_site, 2026-10-08). Rename lands once\n // self-rename goes through the key lifecycle broker by userId. Without a\n // registry name (a public viewer) the name is simply the character's.\n const registryNamed = useAppStore(s => !!(s.me.clientId && s.roster[s.me.clientId]?.registryName));\n const [localSprite, setLocalSprite] = useState<string | null>(sprite);\n const [usedSprites, setUsedSprites] = useState<Set<string>>(new Set());\n const [page, setPage] = useState(() => pageIndexFor(PAGES, sprite));\n\n // Sync local state when modal opens\n useEffect(() => {\n if (open) {\n setLocalSprite(sprite);\n setPage(pageIndexFor(PAGES, sprite));\n }\n }, [open, sprite]);\n\n // Which sprites other people hold, for greying them out. The grid renders\n // at once with everything available and greys in place when this lands —\n // the portraits are static files and need not wait on it.\n useEffect(() => {\n if (!open || !roomKey) return;\n const url = `${getServerOrigin(config)}/api/rooms/${encodeURIComponent(roomKey)}/sprites`;\n fetch(url)\n .then(res => {\n if (!res.ok) {\n warn('character', 'sprites fetch non-OK', { status: res.status });\n return Promise.reject(new Error(`status ${res.status}`));\n }\n return res.json() as Promise<{ used?: string[] }>;\n })\n .then(data => setUsedSprites(new Set(data.used ?? [])))\n .catch((err: unknown) => {\n error('character', 'sprites fetch failed', errorFields(err));\n setUsedSprites(new Set());\n });\n }, [open, roomKey]);\n\n const updateIdentity = () => {\n // A registry name stays as it is; otherwise the name follows the character.\n const nextName = registryNamed || !localSprite ? name : displayNameFor(localSprite);\n debug('character', 'Updating identity', { name: nextName, sprite: localSprite });\n setIdentity(nextName, localSprite);\n sendIdentityUpdate(nextName, localSprite || undefined);\n onClose();\n };\n\n // Check if a sprite is available (not used by others, or is the current user's sprite)\n const isSpriteAvailable = (c: string) => {\n if (c === sprite) return true; // Current user's sprite is always selectable\n return !usedSprites.has(c);\n };\n\n // Handle backdrop click\n const handleBackdropClick = (e: React.MouseEvent) => {\n if (e.target === e.currentTarget) {\n onClose();\n }\n };\n\n // Handle escape key\n useEffect(() => {\n if (!open) return;\n const handleEscape = (e: KeyboardEvent) => {\n if (e.key === 'Escape') onClose();\n else if (e.key === 'ArrowLeft') setPage(p => Math.max(0, p - 1));\n else if (e.key === 'ArrowRight') setPage(p => Math.min(PAGES.length - 1, p + 1));\n };\n window.addEventListener('keydown', handleEscape);\n return () => window.removeEventListener('keydown', handleEscape);\n }, [open, onClose]);\n\n // Check if there are any changes\n const hasChanges = localSprite !== sprite;\n\n if (!open) return null;\n\n return (\n <div\n className=\"fixed inset-0 bg-black/70 flex items-center justify-center z-50\"\n onMouseDown={handleBackdropClick}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label=\"Change character\"\n style={{ willChange: 'opacity' }}\n >\n <div className=\"bg-bg border border-border rounded-lg shadow-xl w-[340px] mx-4 overflow-hidden\">\n {/* Content */}\n <div className=\"p-4 space-y-4 text-sm\">\n {/* Display Name with close button */}\n <div>\n <div className=\"flex items-center justify-between mb-1\">\n <label className=\"text-xs uppercase tracking-wide opacity-70\">{registryNamed ? 'Display Name' : 'Character'}</label>\n <button\n type=\"button\"\n onClick={onClose}\n className=\"text-text-secondary hover:text-white hover:bg-bg-alt rounded p-1 transition-colors cursor-pointer\"\n aria-label=\"Close\"\n >\n <svg className=\"w-4 h-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M6 18L18 6M6 6l12 12\" />\n </svg>\n </button>\n </div>\n {registryNamed && <div className=\"modal-input\" aria-readonly=\"true\">{name}</div>}\n </div>\n\n {/* Character gallery — one page per show, paged with the arrows.\n Rendered immediately, no loading spinner: the sprite <img>\n fetches run in parallel with the /api/rooms/.../sprites JSON\n fetch, and greying happens in place once that resolves. */}\n <div>\n {registryNamed && <label className=\"block text-xs uppercase tracking-wide mb-2 opacity-70\">Character</label>}\n <div className=\"h-[152px] flex items-center gap-1\">\n <PageArrow direction=\"prev\" hidden={page === 0} onClick={() => setPage(page - 1)} />\n <div className=\"grid grid-cols-4 gap-2 h-full flex-1 content-start\">\n {(PAGES[page] ?? []).map(c => {\n const available = isSpriteAvailable(c);\n const isSelected = localSprite === c;\n return (\n <button\n key={c}\n onClick={() => {\n if (!available) {\n debug('character', 'Sprite unavailable', { sprite: c });\n return;\n }\n debug('character', 'Sprite selected', { sprite: c });\n setLocalSprite(c);\n }}\n disabled={!available}\n className={`relative group rounded-lg p-1 flex flex-col items-center gap-1 transition-all ${\n isSelected\n ? 'ring-2 ring-success ring-offset-2 ring-offset-bg bg-bg-card cursor-pointer'\n : available\n ? 'hover:bg-bg-card border border-transparent hover:border-border-light cursor-pointer'\n : 'opacity-40 cursor-not-allowed'\n }`}\n >\n <img\n src={spriteUrlFor(c)}\n alt={c}\n crossOrigin=\"anonymous\"\n className={`w-12 h-12 object-cover rounded ${!available ? 'grayscale' : ''}`}\n />\n <span className=\"text-[10px] opacity-70 group-hover:opacity-100 whitespace-nowrap\">{displayNameFor(c)}</span>\n {!available && (\n <span className=\"absolute inset-0 flex items-center justify-center bg-bg/60 rounded\">\n <span className=\"text-[8px] font-medium bg-danger-bg text-on-danger-bg px-1 rounded\">In Use</span>\n </span>\n )}\n </button>\n );\n })}\n </div>\n <PageArrow direction=\"next\" hidden={page >= PAGES.length - 1} onClick={() => setPage(page + 1)} />\n </div>\n </div>\n </div>\n\n {/* Footer */}\n <div className=\"px-4 pb-4 flex justify-end gap-2\">\n {tierlist && (\n <button\n type=\"button\"\n onClick={tierlist.open}\n // Warm the posters on intent so the board opens populated.\n onMouseEnter={tierlist.warm}\n onFocus={tierlist.warm}\n className=\"modal-btn-cancel mr-auto inline-flex items-center gap-1\"\n aria-label=\"Tierlist\"\n >\n <svg className=\"w-3.5 h-3.5\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={2}>\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M4 6h16M4 12h11M4 18h6\" />\n </svg>\n Tierlist\n </button>\n )}\n <button\n onClick={onClose}\n className=\"modal-btn-cancel\"\n >\n Cancel\n </button>\n <button\n onClick={updateIdentity}\n disabled={!hasChanges}\n className=\"modal-btn-update\"\n style={!hasChanges ? {\n opacity: 0.4,\n cursor: 'not-allowed',\n filter: 'grayscale(50%)'\n } : undefined}\n >\n Update\n </button>\n </div>\n </div>\n </div>\n );\n}\n\n/** A gallery arrow. Hidden arrows keep their slot so the grid never shifts between pages. */\nfunction PageArrow({ direction, hidden, onClick }: { direction: 'prev' | 'next'; hidden: boolean; onClick: () => void }) {\n return (\n <button\n type=\"button\"\n onClick={onClick}\n className=\"char-page-btn\"\n data-hidden={hidden ? '1' : undefined}\n disabled={hidden}\n aria-hidden={hidden}\n tabIndex={hidden ? -1 : undefined}\n aria-label={direction === 'prev' ? 'Previous characters' : 'Next characters'}\n >\n <svg className=\"w-4 h-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d={direction === 'prev' ? 'M15 19l-7-7 7-7' : 'M9 5l7 7-7 7'} />\n </svg>\n </button>\n );\n}\n"],"names":["PAGES","characterPages","CharacterModal","open","onClose","roomKey","tierlist","config","useConfig","name","sprite","setIdentity","useAppStore","registryNamed","s","localSprite","setLocalSprite","useState","usedSprites","setUsedSprites","page","setPage","pageIndexFor","useEffect","url","getServerOrigin","res","warn","data","err","error","errorFields","updateIdentity","nextName","displayNameFor","debug","sendIdentityUpdate","isSpriteAvailable","c","handleBackdropClick","handleEscape","e","p","hasChanges","jsx","jsxs","PageArrow","available","spriteUrlFor","direction","hidden","onClick"],"mappings":"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"}
1
+ {"version":3,"file":"CharacterModal-D5zv4le2.js","sources":["../../src/components/CharacterModal.tsx"],"sourcesContent":["import React, { useEffect, useState } from 'react';\nimport { useAppStore } from '../lib/store';\nimport { sendIdentityUpdate } from '../lib/socket';\nimport { characterPages, pageIndexFor, spriteUrlFor, displayNameFor } from '../lib/characters';\n\nconst PAGES = characterPages();\nimport { useConfig, getServerOrigin } from '../lib/config';\nimport { debug, error, errorFields, warn } from '../lib/debug';\n\ninterface Props {\n open: boolean;\n onClose: () => void;\n roomKey?: string;\n /** The global tierlist, when this page has one. The room chrome mounts this\n * modal around a host app with no tierlist, so it passes nothing and the\n * button is absent. Passed in rather than imported so the chrome bundle\n * never reaches the tierlist client. */\n tierlist?: { open: () => void; warm: () => void };\n}\n\nexport default function CharacterModal({ open, onClose, roomKey, tierlist }: Props) {\n const config = useConfig();\n const { name, sprite, setIdentity } = useAppStore();\n // An account's name is its key-registry name, shown READ-ONLY. It is not\n // renamed from here yet: hadoku_site's POST /session/name rewrites one\n // registry row and splits the key's active/standby rotation pair, so the\n // name reverts at the next swap (hadoku_site, 2026-10-08). Rename lands once\n // self-rename goes through the key lifecycle broker by userId. Without a\n // registry name (a public viewer) the name is simply the character's.\n const registryNamed = useAppStore(s => !!(s.me.clientId && s.roster[s.me.clientId]?.registryName));\n const [localSprite, setLocalSprite] = useState<string | null>(sprite);\n const [usedSprites, setUsedSprites] = useState<Set<string>>(new Set());\n const [page, setPage] = useState(() => pageIndexFor(PAGES, sprite));\n\n // Sync local state when modal opens\n useEffect(() => {\n if (open) {\n setLocalSprite(sprite);\n setPage(pageIndexFor(PAGES, sprite));\n }\n }, [open, sprite]);\n\n // Which sprites other people hold, for greying them out. The grid renders\n // at once with everything available and greys in place when this lands —\n // the portraits are static files and need not wait on it.\n useEffect(() => {\n if (!open || !roomKey) return;\n const url = `${getServerOrigin(config)}/api/rooms/${encodeURIComponent(roomKey)}/sprites`;\n fetch(url)\n .then(res => {\n if (!res.ok) {\n warn('character', 'sprites fetch non-OK', { status: res.status });\n return Promise.reject(new Error(`status ${res.status}`));\n }\n return res.json() as Promise<{ used?: string[] }>;\n })\n .then(data => setUsedSprites(new Set(data.used ?? [])))\n .catch((err: unknown) => {\n error('character', 'sprites fetch failed', errorFields(err));\n setUsedSprites(new Set());\n });\n }, [open, roomKey]);\n\n const updateIdentity = () => {\n // A registry name stays as it is; otherwise the name follows the character.\n const nextName = registryNamed || !localSprite ? name : displayNameFor(localSprite);\n debug('character', 'Updating identity', { name: nextName, sprite: localSprite });\n setIdentity(nextName, localSprite);\n sendIdentityUpdate(nextName, localSprite || undefined);\n onClose();\n };\n\n // Check if a sprite is available (not used by others, or is the current user's sprite)\n const isSpriteAvailable = (c: string) => {\n if (c === sprite) return true; // Current user's sprite is always selectable\n return !usedSprites.has(c);\n };\n\n // Handle backdrop click\n const handleBackdropClick = (e: React.MouseEvent) => {\n if (e.target === e.currentTarget) {\n onClose();\n }\n };\n\n // Handle escape key\n useEffect(() => {\n if (!open) return;\n const handleEscape = (e: KeyboardEvent) => {\n if (e.key === 'Escape') onClose();\n else if (e.key === 'ArrowLeft') setPage(p => Math.max(0, p - 1));\n else if (e.key === 'ArrowRight') setPage(p => Math.min(PAGES.length - 1, p + 1));\n };\n window.addEventListener('keydown', handleEscape);\n return () => window.removeEventListener('keydown', handleEscape);\n }, [open, onClose]);\n\n // Check if there are any changes\n const hasChanges = localSprite !== sprite;\n\n if (!open) return null;\n\n return (\n <div\n className=\"fixed inset-0 bg-black/70 flex items-center justify-center z-50\"\n onMouseDown={handleBackdropClick}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label=\"Change character\"\n style={{ willChange: 'opacity' }}\n >\n <div className=\"bg-bg border border-border rounded-lg shadow-xl w-[340px] mx-4 overflow-hidden\">\n {/* Content */}\n <div className=\"p-4 space-y-4 text-sm\">\n {/* Display Name with close button */}\n <div>\n <div className=\"flex items-center justify-between mb-1\">\n <label className=\"text-xs uppercase tracking-wide opacity-70\">{registryNamed ? 'Display Name' : 'Character'}</label>\n <button\n type=\"button\"\n onClick={onClose}\n className=\"text-text-secondary hover:text-white hover:bg-bg-alt rounded p-1 transition-colors cursor-pointer\"\n aria-label=\"Close\"\n >\n <svg className=\"w-4 h-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M6 18L18 6M6 6l12 12\" />\n </svg>\n </button>\n </div>\n {registryNamed && <div className=\"modal-input\" aria-readonly=\"true\">{name}</div>}\n </div>\n\n {/* Character gallery — one page per show, paged with the arrows.\n Rendered immediately, no loading spinner: the sprite <img>\n fetches run in parallel with the /api/rooms/.../sprites JSON\n fetch, and greying happens in place once that resolves. */}\n <div>\n {registryNamed && <label className=\"block text-xs uppercase tracking-wide mb-2 opacity-70\">Character</label>}\n <div className=\"h-[152px] flex items-center gap-1\">\n <PageArrow direction=\"prev\" hidden={page === 0} onClick={() => setPage(page - 1)} />\n <div className=\"grid grid-cols-4 gap-2 h-full flex-1 content-start\">\n {(PAGES[page] ?? []).map(c => {\n const available = isSpriteAvailable(c);\n const isSelected = localSprite === c;\n return (\n <button\n key={c}\n onClick={() => {\n if (!available) {\n debug('character', 'Sprite unavailable', { sprite: c });\n return;\n }\n debug('character', 'Sprite selected', { sprite: c });\n setLocalSprite(c);\n }}\n disabled={!available}\n className={`relative group rounded-lg p-1 flex flex-col items-center gap-1 transition-all ${\n isSelected\n ? 'ring-2 ring-success ring-offset-2 ring-offset-bg bg-bg-card cursor-pointer'\n : available\n ? 'hover:bg-bg-card border border-transparent hover:border-border-light cursor-pointer'\n : 'opacity-40 cursor-not-allowed'\n }`}\n >\n <img\n src={spriteUrlFor(c)}\n alt={c}\n crossOrigin=\"anonymous\"\n className={`w-12 h-12 object-cover rounded ${!available ? 'grayscale' : ''}`}\n />\n <span className=\"text-[10px] opacity-70 group-hover:opacity-100 whitespace-nowrap\">{displayNameFor(c)}</span>\n {!available && (\n <span className=\"absolute inset-0 flex items-center justify-center bg-bg/60 rounded\">\n <span className=\"text-[8px] font-medium bg-danger-bg text-on-danger-bg px-1 rounded\">In Use</span>\n </span>\n )}\n </button>\n );\n })}\n </div>\n <PageArrow direction=\"next\" hidden={page >= PAGES.length - 1} onClick={() => setPage(page + 1)} />\n </div>\n </div>\n </div>\n\n {/* Footer */}\n <div className=\"px-4 pb-4 flex justify-end gap-2\">\n {tierlist && (\n <button\n type=\"button\"\n onClick={tierlist.open}\n // Warm the posters on intent so the board opens populated.\n onMouseEnter={tierlist.warm}\n onFocus={tierlist.warm}\n className=\"modal-btn-cancel mr-auto inline-flex items-center gap-1\"\n aria-label=\"Tierlist\"\n >\n <svg className=\"w-3.5 h-3.5\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={2}>\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M4 6h16M4 12h11M4 18h6\" />\n </svg>\n Tierlist\n </button>\n )}\n <button\n onClick={onClose}\n className=\"modal-btn-cancel\"\n >\n Cancel\n </button>\n <button\n onClick={updateIdentity}\n disabled={!hasChanges}\n className=\"modal-btn-update\"\n style={!hasChanges ? {\n opacity: 0.4,\n cursor: 'not-allowed',\n filter: 'grayscale(50%)'\n } : undefined}\n >\n Update\n </button>\n </div>\n </div>\n </div>\n );\n}\n\n/** A gallery arrow. Hidden arrows keep their slot so the grid never shifts between pages. */\nfunction PageArrow({ direction, hidden, onClick }: { direction: 'prev' | 'next'; hidden: boolean; onClick: () => void }) {\n return (\n <button\n type=\"button\"\n onClick={onClick}\n className=\"char-page-btn\"\n data-hidden={hidden ? '1' : undefined}\n disabled={hidden}\n aria-hidden={hidden}\n tabIndex={hidden ? -1 : undefined}\n aria-label={direction === 'prev' ? 'Previous characters' : 'Next characters'}\n >\n <svg className=\"w-4 h-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d={direction === 'prev' ? 'M15 19l-7-7 7-7' : 'M9 5l7 7-7 7'} />\n </svg>\n </button>\n );\n}\n"],"names":["PAGES","characterPages","CharacterModal","open","onClose","roomKey","tierlist","config","useConfig","name","sprite","setIdentity","useAppStore","registryNamed","s","localSprite","setLocalSprite","useState","usedSprites","setUsedSprites","page","setPage","pageIndexFor","useEffect","url","getServerOrigin","res","warn","data","err","error","errorFields","updateIdentity","nextName","displayNameFor","debug","sendIdentityUpdate","isSpriteAvailable","c","handleBackdropClick","handleEscape","e","p","hasChanges","jsx","jsxs","PageArrow","available","spriteUrlFor","direction","hidden","onClick"],"mappings":"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"}
@@ -1,9 +1,9 @@
1
1
  import { jsx as A, jsxs as l } from "react/jsx-runtime";
2
2
  import { useState as v, useEffect as w, useMemo as D, useRef as Y } from "react";
3
- import { d as g, w as V, j as H, l as Oe, s as ce, h as O, aA as Ze, aB as Ge, U as De, aC as y, V as Ce, aD as xe, F as se, aE as ye, aF as Ne, aG as Me, aH as Le, aI as Ke, g as Re, aJ as Je } from "./index-BcsCCk6O.js";
3
+ import { d as g, w as V, j as H, l as Oe, s as ce, h as O, aA as Ze, aB as Ge, U as De, aC as y, V as Ce, aD as xe, F as se, aE as ye, aF as Ne, aG as Me, aH as Le, aI as Ke, g as Re, aJ as Je } from "./index-BLI7w-vZ.js";
4
4
  import { b as He } from "./cover-AUIrlUMF.js";
5
5
  import { createPortal as We } from "react-dom";
6
- import { S as Xe } from "./Room-Bana0IVO.js";
6
+ import { S as Xe } from "./Room-5-BsgWgy.js";
7
7
  import { Icon as Ye } from "@wolffm/themes";
8
8
  const Ve = "data:image/png;base64,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", Fe = 700, je = -1, _e = -2, $e = { endMatch: !1, rejectedStale: !1 };
9
9
  function eA(e, n, r) {
@@ -795,7 +795,7 @@ function Qe() {
795
795
  }
796
796
  function gA() {
797
797
  const e = `${Re()}/game/`, n = e.includes("?") ? "&" : "?";
798
- return `${e}${n}net=1&v=${encodeURIComponent("0.43.17")}`;
798
+ return `${e}${n}net=1&v=${encodeURIComponent("0.43.18")}`;
799
799
  }
800
800
  function NA() {
801
801
  const e = Y(null), n = Y(null), r = O((t) => t.snapshot.leaderId), a = O((t) => t.me.clientId), s = !!r && r === a, f = O((t) => t.roster), h = D(() => Object.values(f), [f]), Q = O((t) => t.gameSessionWins), c = O((t) => t.gameBotCount), C = D(() => {
@@ -1512,4 +1512,4 @@ function NA() {
1512
1512
  export {
1513
1513
  NA as default
1514
1514
  };
1515
- //# sourceMappingURL=GameModule-CplsrOUf.js.map
1515
+ //# sourceMappingURL=GameModule-BzHJYaGz.js.map