Lands Gosalyn's round 3 candidate,a2c72ac95(commit candidate, basec771a36f4), approved by Webby (29979, rev 2731). 21 paths: the new packages/library (src, tests, fixtures, package.json, README), scripts/stack-copy.sh (packages/library/src in COPY_PATHS), packages/cli/tests/stack-copy.test.mjs, scripts/mosaic and agents/gosalyn/work/queue-212/BUILD.md. The 21-path patch-id, ea84d3e35470, equals the candidate's. Evidence: Gosalyn's slot run on round 3: 20 files 451 pass, 0 fail; test-queue 28/0 (gosalyn-0086). Landing gate (Sage, 08:51-09:03Z, base4ced5e63d) on a tree that also held row 197's 32 paths: GATE_PASSED, node tests 981 pass 0 fail (library 39, cli 681, control-board 38, harness 16, webui 51, plus the scripts/test-*.sh node suites), selftests 429 passed 0 failed. Row 212 lands alone (mos-1162); row 197 waits for an r6 after its lane proof stopped. No 212 path changed betweenc771a36f4and this commit's parent. `queue review verify-commit 212` -> ok row 212 round 3:a2c72ac95matches the commit candidate (21 paths).
60 lines
3.7 KiB
JavaScript
60 lines
3.7 KiB
JavaScript
// renderItems(body): how a stored cited reply looks (design 7.4), shared by the Console chat (C2-10b)
|
|
// and the Discord relay (C2-18). Pure: no git, no clock. The caller passes what it knows about cites.
|
|
// The guard (C2-06) checks limits before a reply is stored. This only refuses to show a body that
|
|
// doesn't have the reply shape, and then it shows a fixed line, never the body's text.
|
|
export const KINDS = Object.freeze({ fact: 'Fact', finding: 'Finding', correction: 'Correction', question: 'Question', idea: 'Idea' });
|
|
export const NOTICES = Object.freeze({ 'no-cited-answer': 'No cited answer', 'no-answer': 'No answer', 'turn-failed': 'The turn failed' });
|
|
export const UNREADABLE = 'This reply could not be shown.';
|
|
const SHA = /^[0-9a-f]{40}$/;
|
|
|
|
const plain = value => value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
const line = n => Number.isSafeInteger(n) && n > 0;
|
|
function cite(value, { scopeOf, changed }) {
|
|
if (!plain(value) || typeof value.quote !== 'string') return null;
|
|
if (Object.hasOwn(value, 'url')) {
|
|
if (typeof value.url !== 'string' || Object.hasOwn(value, 'path')) return null;
|
|
return { badge: 'web', url: value.url, ref: value.url, quote: value.quote, changed: false };
|
|
}
|
|
const { path, commit, lines } = value;
|
|
if (typeof path !== 'string' || !SHA.test(commit ?? '') || !Array.isArray(lines) || lines.length !== 2
|
|
|| !line(lines[0]) || !line(lines[1]) || lines[1] < lines[0]) return null;
|
|
const badge = scopeOf?.(path);
|
|
const ref = `${path}:${lines[0] === lines[1] ? lines[0] : `${lines[0]}-${lines[1]}`} @${commit.slice(0, 7)}`;
|
|
return { badge: badge === 'canon' || badge === 'playground' ? badge : null, path, commit, lines: [...lines], ref,
|
|
quote: value.quote, changed: changed?.({ path, commit }) === true };
|
|
}
|
|
const quoted = text => JSON.stringify(text);
|
|
|
|
// body: the stored message body (a JSON string or the parsed object).
|
|
// options.scopeOf(path) -> 'canon' | 'playground' | undefined, for the scope badge.
|
|
// options.changed({path, commit}) -> true when the file's last commit is newer than the cited one.
|
|
// Returns { kind: 'items', items, text } | { kind: 'notice', notice, text } | { kind: 'unreadable', text }.
|
|
export function renderItems(body, options = {}) {
|
|
const unreadable = { kind: 'unreadable', text: UNREADABLE };
|
|
let value = body;
|
|
if (typeof body === 'string') { try { value = JSON.parse(body); } catch { return unreadable; } }
|
|
if (!plain(value) || !Array.isArray(value.items)) return unreadable;
|
|
const keys = Object.keys(value);
|
|
if (value.items.length === 0) {
|
|
if (keys.length !== 2 || !Object.hasOwn(NOTICES, value.notice)) return unreadable;
|
|
return { kind: 'notice', notice: value.notice, text: NOTICES[value.notice] };
|
|
}
|
|
if (keys.length !== 1) return unreadable;
|
|
const items = [];
|
|
for (const item of value.items) {
|
|
if (!plain(item) || !Object.hasOwn(KINDS, item.kind) || typeof item.text !== 'string' || !Array.isArray(item.cites)) return unreadable;
|
|
const correction = item.kind === 'correction';
|
|
if (correction && (typeof item.from !== 'string' || typeof item.to !== 'string')) return unreadable;
|
|
const cites = item.cites.map(c => cite(c, options));
|
|
if (cites.some(c => c === null)) return unreadable;
|
|
items.push({ kind: item.kind, label: KINDS[item.kind], text: item.text,
|
|
...(correction ? { from: item.from, to: item.to } : {}), cites });
|
|
}
|
|
const text = items.map((item, i) => [
|
|
`${i + 1}. ${item.label}: ${item.text}`,
|
|
...(item.kind === 'correction' ? [` From: ${quoted(item.from)}`, ` To: ${quoted(item.to)}`] : []),
|
|
...item.cites.map(c => ` ${c.badge ? `[${c.badge}] ` : ''}${c.ref} ${quoted(c.quote)}${c.changed ? ' (changed since)' : ''}`),
|
|
].join('\n')).join('\n');
|
|
return { kind: 'items', items, text };
|
|
}
|