Add administrative room close and reopen controls
Preserve readable private tables, discussion and pending games while closure blocks new play, joins, messages, AI and seat mutations. Add live-authorized atomic closure audits, room-version/revision fences, dedicated controls and exact saved retries. Reopen with the saved pause/join settings and current credentials; restoring a removed but closed room keeps its AI stopped. Verified types, lint, 5356 offline tests, 52 HTTP tests and build. Seventeen dedicated authenticated HTTP groups and independent privacy/persistence review passed. Desktop/mobile browser QA confirmed one close after a lost response and refresh, persistent readable notices, retained private draft, mobile reopening and saved-seat refresh. All 2,001 baseline rooms, 4,243 seats and related records preserved. Positive production admin acceptance awaits authorized QA access; full admin and rules gates remain open.
This commit is contained in:
1 parent
238de3860d
commit
98b1b3261b
49 files changed
+4255
-86
No files matched your search
@@ -18,6 +18,7 @@ import {
|
||||
/** Remount on room/seat changes. Messages belong to seats, never browser identities. */
|
||||
export function TableTalk({ game }: { game: GameView }) {
|
||||
const field = useId();
|
||||
const closed = !!game.roomControl?.closed;
|
||||
const [open, setOpen] = useState(false);
|
||||
const [recipient, setRecipient] = useState('');
|
||||
const [text, setText] = useState('');
|
||||
@@ -151,7 +152,7 @@ export function TableTalk({ game }: { game: GameView }) {
|
||||
}
|
||||
}
|
||||
async function submit() {
|
||||
if (sending.current || !ready) return;
|
||||
if (sending.current || !ready || (closed && !record)) return;
|
||||
const draft = record ?? {
|
||||
id: randomId(),
|
||||
recipientId: recipient || null,
|
||||
@@ -314,6 +315,7 @@ export function TableTalk({ game }: { game: GameView }) {
|
||||
{shownPage.messages.length === 0 && (
|
||||
<p className="muted">No messages loaded in this conversation.</p>
|
||||
)}
|
||||
{closed && <p className="notice">This room is closed. Discussion history remains available; an exact saved message can still be confirmed.</p>}
|
||||
<form
|
||||
onSubmit={(event) => {
|
||||
event.preventDefault();
|
||||
@@ -330,7 +332,7 @@ export function TableTalk({ game }: { game: GameView }) {
|
||||
rows={3}
|
||||
maxLength={TALK_LIMIT}
|
||||
value={text}
|
||||
disabled={busy || !!record}
|
||||
disabled={busy || !!record || closed}
|
||||
onChange={(event) => setText(event.target.value)}
|
||||
aria-describedby={`${field}-count`}
|
||||
/>
|
||||
@@ -341,6 +343,7 @@ export function TableTalk({ game }: { game: GameView }) {
|
||||
type="submit"
|
||||
disabled={
|
||||
!ready ||
|
||||
(closed && !record) ||
|
||||
busy ||
|
||||
!text.trim() ||
|
||||
(!record && !!recipient && !target)
|
||||
|
||||
Reference in new issue
Block a user