Files
stack/agents/dewey/work/wui/mockups/shared/views.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

204 lines
36 KiB
JavaScript

// Route views and building blocks shared by the five mockups. Each design composes these differently.
(() => {
'use strict';
const D = window.MosaicData, B = window.Brand;
const esc = s => String(s ?? '').replace(/[&<>"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));
const icon = (id, label) => `<span class="ic" aria-hidden="true">${B.icon(id)}</span>${label ? `<span class="ic-label">${esc(label)}</span>` : ''}`;
const ago = iso => {
const ms = D.now - new Date(iso); const m = Math.round(ms / 60000);
if (m < 1) return 'just now'; if (m < 60) return `${m} min ago`;
const h = Math.floor(m / 60); if (h < 24) return `${h} h ${m % 60} min ago`;
const d = Math.floor(h / 24); return `${d} d ${h % 24} h ago`;
};
const dur = iso => { const m = Math.round((D.now - new Date(iso)) / 60000); const h = Math.floor(m / 60); return h ? `${h} h ${m % 60} min` : `${m} min`; };
const stamp = iso => iso.replace('T', ' ').replace(':00Z', ' UTC');
const STATES = {running:['Running','ok'], waiting:['Waiting on you','attn'], quiet:['Quiet wait','muted'], blocked:['Blocked','attn'], stalled:['Stalled','danger'], settled:['Settled','ok'], stopped:['Stopped','muted']};
const badge = state => { const [label, tone] = STATES[state] || [state, 'muted']; return `<span class="badge badge-${tone}" data-state="${state}">${label}</span>`; };
const feat = status => `<span class="feat feat-${status}" title="Feature status from FEATURES.md">${status}</span>`;
const agentOf = id => D.agents.find(a => a.id === id) || {id, name: id, type: ''};
const projectOf = id => D.projects.find(p => p.id === id) || {id, name: id, workspaces: []};
const sessionOf = id => D.sessions.find(s => s.id === id);
const scope = s => `<span class="scope"><b>${esc(agentOf(s.agent).name)}</b> <span class="sep">in</span> ${esc(s.project)}<span class="sep">/</span>${esc(s.workspace)}</span>`;
const avatar = id => `<span class="avatar" aria-hidden="true" data-agent="${esc(id)}">${esc(agentOf(id).name.slice(0, 2))}</span>`;
const link = (href, text, cls = '') => `<a href="${esc(href)}" class="${cls}">${text}</a>`;
const btn = (label, action, opts = {}) => `<button type="button" class="btn ${opts.cls || ''}" data-action="${esc(action)}" ${opts.disabled ? 'disabled aria-disabled="true"' : ''} ${opts.attrs || ''}>${label}</button>`;
const actionLink = ([label, target], cls = 'btn') => target.startsWith('action:') ? btn(esc(label), target.slice(7), {cls}) : link(target, esc(label), cls);
const kindIcon = {acceptance:'check', blocked:'warning', decision:'board', stalled:'error', config:'extensions', 'quiet':'dim'};
// Demo state wrapper: loading, empty, error, or the real content.
function stateWrap(ctx, what, content, opts = {}) {
const s = ctx.demoState;
if (s === 'loading') return `<div class="state state-loading" role="status" aria-live="polite"><span class="spinner" aria-hidden="true"></span><p>Loading ${esc(what)}. Nothing is being requested; this is a demonstration state.</p>${opts.skeleton ? `<div class="skeleton" aria-hidden="true">${'<i></i>'.repeat(opts.skeleton)}</div>` : ''}</div>`;
if (s === 'empty') return `<div class="state state-empty"><p><b>No ${esc(what)} yet.</b></p><p>${esc(opts.emptyHelp || 'When there is something here you will see it listed with its agent, project, and workspace.')}</p>${opts.emptyAction ? actionLink(opts.emptyAction, 'btn primary') : ''}</div>`;
if (s === 'error') return `<div class="state state-error" role="alert"><p><b>Could not load ${esc(what)}.</b> The service refused the request: audit recording is unavailable, so the operation was not attempted (fail closed).</p><p>Your view is unchanged. ${btn('Try again', 'retry', {cls:'primary'})} ${link('#/settings', 'Check settings', 'btn')}</p></div>`;
return content;
}
const legend = () => `<div class="legend" aria-label="Feature status legend"><span class="legend-title">Legend</span>${['implemented','planned','proposed','mockup'].map(feat).join('')}<a href="../FEATURES.md" class="legend-link">FEATURES.md</a></div>`;
// Building blocks
function waitingList(ctx, variant = 'list') {
const items = D.waiting.map(w => {
const s = sessionOf(w.session);
return `<li class="wait-item wait-${w.kind}"><div class="wait-head">${icon(kindIcon[w.kind] || 'conversation')}<h3><a href="#/sessions/${esc(w.session)}">${esc(w.title)}</a></h3><span class="wait-since">${ago(w.since)}</span></div><p class="wait-scope">${scope(s)} ${badge(s.state)}</p><p class="wait-detail">${esc(w.detail)}</p><div class="actions">${w.actions.map((a, i) => actionLink(a, i === 0 ? 'btn primary' : 'btn')).join('')}</div></li>`;
}).join('');
return stateWrap(ctx, 'items waiting on you', `<ol class="waiting waiting-${variant}">${items}</ol>`, {skeleton: 3, emptyHelp: 'Nothing is waiting on you. Sessions that need a decision, an acceptance, or a check-in appear here first.'});
}
function sessionsTable(ctx, list = D.sessions, opts = {}) {
const rows = list.map(s => `<tr data-session="${esc(s.id)}" class="${s.state === 'waiting' ? 'is-attn' : ''}">
<td class="col-agent">${avatar(s.agent)}<a href="#/sessions/${esc(s.id)}">${esc(agentOf(s.agent).name)}</a></td>
<td class="col-scope">${esc(s.project)}<span class="sep">/</span>${esc(s.workspace)}</td>
<td class="col-state">${badge(s.state)}</td>
<td class="col-goal"><span class="goal-text">${esc(s.goal.text)}</span><span class="goal-report">${esc(s.goal.report)}</span></td>
<td class="col-ctl">${esc(s.controller)}<span class="gen">gen ${s.generation}</span></td>
<td class="col-since"><time datetime="${esc(s.since)}">${ago(s.since)}</time></td>
${opts.compact ? '' : `<td class="col-config"><span class="cfg cfg-${s.config}">${{matching:'Matches', changed:'Changed', unknown:'Unknown'}[s.config]}</span></td>`}
</tr>`).join('');
return stateWrap(ctx, 'sessions', `<div class="table-wrap"><table class="sessions"><caption class="sr-only">Agent sessions across projects</caption><thead><tr><th scope="col">Agent</th><th scope="col">Project / workspace</th><th scope="col">Status</th><th scope="col">Goal</th><th scope="col">Controller</th><th scope="col">Since</th>${opts.compact ? '' : '<th scope="col">Config</th>'}</tr></thead><tbody>${rows}</tbody></table></div>`, {skeleton: 5, emptyAction: ['Launch an agent', '#/launch']});
}
function sessionCard(s) {
return `<article class="session-card ${s.state === 'waiting' ? 'is-attn' : ''}" data-session="${esc(s.id)}"><header>${avatar(s.agent)}<div><h3><a href="#/sessions/${esc(s.id)}">${esc(agentOf(s.agent).name)}</a></h3><p class="scope-line">${esc(s.project)}<span class="sep">/</span>${esc(s.workspace)}</p></div>${badge(s.state)}</header><p class="goal-text">${esc(s.goal.text)}</p><p class="goal-report">${esc(s.goal.report)}</p><footer><span>${esc(s.controller)} <span class="gen">gen ${s.generation}</span></span><time datetime="${esc(s.since)}">${ago(s.since)}</time></footer></article>`;
}
function sessionCards(ctx, list = D.sessions) {
return stateWrap(ctx, 'sessions', `<div class="card-grid">${list.map(sessionCard).join('')}</div>`, {skeleton: 6, emptyAction: ['Launch an agent', '#/launch']});
}
function sessionColumns(ctx, groups) {
const cols = groups.map(([title, states, tone]) => {
const list = D.sessions.filter(s => states.includes(s.state));
return `<section class="column column-${tone}" aria-labelledby="col-${esc(states[0])}"><h2 id="col-${esc(states[0])}">${esc(title)} <span class="count">${list.length}</span></h2>${list.length ? list.map(sessionCard).join('') : '<p class="column-empty">None</p>'}</section>`;
}).join('');
return stateWrap(ctx, 'sessions', `<div class="columns">${cols}</div>`, {skeleton: 4, emptyAction: ['Launch an agent', '#/launch']});
}
function eventStream(ctx, limit = 12) {
const items = D.events.slice(0, limit).map(e => { const s = e.session && sessionOf(e.session); return `<li class="event event-${e.kind}">${icon({start:'plus', turn:'conversation', tool:'extensions', waiting:'warning', quiet:'dim', config:'extensions', blocked:'warning', stalled:'error', settled:'check', release:'projects', stop:'error'}[e.kind] || 'conversation')}<div><p>${s ? `<a href="#/sessions/${esc(s.id)}">${esc(e.text)}</a>` : esc(e.text)}</p><time datetime="${esc(e.when)}">${ago(e.when)}${s ? ` · ${esc(s.project)}/${esc(s.workspace)}` : ''}</time></div></li>`; }).join('');
return stateWrap(ctx, 'activity', `<ol class="stream">${items}</ol>`, {skeleton: 6});
}
function quickLaunch() {
return `<form class="quick-launch" data-form="quick-launch" aria-labelledby="ql-title"><h2 id="ql-title">Launch</h2><p class="muted">Resume is the default. First use creates the conversation and says so.</p>
<label>Agent<select name="agent">${D.agents.map(a => `<option value="${a.id}">${esc(a.name)}</option>`).join('')}</select></label>
<label>Project<select name="project">${D.projects.map(p => `<option value="${p.id}">${esc(p.name)}</option>`).join('')}</select></label>
<label>Workspace<select name="workspace">${D.projects[0].workspaces.map(w => `<option value="${w.id}">${esc(w.name)}${w.status === 'retired' ? ' (retired)' : ''}</option>`).join('')}</select></label>
<div class="actions"><button type="submit" class="btn primary">Resume</button>${link('#/launch', 'More options', 'btn')}</div><output name="result" class="form-result" aria-live="polite"></output></form>`;
}
const running = () => D.sessions.filter(s => ['running','waiting','quiet','blocked','stalled'].includes(s.state));
const recent = () => D.sessions.filter(s => ['settled','stopped'].includes(s.state));
// Route views
const views = {
home(ctx) {
if (ctx.compose?.home) return ctx.compose.home(ctx, api);
return `<div class="page-head"><h1>Control board</h1><p class="muted">${running().length} sessions active across ${D.projects.length} projects. ${D.waiting.length} waiting on you. ${feat('proposed')}</p></div>
<div class="home-grid"><section class="panel panel-waiting" aria-labelledby="h-wait"><h2 id="h-wait">Waiting on you <span class="count">${D.waiting.length}</span></h2>${waitingList(ctx)}</section>
<section class="panel" aria-labelledby="h-run"><h2 id="h-run">Running now <span class="count">${running().length}</span></h2>${sessionsTable(ctx, running())}</section>
<section class="panel" aria-labelledby="h-recent"><h2 id="h-recent">Recently settled</h2>${sessionsTable(ctx, recent(), {compact: true})}</section>
<aside class="panel panel-side"><section aria-labelledby="h-act"><h2 id="h-act">Activity</h2>${eventStream(ctx, 6)}</section>${quickLaunch()}</aside></div>`;
},
sessions(ctx) {
const f = ctx.params.filter || 'all';
const list = f === 'all' ? D.sessions : D.sessions.filter(s => s.state === f || s.project === f);
const chips = [['all','All'], ...Object.entries(STATES).map(([k, [l]]) => [k, l]), ...D.projects.map(p => [p.id, p.name])];
return `<div class="page-head"><h1>Sessions</h1><p class="muted">Every conversation is bound to one agent, project, and workspace. ${feat('planned')}</p></div>
<nav class="chips" aria-label="Filter sessions">${chips.map(([k, l]) => `<a href="#/sessions?filter=${k}" class="chip" ${f === k ? 'aria-current="true"' : ''}>${esc(l)}</a>`).join('')}</nav>
${sessionsTable(ctx, list)}<p class="muted small">${list.length} of ${D.sessions.length} sessions shown. Legacy sessions are not listed until adopted by a reviewed assignment.</p>`;
},
session(ctx) {
const s = sessionOf(ctx.params.id); if (!s) return views.notFound(ctx);
const t = D.transcript[s.id] || D.defaultTranscript;
const youControl = s.controller.startsWith('you');
const observing = s.observers.includes('you') || ctx.local.observing.has(s.id);
const queue = ctx.local.queue[s.id] || [{id: 'q1', text: 'Please summarize what changed since yesterday.', state: 'started'}, {id: 'q2', text: 'Then list any open questions for me.', state: 'queued'}];
const w = D.waiting.find(x => x.session === s.id);
return `<nav class="crumbs" aria-label="Breadcrumb"><a href="#/">Control board</a><span class="sep">/</span><a href="#/sessions">Sessions</a><span class="sep">/</span><span aria-current="page">${esc(agentOf(s.agent).name)}</span></nav>
<div class="page-head session-head">${avatar(s.agent)}<div><h1>${esc(agentOf(s.agent).name)} <span class="muted">in</span> ${esc(s.project)}<span class="sep">/</span>${esc(s.workspace)}</h1><p class="muted">${badge(s.state)} · started ${stamp(s.started)} · ${s.turns} turns · session <code>${esc(s.id)}</code></p></div>
<div class="actions head-actions">${btn(icon('people', observing ? 'Observing' : 'Observe'), 'observe:' + s.id, {disabled: observing || youControl, attrs: `aria-pressed="${observing}"`})}${btn(icon('arrow', youControl ? 'You control' : 'Request control'), 'control:' + s.id, {disabled: youControl})}${btn(icon('error', 'Stop'), 'stop:' + s.id, {disabled: ['settled','stopped'].includes(s.state)})}${link('#/launch?agent=' + s.agent + '&project=' + s.project + '&workspace=' + s.workspace + '&mode=fresh', icon('plus', 'Fresh'), 'btn')}</div></div>
${w ? `<section class="notice notice-attn" aria-labelledby="n-wait"><h2 id="n-wait">Waiting on you: ${esc(w.title)}</h2><p>${esc(w.detail)}</p><div class="actions">${w.actions.map((a, i) => actionLink(a, i === 0 ? 'btn primary' : 'btn')).join('')}${btn('Mark handled (simulated)', 'handled:' + w.id)}</div></section>` : ''}
<div class="session-grid">
<section class="panel transcript" aria-labelledby="h-tr"><h2 id="h-tr">Conversation</h2>${stateWrap(ctx, 'transcript', `<ol class="turns">${t.map(m => `<li class="turn turn-${m.who === 'you' ? 'you' : 'agent'}"><span class="who">${esc(m.who === 'agent' ? agentOf(s.agent).name : m.who)}</span><p>${esc(m.text)}</p>${m.tool ? `<p class="tool">${icon('extensions')}${esc(m.tool)}</p>` : ''}</li>`).join('')}</ol>`, {skeleton: 3})}
<form class="composer" data-form="composer" data-session="${esc(s.id)}"><label for="msg-${esc(s.id)}">Message ${esc(agentOf(s.agent).name)}${youControl ? '' : ' (you are not the controller; this will be queued as a request)'}</label><textarea id="msg-${esc(s.id)}" rows="3" placeholder="Steer, ask, or decide"></textarea><div class="actions"><button type="submit" class="btn primary">Send</button><button type="button" class="btn" data-action="queue-after:${esc(s.id)}">Queue after current turn</button></div><output class="form-result" aria-live="polite"></output></form></section>
<aside class="session-side">
<section class="panel" aria-labelledby="h-goal"><h2 id="h-goal">Goal ${feat('implemented')}</h2><p class="goal-text">${esc(s.goal.text)}</p><p><span class="badge badge-${{satisfied:'ok', blocked:'attn', in_progress:'muted'}[s.goal.status]}">${esc(s.goal.status.replace('_', ' '))}</span> ${esc(s.goal.report)}</p><div class="actions">${btn('Stop goal', 'goal-stop:' + s.id)}${btn('Clear', 'goal-clear:' + s.id)}</div></section>
<section class="panel" aria-labelledby="h-ctl"><h2 id="h-ctl">Control ${feat('planned')}</h2><dl class="kv"><dt>Controller</dt><dd>${esc(s.controller)}</dd><dt>Generation</dt><dd>${s.generation}</dd><dt>Observers</dt><dd>${[...s.observers, ...(ctx.local.observing.has(s.id) ? ['you'] : [])].join(', ') || 'none'}</dd><dt>Harness</dt><dd>${esc(agentOf(s.agent).harness)} · ${esc(agentOf(s.agent).model)}</dd></dl><p class="small muted">One controlling connection at a time. Observers cannot send control input; transfer is explicit and records the generation.</p></section>
<section class="panel" aria-labelledby="h-q"><h2 id="h-q">Steering queue ${feat('proposed')}</h2><ol class="queue">${queue.map(q => `<li class="q-${q.state}"><span class="badge badge-${q.state === 'started' ? 'ok' : 'muted'}">${q.state}</span><span>${esc(q.text)}</span>${btn(q.state === 'started' ? 'Interrupt' : 'Cancel', 'cancel:' + s.id + ':' + q.id)}</li>`).join('') || '<li class="muted">Queue is empty.</li>'}</ol><p class="small muted">A started turn may finish its current tool call before it stops. Queued items are removed immediately.</p></section>
<section class="panel cfg-panel cfg-${s.config}" aria-labelledby="h-cfg"><h2 id="h-cfg">Configuration ${feat('planned')}</h2><p>${{matching:'Launch inputs match the current base configuration.', changed:'Base configuration changed after launch: SOUL rev 13 to rev 14. This session keeps its launch inputs. Fresh is recommended at a safe point.', unknown:'Could not compare: the launch manifest for this session is missing. Treat as unknown, not as matching.'}[s.config]}</p><div class="actions">${btn('Check now', 'config-check:' + s.id)}</div></section>
<section class="panel" aria-labelledby="h-ev"><h2 id="h-ev">Evidence ${feat('implemented')}</h2><ul class="plain">${D.runs.filter(r => r.agent === s.agent).slice(0, 3).map(r => `<li><a href="#/runs">${esc(r.id)}</a> <span class="muted">${esc(r.outcome)}, ${esc(r.duration)}</span></li>`).join('') || '<li class="muted">No headless runs for this agent.</li>'}</ul></section>
</aside></div>`;
},
projects(ctx) {
return `<div class="page-head"><h1>Projects</h1><p class="muted">A project registers agents and owns its workspaces. Membership in a project does not open every workspace. ${feat('planned')}</p><div class="actions">${btn(icon('plus', 'New project'), 'new-project', {cls: 'primary'})}</div></div>
${stateWrap(ctx, 'projects', `<div class="card-grid project-grid">${D.projects.map(p => `<article class="project-card"><header><h2><a href="#/projects/${p.id}">${esc(p.name)}</a></h2><span class="badge badge-ok">${esc(p.status)}</span></header>
<h3>Workspaces</h3><ul class="pills">${p.workspaces.map(w => `<li class="${w.status}">${esc(w.name)}${w.status === 'retired' ? ' <span class="muted">retired</span>' : ''}</li>`).join('')}</ul>
<h3>Agents</h3><ul class="pills">${p.agents.map(a => `<li>${avatar(a)}${esc(agentOf(a).name)}</li>`).join('')}</ul>
<footer><span>${D.sessions.filter(s => s.project === p.id && !['settled','stopped'].includes(s.state)).length} active sessions</span><span>${D.missions.filter(m => m.project === p.id).length} missions</span></footer></article>`).join('')}</div>`, {skeleton: 3, emptyAction: ['Create a project', 'action:new-project']})}`;
},
project(ctx) {
const p = D.projects.find(x => x.id === ctx.params.id); if (!p) return views.notFound(ctx);
const regs = D.agents.flatMap(a => a.registrations.filter(r => r[0] === p.id).map(r => ({agent: a, workspace: r[1], role: r[2]})));
return `<nav class="crumbs" aria-label="Breadcrumb"><a href="#/">Control board</a><span class="sep">/</span><a href="#/projects">Projects</a><span class="sep">/</span><span aria-current="page">${esc(p.name)}</span></nav>
<div class="page-head"><h1>${esc(p.name)}</h1><p class="muted">Policy: ${esc(p.policy)} · ${p.workspaces.length} workspaces · ${p.agents.length} registered agents</p></div>
<div class="two-col">
<section class="panel" aria-labelledby="h-ws"><h2 id="h-ws">Workspaces ${feat('planned')}</h2><div class="table-wrap"><table><thead><tr><th scope="col">Workspace</th><th scope="col">Status</th><th scope="col">Active sessions</th><th scope="col">Actions</th></tr></thead><tbody>${p.workspaces.map(w => `<tr><td>${esc(w.name)}</td><td>${w.status === 'retired' ? '<span class="badge badge-muted">Retired</span>' : '<span class="badge badge-ok">Active</span>'}</td><td>${D.sessions.filter(s => s.project === p.id && s.workspace === w.id && !['settled','stopped'].includes(s.state)).length}</td><td class="actions">${w.status === 'retired' ? btn('Reopen', 'reopen:' + w.id) : btn('Retire', 'retire:' + w.id)}${link('#/launch?project=' + p.id + '&workspace=' + w.id, 'Launch', 'btn')}</td></tr>`).join('')}</tbody></table></div></section>
<section class="panel" aria-labelledby="h-reg"><h2 id="h-reg">Registrations ${feat('planned')}</h2><div class="table-wrap"><table><thead><tr><th scope="col">Agent</th><th scope="col">Workspace</th><th scope="col">Role</th><th scope="col">Status</th></tr></thead><tbody>${regs.map(r => `<tr><td>${avatar(r.agent.id)}${esc(r.agent.name)}</td><td>${esc(r.workspace)}</td><td>${esc(r.role)}</td><td><span class="badge badge-ok">Active</span></td></tr>`).join('')}</tbody></table></div><p class="small muted">Roles are ceilings narrowed by registration. A role label never grants access to another workspace.</p></section>
<section class="panel" aria-labelledby="h-mis"><h2 id="h-mis">Missions ${feat('implemented')}</h2><ul class="plain">${D.missions.filter(m => m.project === p.id).map(m => `<li><b>${esc(m.title)}</b> <span class="muted">${m.workspace ? m.workspace : 'project level'} · owner ${esc(m.owner)}</span><br><span class="small">${esc(m.criteria)}</span></li>`).join('')}</ul></section>
<section class="panel" aria-labelledby="h-dec"><h2 id="h-dec">Decisions ${feat('planned')}</h2><ul class="plain">${D.decisions.map(d => `<li><b>${esc(d.id)}</b> <span class="muted">${esc(d.date)} · ${esc(d.by)} · ${esc(d.kind)}</span><br><span class="small">${esc(d.text)}</span></li>`).join('')}</ul></section></div>`;
},
agents(ctx) {
return `<div class="page-head"><h1>Agents</h1><p class="muted">One reusable definition per agent. Registrations place it in projects and workspaces without creating a second identity. ${feat('planned')}</p><div class="actions">${btn(icon('plus', 'New agent'), 'new-agent', {cls: 'primary'})}</div></div>
${stateWrap(ctx, 'agents', `<div class="card-grid agent-grid">${D.agents.map(a => `<article class="agent-card"><header>${avatar(a.id)}<div><h2>${esc(a.name)}</h2><p class="muted">${esc(a.type)} · ${esc(a.harness)} · ${esc(a.model)}</p></div></header><dl class="kv"><dt>SOUL</dt><dd>${esc(a.soul)} <span class="muted">(one canonical file)</span></dd><dt>Skills</dt><dd>${a.skills.join(', ') || 'none'}</dd><dt>Registered</dt><dd><ul class="plain">${a.registrations.map(r => `<li>${esc(r[0])}<span class="sep">/</span>${esc(r[1])} <span class="muted">${esc(r[2])}</span></li>`).join('')}</ul></dd><dt>Sessions</dt><dd>${D.sessions.filter(s => s.agent === a.id && !['settled','stopped'].includes(s.state)).length} active</dd></dl><footer class="actions">${link('#/launch?agent=' + a.id, 'Launch', 'btn primary')}${btn('Edit SOUL', 'edit-soul:' + a.id)}</footer></article>`).join('')}</div>`, {skeleton: 4})}`;
},
work(ctx) {
const f = ctx.params.project || 'all';
const list = D.tasks.filter(t => f === 'all' || (t.mission ? D.missions.find(m => m.id === t.mission)?.project === f : t.project === f));
const cols = [['backlog','Backlog'],['ready','Ready'],['doing','Doing'],['review','Review'],['waiting','Waiting on owner'],['done','Done']];
return `<div class="page-head"><h1>Work</h1><p class="muted">Missions govern tasks. Assignments bind a task to one agent. Kanban columns are a view of the same records, not a second list. ${feat('implemented')} ${feat('proposed')}</p></div>
<nav class="chips" aria-label="Filter by project"><a href="#/work" class="chip" ${f === 'all' ? 'aria-current="true"' : ''}>All</a>${D.projects.map(p => `<a href="#/work?project=${p.id}" class="chip" ${f === p.id ? 'aria-current="true"' : ''}>${esc(p.name)}</a>`).join('')}</nav>
${stateWrap(ctx, 'tasks', `<div class="columns kanban">${cols.map(([k, label]) => { const ts = list.filter(t => t.column === k); return `<section class="column" aria-labelledby="k-${k}"><h2 id="k-${k}">${label} <span class="count">${ts.length}</span></h2>${ts.map(t => `<details class="task-card"><summary><span class="task-title">${esc(t.title)}</span><span class="task-meta">${t.assignee ? avatar(t.assignee) + esc(agentOf(t.assignee).name === t.assignee ? t.assignee : agentOf(t.assignee).name) : '<span class="muted">unassigned</span>'}</span></summary><dl class="kv"><dt>Mission</dt><dd>${t.mission ? esc(D.missions.find(m => m.id === t.mission)?.title) : 'ad hoc (still needs purpose and criteria)'}</dd><dt>Depends on</dt><dd>${t.deps.map(d => `<code>${esc(d)}</code>`).join(', ') || 'nothing'}</dd><dt>Evidence</dt><dd>${esc(t.evidence) || '<span class="muted">none yet</span>'}</dd></dl><div class="actions">${btn('Assign', 'assign:' + t.id)}${btn('Move', 'move:' + t.id)}${k === 'review' ? btn('Accept', 'accept:' + t.id, {cls: 'primary'}) : ''}</div></details>`).join('') || '<p class="column-empty">None</p>'}</section>`; }).join('')}</div>`, {skeleton: 4})}`;
},
runs(ctx) {
return `<div class="page-head"><h1>Runs and evidence</h1><p class="muted">Run records are write-once. Prune leaves a receipt. Nothing here can be edited. ${feat('implemented')}</p></div>
${stateWrap(ctx, 'runs', `<div class="table-wrap"><table class="runs"><thead><tr><th scope="col">Run</th><th scope="col">Task</th><th scope="col">Agent / workspace</th><th scope="col">Outcome</th><th scope="col">Duration</th><th scope="col">Tools</th><th scope="col">Evidence</th></tr></thead><tbody>${D.runs.map(r => `<tr><td><code>${esc(r.id)}</code></td><td>${esc(r.task)}</td><td>${esc(agentOf(r.agent).name)} · ${esc(r.workspace)}</td><td><span class="badge badge-${r.outcome === 'success' ? 'ok' : 'danger'}">${esc(r.outcome)} (exit ${r.exit})</span></td><td>${esc(r.duration)}</td><td>${esc(r.tools)}</td><td>${esc(r.evidence)} ${btn('Show', 'show-run:' + r.id)}</td></tr>`).join('')}</tbody></table></div>`, {skeleton: 5})}
<div class="two-col"><section class="panel" aria-labelledby="h-rel"><h2 id="h-rel">Releases ${feat('implemented')}</h2><ul class="plain">${D.releases.map(r => `<li><b>${esc(r.id)}</b> <span class="badge badge-${r.state === 'active' ? 'ok' : 'muted'}">${esc(r.state)}</span> <span class="muted">${esc(r.image)}${r.activated ? ' · activated ' + stamp(r.activated) + ' by ' + esc(r.by) : ''}</span> ${r.state === 'packaged' ? btn('Activate', 'activate:' + r.id) : r.state === 'previous' ? btn('Roll back to', 'rollback:' + r.id) : ''}</li>`).join('')}</ul></section>
<section class="panel" aria-labelledby="h-audit"><h2 id="h-audit">Command evidence ${feat('planned')}</h2><p class="small">Each managed command records actor, scope, assignment, the authorized command, enforced limits, start and end, outcome, and evidence references. Internal file and network calls are not separately enumerated (owner ruling Q27 A).</p><dl class="kv"><dt>Actor</dt><dd>rocko · execution s-00a7d195</dd><dt>Command</dt><dd><code>npm test</code> (allowlisted)</dd><dt>Limits</dt><dd>workspace acme-ops/ops, no network</dd><dt>Outcome</dt><dd>exit 0, 48 s</dd></dl></section></div>`;
},
messages(ctx) {
const sel = ctx.params.id || D.messages[0].id; const m = D.messages.find(x => x.id === sel) || D.messages[0];
return `<div class="page-head"><h1>Messages</h1><p class="muted">Every message addresses a workspace. ${D.messages.filter(x => x.unread).length} unread. ${feat('proposed')}</p></div>
<div class="split"><nav class="thread-list" aria-label="Threads">${stateWrap(ctx, 'messages', `<ul class="plain">${D.messages.map(x => `<li><a href="#/messages?id=${x.id}" ${x.id === m.id ? 'aria-current="true"' : ''} class="thread ${x.unread ? 'unread' : ''}">${avatar(x.from)}<span><b>${esc(agentOf(x.from).name === x.from ? x.from : agentOf(x.from).name)}</b> <span class="muted small">${esc(x.project)}/${esc(x.workspace)} · ${ago(x.when)}</span><br><span class="preview">${esc(x.text)}</span></span></a></li>`).join('')}</ul>`, {skeleton: 4})}</nav>
<section class="panel thread-view" aria-labelledby="h-thread"><h2 id="h-thread">${esc(agentOf(m.from).name === m.from ? m.from : agentOf(m.from).name)} to ${esc(m.to)} <span class="muted small">${esc(m.project)}/${esc(m.workspace)}</span></h2><p class="message-body">${esc(m.text)}</p><time class="muted small" datetime="${esc(m.when)}">${stamp(m.when)}</time>
<form class="composer" data-form="reply"><label for="reply">Reply in ${esc(m.project)}/${esc(m.workspace)}</label><textarea id="reply" rows="3"></textarea><div class="actions"><button type="submit" class="btn primary">Send</button>${link('#/sessions', 'Open session', 'btn')}</div><output class="form-result" aria-live="polite"></output></form>
<p class="small muted">Transport: mosaic comms (proposed). Today's transport is the tmux agent-send wrapper. Delivery never grants reassignment authority.</p></section></div>`;
},
library(ctx) {
return `<div class="page-head"><h1>Library</h1><p class="muted">Skills, extensions, and packages with their lifecycle and role ceilings. ${feat('planned')}</p></div>
${stateWrap(ctx, 'skills', `<div class="table-wrap"><table><thead><tr><th scope="col">Name</th><th scope="col">Version</th><th scope="col">State</th><th scope="col">Ceiling</th><th scope="col">Note</th><th scope="col">Actions</th></tr></thead><tbody>${D.skills.map(s => `<tr><td><b>${esc(s.id)}</b></td><td>${esc(s.version)}</td><td><span class="badge badge-${s.state === 'active' ? 'ok' : 'muted'}">${esc(s.state)}</span></td><td>${esc(s.ceiling)}</td><td>${esc(s.note)}</td><td class="actions">${s.state === 'active' ? btn('Deactivate', 'deactivate:' + s.id) : btn('Activate', 'activate-skill:' + s.id)}${btn('Uninstall', 'uninstall:' + s.id, {disabled: s.ceiling === 'extension'})}</td></tr>`).join('')}</tbody></table></div>`, {skeleton: 5})}
<section class="panel" aria-labelledby="h-pk"><h2 id="h-pk">Packages ${feat('planned')}</h2><p class="small">packages/mosaic (registry schemas, validate and list), packages/config, packages/agent, packages/auth. Extensions remain the canonical source until M20 packaging is chartered.</p></section>`;
},
settings(ctx) {
const palettes = B.palettes.map(p => `<option value="${p.id}" ${ctx.theme.palette === p.id ? 'selected' : ''}>${esc(p.name)}</option>`).join('');
return `<div class="page-head"><h1>Settings</h1><p class="muted">Appearance is yours. Accounts show metadata only; credentials never appear in the interface.</p></div>
<div class="settings-grid">
<section class="panel" aria-labelledby="h-app"><h2 id="h-app">Appearance ${feat('mockup')}</h2><label class="field">Palette<select id="palette-select" data-theme="palette">${palettes}</select></label><fieldset class="mode-control"><legend>Mode</legend><div class="segmented">${['light','dim','dark'].map(m => `<label><input type="radio" name="mode" value="${m}" data-theme="mode" ${ctx.theme.mode === m ? 'checked' : ''}><span>${m[0].toUpperCase() + m.slice(1)}</span></label>`).join('')}</div></fieldset><p class="small muted">Follows your system preference until you choose. Stored in this browser only.</p></section>
<section class="panel" aria-labelledby="h-acc"><h2 id="h-acc">Accounts ${feat('implemented')} ${feat('planned')}</h2><div class="table-wrap"><table><thead><tr><th scope="col">Account</th><th scope="col">Provider</th><th scope="col">Kind</th><th scope="col">Status</th><th scope="col">Default for</th><th scope="col">Credential</th></tr></thead><tbody>${D.accounts.map(a => `<tr><td><b>${esc(a.id)}</b></td><td>${esc(a.provider)}</td><td>${esc(a.kind)}</td><td><span class="badge badge-${{valid:'ok', expiring:'attn', 'needs login':'danger'}[a.status]}">${esc(a.status)}</span>${a.expires ? `<span class="small muted"> until ${esc(a.expires)}</span>` : ''}</td><td>${a.defaultFor.map(x => esc(agentOf(x).name)).join(', ') || '<span class="muted">none</span>'}</td><td class="muted small">${esc(a.credential)}</td></tr>`).join('')}</tbody></table></div><div class="actions">${btn('Add account', 'add-account')}${btn('Refresh status', 'auth-status')}</div></section>
<section class="panel" aria-labelledby="h-roles"><h2 id="h-roles">Roles ${feat('planned')}</h2><ul class="plain"><li><b>Reader</b>: permitted work records and scoped file inspection.</li><li><b>Contributor</b>: plus assigned file changes and result submission.</li><li><b>Reviewer</b>: evidence inspection and independent routine acceptance.</li><li><b>Coordinator</b>: within-plan decomposition and assignment changes.</li></ul><p class="small muted">Not a rank hierarchy. Role files change only through reviewed commits.</p></section>
<section class="panel" aria-labelledby="h-onb"><h2 id="h-onb">Setup and presets ${feat('proposed')}</h2><p class="small">Site homelab · instance main · system auth internal · break-glass admin set. Presets: <span class="pill">Software factory</span> <span class="pill">Personal assistant</span> <span class="pill">Journal</span></p><div class="actions">${btn('Reconfigure', 'reconfigure')}${btn('Add user', 'add-user')}</div></section>
<section class="panel" aria-labelledby="h-proto"><h2 id="h-proto">Prototype controls ${feat('mockup')}</h2><label class="field">Demonstration state<select id="demo-state" data-demo>${['normal','loading','empty','error'].map(s => `<option value="${s}" ${ctx.demoState === s ? 'selected' : ''}>${s[0].toUpperCase() + s.slice(1)}</option>`).join('')}</select></label><p class="small muted">Changes how lists render so reviewers can see loading, empty, and error handling. Nothing is requested or saved.</p>${legend()}</section></div>`;
},
launch(ctx) {
const p = ctx.params;
const wsOptions = pid => (projectOf(pid).workspaces || []).map(w => `<option value="${w.id}" ${p.workspace === w.id ? 'selected' : ''}>${esc(w.name)}${w.status === 'retired' ? ' (retired)' : ''}</option>`).join('');
return `<div class="page-head"><h1>Launch</h1><p class="muted">Name the agent, project, and workspace. Resume reopens the existing conversation; Fresh starts a new one without importing old chat. ${feat('planned')}</p></div>
<form class="launch-form panel" data-form="launch" aria-labelledby="h-launch"><h2 id="h-launch" class="sr-only">Launch form</h2>
<div class="form-grid"><label>Agent<select name="agent">${D.agents.map(a => `<option value="${a.id}" ${p.agent === a.id ? 'selected' : ''}>${esc(a.name)} · ${esc(a.type)}</option>`).join('')}</select></label>
<label>Project<select name="project">${D.projects.map(pr => `<option value="${pr.id}" ${(p.project || D.projects[0].id) === pr.id ? 'selected' : ''}>${esc(pr.name)}</option>`).join('')}</select></label>
<label>Workspace<select name="workspace">${wsOptions(p.project || D.projects[0].id)}</select></label></div>
<fieldset><legend>Conversation</legend><label class="radio"><input type="radio" name="mode" value="resume" ${p.mode !== 'fresh' ? 'checked' : ''}> Resume <span class="muted">(default; first use creates and announces)</span></label><label class="radio"><input type="radio" name="mode" value="fresh" ${p.mode === 'fresh' ? 'checked' : ''}> Fresh <span class="muted">(new conversation; checked work state only)</span></label></fieldset>
<fieldset data-fresh-only ${p.mode === 'fresh' ? '' : 'hidden'}><legend>Work decision for Fresh</legend><label class="radio"><input type="radio" name="work" value="continue" checked> Continue selected assignments</label><label class="radio"><input type="radio" name="work" value="select"> Select specific assignments</label><label class="radio"><input type="radio" name="work" value="abandon"> Abandon this agent's assignments here (missions remain)</label><label class="radio"><input type="radio" name="work" value="none"> None: conversation and inspection only</label></fieldset>
<div class="actions"><button type="submit" class="btn primary">Launch (simulated)</button>${link('#/', 'Cancel', 'btn')}</div><output class="form-result" aria-live="polite"></output></form>
<section class="panel" aria-labelledby="h-rules"><h2 id="h-rules">What the service will do</h2><ul class="plain small"><li>If this scope already has a running session, you get a conflict with an offer to connect. Nothing is launched twice.</li><li>If the workspace is retired or policy is missing, the launch refuses with the reason. Refusals are not routed around.</li><li>Launch inputs are recorded with a fingerprint so later configuration changes can be detected.</li></ul></section>`;
},
notFound(ctx) {
return `<div class="page-head"><h1>Page not found</h1><p class="muted">Nothing lives at <code>${esc(ctx.route)}</code>. Your work is unaffected.</p></div><ul class="plain"><li>${link('#/', 'Back to the control board')}</li><li>${link('#/sessions', 'All sessions')}</li><li>${link('#/work', 'Work')}</li></ul>`;
},
};
const api = {views, esc, icon, ago, dur, stamp, badge, feat, avatar, scope, link, btn, actionLink, legend, stateWrap, waitingList, sessionsTable, sessionCard, sessionCards, sessionColumns, eventStream, quickLaunch, running, recent, agentOf, projectOf, sessionOf, STATES};
window.MosaicViews = api;
})();