Players see their reports answered in the lobby ledger
Reports gain ids; replies live in the same JSONL, folding onto their report when read (legacy reports answer to their timestamp). The lobby asks with the same seat-token proof as the games ledger — wake-free — and lists each report with its reply and status, or 'the wizards are studying the moment'. deploy/feedback-reply.sh answers one from here. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015RCWSTnb1KYTPyL4GmhGnF
This commit is contained in:
co-authored by
Claude Fable 5
parent
eabc10f18c
commit
5abd0ba60e
@@ -148,13 +148,61 @@ export function readAllRooms(): Map<string, RoomLine[]> {
|
||||
return rooms;
|
||||
}
|
||||
|
||||
/** Player surprise reports, one JSONL line each, beside the rooms
|
||||
* directory — the room id and seq pin the exact moment to replay. */
|
||||
/** Player surprise reports and the wizards' replies share one JSONL file
|
||||
* beside the rooms directory — a report line carries roomId/seq pinning
|
||||
* its moment; a reply line carries reportId/status/text and folds onto
|
||||
* its report when read. Legacy reports without an id answer to their
|
||||
* timestamp. */
|
||||
export function appendFeedback(entry: Record<string, unknown>): void {
|
||||
ensureDataDir();
|
||||
appendFileSync(join(DATA_DIR, "..", "feedback.jsonl"), JSON.stringify(entry) + "\n", "utf8");
|
||||
}
|
||||
|
||||
export interface FeedbackReport {
|
||||
id: string;
|
||||
at: string;
|
||||
roomId: string;
|
||||
player: string;
|
||||
seq: number;
|
||||
round: number | null;
|
||||
happened: string;
|
||||
expected: string;
|
||||
reply?: { at: string; text: string; status: string };
|
||||
}
|
||||
|
||||
export function readFeedback(): FeedbackReport[] {
|
||||
const file = join(DATA_DIR, "..", "feedback.jsonl");
|
||||
if (!existsSync(file)) return [];
|
||||
const reports = new Map<string, FeedbackReport>();
|
||||
for (const raw of readFileSync(file, "utf8").split("\n")) {
|
||||
if (!raw.trim()) continue;
|
||||
let line: Record<string, unknown>;
|
||||
try { line = JSON.parse(raw); } catch { continue; }
|
||||
if (typeof line.reportId === "string") {
|
||||
const report = reports.get(line.reportId);
|
||||
if (report) {
|
||||
report.reply = {
|
||||
at: String(line.at ?? ""), text: String(line.text ?? ""), status: String(line.status ?? "resolved"),
|
||||
};
|
||||
}
|
||||
continue;
|
||||
}
|
||||
const id = String(line.id ?? line.at ?? "");
|
||||
if (!id) continue;
|
||||
reports.set(id, {
|
||||
id,
|
||||
at: String(line.at ?? ""),
|
||||
roomId: String(line.roomId ?? ""),
|
||||
player: String(line.player ?? ""),
|
||||
seq: Number(line.seq ?? 0),
|
||||
round: line.round == null ? null : Number(line.round),
|
||||
happened: String(line.happened ?? ""),
|
||||
expected: String(line.expected ?? ""),
|
||||
});
|
||||
}
|
||||
return [...reports.values()];
|
||||
}
|
||||
|
||||
/** Retire an abandoned room's ledger to the graveyard — never deleted,
|
||||
* only moved out of the living rooms directory. */
|
||||
export function archiveRoomFile(roomId: string): void {
|
||||
|
||||
Reference in New Issue
Block a user