From 4405307c99e721de8870f1ce5a9b43cdbbff7256 Mon Sep 17 00:00:00 2001 From: Ein Anderssono Date: Fri, 23 Oct 2026 22:35:02 +0200 Subject: [PATCH] Add storyteller-only admin reading view --- index.html | 27 +++++++++++++++++++++++-- scripts/check-frontend.sh | 2 ++ tests/admin-conversation-trace.test.cjs | 8 ++++++++ 3 files changed, 35 insertions(+), 2 deletions(-) diff --git a/index.html b/index.html index a5f7d9d..ade5307 100644 --- a/index.html +++ b/index.html @@ -28,6 +28,11 @@ #admin-content { display: grid; gap: 1rem; } #admin-content section, #admin-content article { border: 1px solid currentColor; border-radius: .35rem; padding: .75rem; } #admin-content article > button { display: block; max-width: 100%; margin-block: .5rem; text-align: start; white-space: normal; } + #admin-content article > .admin-story-reply { display: none; } + #admin-content .admin-simple-conversation > h2, + #admin-content .admin-simple-conversation > p:not(.admin-story-empty), + #admin-content .admin-simple-conversation article > :not(.admin-story-reply) { display: none; } + #admin-content .admin-simple-conversation article > .admin-story-reply { display: block; margin-block: .85rem; white-space: pre-wrap; } #admin-content pre { white-space: pre-wrap; overflow-wrap: anywhere; max-height: 32rem; overflow: auto; } #admin-toolbar { display: flex; flex-wrap: wrap; gap: .5rem; margin-block: 1rem; } #player-search { display: flex; flex-wrap: wrap; align-items: end; gap: .5rem; } @@ -168,6 +173,7 @@ let adminOffset = 0; const adminPageSize = 50; let adminTraceOffset = 0; + let adminSimpleConversation = false; let presenceTimer = null; let playerMessagesPrimed = false; const seenPlayerMessageIds = new Set(); @@ -652,6 +658,7 @@ const profile = await request(`/api/admin/players/${encodeURIComponent(accountId)}`); adminJson(`${profile.username} · profil och biografi`, profile); adminTraceOffset = 0; + adminSimpleConversation = false; const trace = await request(`/api/admin/players/${encodeURIComponent(accountId)}/trace?limit=200&offset=0`); renderAdminConversations(trace, accountId); } @@ -670,7 +677,21 @@ const conversations = [...groups.values()].sort((a, b) => String(a.at).localeCompare(String(b.at))); adminElement('h2', 'Samtal och beslut', root); adminElement('p', `Visar ${conversations.length} korrelerade samtalsturer från den här sidan. Välj en berättarreplik eller ett AI-svar för att öppna hela registrerade beslutskedjan. Registreringstid, världstid och commitordning visas var för sig.`, root); - if (conversations.length === 0) { adminElement('p', 'Det finns inga sparade dialogturer för kontot ännu.', root); return; } + const readingToggle = adminElement('button', adminSimpleConversation ? 'Visa felsökningsspår' : 'Lättläst konversation', root); + readingToggle.type = 'button'; + readingToggle.setAttribute('aria-pressed', String(adminSimpleConversation)); + readingToggle.addEventListener('click', () => { + adminSimpleConversation = !adminSimpleConversation; + root.classList.toggle('admin-simple-conversation', adminSimpleConversation); + readingToggle.textContent = adminSimpleConversation ? 'Visa felsökningsspår' : 'Lättläst konversation'; + readingToggle.setAttribute('aria-pressed', String(adminSimpleConversation)); + }); + root.classList.toggle('admin-simple-conversation', adminSimpleConversation); + if (conversations.length === 0) { + const empty = adminElement('p', 'Det finns inga sparade berättarrepliker för kontot ännu.', root); + empty.className = 'admin-story-empty'; + return; + } for (const [index, group] of conversations.entries()) { group.events.sort((a, b) => (a.commit_sequence ?? 0) - (b.commit_sequence ?? 0)); group.messages.sort((a, b) => (a.sequence ?? 0) - (b.sequence ?? 0)); @@ -683,6 +704,8 @@ if (payload.player_message) { adminElement('p', `Spelaren: ${payload.player_message}`, card); showedPlayerInput = true; } const reply = payload.reply || payload.npc_reply; if (reply) { + const story = adminElement('p', `Berättelsen: ${reply}`, card); + story.className = 'admin-story-reply'; const button = adminElement('button', `Berättaren${payload.npc_reply ? ' / NPC' : ''}: ${reply}`, card); button.type = 'button'; button.setAttribute('aria-expanded', 'false'); @@ -719,7 +742,7 @@ } const pageHasMore = hasMoreOverride ?? trace.has_more ?? (Math.max(trace.events?.length || 0, trace.ai_interactions?.length || 0, trace.player_messages?.length || 0) >= (trace.limit || 200)); if (pageHasMore) { - const older = adminElement('button', 'Visa äldre samtalsturer', root); + const older = adminElement('button', adminSimpleConversation ? 'Visa äldre berättarrepliker' : 'Visa äldre samtalsturer', root); older.type = 'button'; older.addEventListener('click', async () => { older.disabled = true; diff --git a/scripts/check-frontend.sh b/scripts/check-frontend.sh index 42e6a00..6455f06 100755 --- a/scripts/check-frontend.sh +++ b/scripts/check-frontend.sh @@ -104,6 +104,8 @@ grep -q 'Välj konto att följa' index.html grep -q 'function renderAdminConversations' index.html grep -q 'function renderDecisionProvenance' index.html grep -q 'Visa äldre samtalsturer' index.html +grep -q 'Lättläst konversation' index.html +grep -q 'admin-simple-conversation' index.html grep -q 'aria-expanded' index.html grep -q "request('/api/admin/prompts'" index.html grep -q "request('/api/admin/rules'" index.html diff --git a/tests/admin-conversation-trace.test.cjs b/tests/admin-conversation-trace.test.cjs index b617516..6d67004 100644 --- a/tests/admin-conversation-trace.test.cjs +++ b/tests/admin-conversation-trace.test.cjs @@ -31,3 +31,11 @@ test('account trace can be paged until all stored turns are loaded', () => { assert.match(page, /Visa äldre samtalsturer/); assert.match(page, /\?limit=\$\{trace\.limit \|\| 200\}&offset=\$\{adminTraceOffset\}/); }); + +test('admin can switch to a storyteller-only reading view and return to diagnostics', () => { + assert.match(page, /Lättläst konversation/); + assert.match(page, /Visa felsökningsspår/); + assert.match(page, /root\.classList\.toggle\('admin-simple-conversation', adminSimpleConversation\)/); + assert.match(page, /story\.className = 'admin-story-reply'/); + assert.match(page, /article > :not\(\.admin-story-reply\)/); +});