From 422c21fbc0ddb1a54e556a05af5bb94933a57b38 Mon Sep 17 00:00:00 2001 From: witbrock Date: Sun, 13 Sep 2026 12:48:55 -0400 Subject: [PATCH] Use readable statement labels in the assertion inspector --- .../js/components/referenceInspector.js | 15 ++++++ .../static/js/test/referenceInspector.test.js | 46 +++++++++++++++++++ .../web/von_interface/static/styles.css | 6 +++ 3 files changed, 67 insertions(+) diff --git a/src/frontend/web/von_interface/static/js/components/referenceInspector.js b/src/frontend/web/von_interface/static/js/components/referenceInspector.js index 83e368211..7a5cfcf25 100644 --- a/src/frontend/web/von_interface/static/js/components/referenceInspector.js +++ b/src/frontend/web/von_interface/static/js/components/referenceInspector.js @@ -21,6 +21,18 @@ function normaliseList(value) { : []; } +// A display label is a bounded view of the statement, never an assertion alias. +// The full reference ID remains alongside it, in lookup requests and in Copy ID. +function assertionDisplayLabel(assertion) { + const statement = clean(assertion?.human_statement)?.replace(/\s+/gu, ' '); + if (!statement) return 'Assertion'; + const characters = Array.from(statement); + if (characters.length <= 96) return statement; + const prefix = characters.slice(0, 95).join(''); + const wordBoundary = prefix.lastIndexOf(' '); + return `${wordBoundary >= 64 ? prefix.slice(0, wordBoundary) : prefix}…`; +} + export function normaliseReferenceManifest(value) { if (!value || typeof value !== 'object') return null; if (value.schema_version !== REFERENCE_MANIFEST_SCHEMA_VERSION) return null; @@ -212,6 +224,8 @@ function renderAssertion(payload) { const body = clearBody(); if (!body) return; const assertion = payload?.assertion || {}; + const title = byId('conversationReferenceInspectorTitle'); + if (title) title.textContent = assertionDisplayLabel(assertion); const summary = appendSection(body, 'Summary'); appendText( summary, @@ -393,6 +407,7 @@ function setHeader(reference) { async function loadCurrentReference() { const reference = currentReference; if (!reference) return; + setHeader(reference); setBusy(true); setStatus(`Loading ${reference.label || 'reference'}`); const body = clearBody(); diff --git a/src/frontend/web/von_interface/static/js/test/referenceInspector.test.js b/src/frontend/web/von_interface/static/js/test/referenceInspector.test.js index 9503bf258..272572eda 100644 --- a/src/frontend/web/von_interface/static/js/test/referenceInspector.test.js +++ b/src/frontend/web/von_interface/static/js/test/referenceInspector.test.js @@ -6,6 +6,7 @@ import { openReferenceInspector, } from '../components/referenceInspector.js'; import { getJsonDetailed } from '../apiService.js'; +import { copyTextWithClipboardFallback } from '../utils/copyJsonButtonState.js'; jest.mock('../apiService.js', () => ({ getJsonDetailed: jest.fn(), @@ -142,9 +143,54 @@ describe('conversation reference inspector', () => { expect(panel.textContent).toContain('Robust multimodal learning'); expect(panel.textContent).toContain('NeurIPS'); expect(panel.textContent).toContain('Organisation — Strong AI Lab'); + expect(document.getElementById('conversationReferenceInspectorTitle').textContent) + .toBe('Student A has paper matching profile'); + expect(document.getElementById('conversationReferenceInspectorExplanation').textContent) + .toBe('ska_profile_1234'); expect(document.getElementById('conversationSituationPanel').classList.contains('hidden')).toBe(true); }); + test('repeated labels remain distinguishable and copy and lookup use the full ID', async () => { + for (const id of ['ska_a3dfb82ac552fccaf6f95aa231eb133a', 'ska_a3dfb82ac552fccaf6f95aa231eb133b']) { + getJsonDetailed.mockResolvedValue({ data: { assertion: { human_statement: 'Ada studies learning.' } } }); + await openReferenceInspector({ reference_id: id, reference_type: 'scoped_assertion', label: 'Assertion' }); + expect(document.getElementById('conversationReferenceInspectorTitle').textContent).toBe('Ada studies learning.'); + expect(document.getElementById('conversationReferenceInspectorExplanation').textContent).toBe(id); + expect(getJsonDetailed).toHaveBeenLastCalledWith(`/api/concepts/assertions/${id}`, { cache: 'no-store' }); + document.getElementById('conversationReferenceInspectorCopyBtn').click(); + expect(copyTextWithClipboardFallback).toHaveBeenLastCalledWith(id); + } + }); + + test.each([undefined, '', ' \n '])('falls back to Assertion with its ID when the statement is %p', async (statement) => { + getJsonDetailed.mockResolvedValue({ data: { assertion: { human_statement: statement } } }); + await openReferenceInspector({ reference_id: 'ska_fallback', reference_type: 'scoped_assertion', label: 'Assertion' }); + expect(document.getElementById('conversationReferenceInspectorTitle').textContent).toBe('Assertion'); + expect(document.getElementById('conversationReferenceInspectorExplanation').textContent).toBe('ska_fallback'); + }); + + test('bounds a long label, normalises whitespace and preserves the complete statement as text', async () => { + const statement = '\n ' + '🧠 Research interests in learning. '.repeat(8); + getJsonDetailed.mockResolvedValue({ data: { assertion: { human_statement: statement } } }); + await openReferenceInspector({ reference_id: 'ska_long', reference_type: 'scoped_assertion', label: 'Assertion' }); + const title = document.getElementById('conversationReferenceInspectorTitle'); + expect(Array.from(title.textContent).length).toBeLessThanOrEqual(96); + expect(title.textContent).toMatch(/…$/); + expect(title.textContent).not.toMatch(/\s{2}/); + expect(document.querySelector('.chat-reference-statement').textContent).toBe(statement.trim()); + expect(document.querySelector('#conversationReferenceInspector img')).toBeNull(); + }); + + test('a failed refresh removes the previously loaded label', async () => { + getJsonDetailed.mockResolvedValueOnce({ data: { assertion: { human_statement: 'Old statement' } } }); + await openReferenceInspector({ reference_id: 'ska_refresh', reference_type: 'scoped_assertion', label: 'Assertion' }); + getJsonDetailed.mockRejectedValueOnce({ status: 404 }); + document.getElementById('conversationReferenceInspectorRefreshBtn').click(); + await Promise.resolve(); + expect(document.getElementById('conversationReferenceInspectorTitle').textContent).toBe('Assertion'); + expect(document.getElementById('conversationReferenceInspectorStatus').textContent).toContain('not available'); + }); + test('renders persisted turn evidence without fetching or calling it a durable receipt', async () => { await openReferenceInspector({ reference_id: 'ev_exact_turn_1234', diff --git a/src/frontend/web/von_interface/static/styles.css b/src/frontend/web/von_interface/static/styles.css index 6f79f8f27..092b75417 100644 --- a/src/frontend/web/von_interface/static/styles.css +++ b/src/frontend/web/von_interface/static/styles.css @@ -9034,6 +9034,11 @@ body[data-active-tab="settingsTab"] .tab-content-area { margin: 0; font-size: 20px; line-height: 1.25; + overflow-wrap: anywhere; +} + +.chat-reference-inspector-header > div:first-child { + min-width: 0; } .chat-reference-inspector-actions { @@ -9089,6 +9094,7 @@ body[data-active-tab="settingsTab"] .tab-content-area { .chat-reference-statement { color: #111827; font-size: 16px; + overflow-wrap: anywhere; } .chat-reference-notice {