Files
stack/agents/dewey/work/wui/mockups/slice1/q1.js
T
jason.woltjeandClaude Opus 5.5 1f4882d2b4 docs(wui): Dewey's design work: brand board, five mockups, slice 1 S5 views and checks
Brand board revisions, the five dashboard mockups with their verify
script and evidence, the slice 1 S5 design note (SLICE1-VIEWS.md, rev 3
plus the S2c section 10 update), the Console mockup of inbox, tasks,
agents and trail (mockups/slice1/) and checks/slice1-verify.mjs (16
checks; check 16 pinned to bus at d27042fa). Also the CHAT-03 progress
log and the 2026-09-26 dirty-tree manifest. Design and evidence only;
no package code. Row 40 (#1522), committed on its own at Sage's
direction, 2026-10-09.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-09 17:54:50 -05:00

85 lines
6.6 KiB
JavaScript

// Stand-in for the Q1 read module, packages/bus/src/views.mjs (lead decision 56, Q1; Rocko's,
// committed in d27042fa, S2c). `views(rows)` ports the four reader verbs of the broker to fixture rows,
// so the page receives the same arrays: inbox, tasks, agents and trail(subject). Verify check 16
// runs the reader from that commit over the same rows and compares (it prints the shas it used).
// The real module returns Promises; the Console server awaits them and sends the arrays, so the
// page itself stays synchronous. When Rocko pins an example fixture, it replaces this.
//
// `missing(rows)` holds what the page needs and no Q1 verb returns. Each entry names its source, and
// the page labels whatever it shows from here. SLICE1-VIEWS.md section 9 lists them for Rocko.
(() => {
'use strict';
const CLOSED = ['resolved', 'withdrawn', 'expired'];
const parse = (table, row) => { const r = { ...row, table }; for (const k of ['body', 'options', 'fields']) if (k in r && table !== 'messages') try { r[k] = JSON.parse(r[k]); } catch { /* kept as text, as the broker does */ } return r; };
const closedOf = (S, id) => S.decision_events.filter(x => x.decision === id && CLOSED.includes(x.op)).at(-1) || null;
const raiseOf = (S, id) => S.events.find(e => e.kind === 'action.allowed' && JSON.parse(e.body).operation === 'decision.raise' && JSON.parse(e.body).decision === id);
// #decisionView: the decision with options decoded, blocking as a boolean and the decision.raise context.
function decisionView(S, d) {
const raise = raiseOf(S, d.id), c = raise && JSON.parse(raise.body);
return { ...d, options: JSON.parse(d.options), blocking: !!d.blocking, authorization: c ? { action: d.action, target: c.target, approvalChoice: c.approvalChoice } : null };
}
function views(S) {
// inbox: open decisions routed to the human, gated first, then seq.
const inbox = () => S.decisions.filter(d => d.route_to === 'human' && !closedOf(S, d.id))
.sort((a, b) => ((a.class === 'gated' ? 0 : 1) - (b.class === 'gated' ? 0 : 1)) || a.seq - b.seq).map(d => decisionView(S, d));
// tasks: task_current (schema v3b), ordered by task_ref, fields decoded. Tombstones included.
function tasks() {
const refs = [...new Set(S.task_snapshots.map(s => s.task_ref))].sort();
return refs.map(ref => {
const ss = S.task_snapshots.filter(s => s.task_ref === ref), ls = ss.filter(s => s.source === 'self').sort((a, b) => a.seq - b.seq).at(-1);
const cur = ss.filter(c => c.source === 'self' || c.read_at > (ls?.at ?? '')).sort((a, b) => a.seq - b.seq).at(-1);
return { ...cur, fields: JSON.parse(cur.fields) };
});
}
// agents: the latest role_claims row per role when that row is a claim, ordered by role.
const agents = () => [...new Set(S.role_claims.map(c => c.role))].sort()
.map(role => S.role_claims.filter(c => c.role === role).sort((a, b) => a.seq - b.seq).at(-1)).filter(c => c.op === 'claim').map(c => ({ ...c }));
// trail: every row tied to the subject (task ref, decision id or message id), tagged with its table.
function trail(subject) {
const rows = new Map(), add = (table, list) => { for (const row of list) rows.set(`${table}:${row.seq}`, parse(table, row)); };
add('events', S.events.filter(e => e.subject === subject));
add('messages', S.messages.filter(m => m.id === subject));
const ds = S.decisions.filter(d => d.id === subject || d.task_ref === subject);
add('decisions', ds);
for (const d of ds) {
add('decision_events', S.decision_events.filter(x => x.decision === d.id));
add('events', S.events.filter(e => e.subject === d.id || JSON.parse(e.body).decision === d.id));
add('messages', S.messages.filter(m => m.decision === d.id));
}
for (const e of [...rows.values()]) if (e.table === 'events' && e.kind === 'task.created') {
const input = S.events.find(x => x.kind === 'human.input' && x.id === e.body.request);
if (input) { add('events', [input]); const b = JSON.parse(input.body); if (b.message) add('messages', S.messages.filter(m => m.id === b.message)); }
}
for (const m of [...rows.values()]) if (m.table === 'messages') add('deliveries', S.deliveries.filter(x => x.message === m.id));
add('task_snapshots', S.task_snapshots.filter(s => s.task_ref === subject));
const runs = new Set([...rows.values()].map(r => r.actor_run ?? r.raised_by_run ?? r.run).filter(Boolean));
for (const run of runs) { add('events', S.events.filter(e => e.actor_run === run && ['session.launched', 'session.ended'].includes(e.kind))); add('role_claims', S.role_claims.filter(c => c.holder_run === run)); }
return [...rows.values()].sort((a, b) => a.at.localeCompare(b.at) || a.table.localeCompare(b.table) || a.seq - b.seq);
}
return { inbox, tasks, agents, trail };
}
function missing(S) {
const L = S.lookups, body = e => JSON.parse(e.body);
const sessions = S.events.filter(e => e.kind === 'session.launched').map(e => ({ at: e.at, role: e.actor_role, run: e.actor_run, ...body(e) }));
const ended = S.events.filter(e => e.kind === 'session.ended').map(e => ({ at: e.at, role: e.actor_role, run: e.actor_run, ...body(e) }));
const ls = S.events.filter(e => e.kind === 'launch.revoked' || e.kind === 'launch.restored').at(-1);
return {
// The business file and the kanban view: buckets, bots, tracker address, instances, launch rule.
business: { source: 'business file and kanban view', ...L },
// Decisions the reader inbox leaves out: routed to roles, and closed.
decisions: { source: 'no Q1 verb lists these', roles: S.decisions.filter(d => d.route_to !== 'human' && !closedOf(S, d.id)).map(d => decisionView(S, d)), closed: S.decisions.filter(d => closedOf(S, d.id)).map(d => ({ ...decisionView(S, d), closedBy: closedOf(S, d.id) })) },
// The launch_state view exists in the schema; no verb returns it.
launch: { source: 'launch_state view, no verb', state: ls ? (ls.kind === 'launch.revoked' ? 'revoked' : 'allowed') : 'allowed', at: ls?.at || null },
// Live and ended sessions; the model comes from the instance definition in the business file.
sessions: { source: 'session events, no verb', live: sessions.filter(l => !ended.some(x => x.run === l.run)), ended },
credentials: { source: 'credential events, no verb', notices: S.events.filter(e => e.kind.startsWith('credential.')).map(e => ({ kind: e.kind.split('.')[1], at: e.at, ...body(e) })) },
// Poll freshness: the S3 status read (lead decision 56, Q2).
status: { source: 'S3 status read, not built yet', ...L.poll },
};
}
window.S1Q1 = { views, missing };
})();