// 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 => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));
const icon = (id, label) => `${B.icon(id)} ${label ? `${esc(label)} ` : ''}`;
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 `${label} `; };
const feat = status => `${status} `;
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 => `${esc(agentOf(s.agent).name)} in ${esc(s.project)}/ ${esc(s.workspace)} `;
const avatar = id => `${esc(agentOf(id).name.slice(0, 2))} `;
const link = (href, text, cls = '') => `${text} `;
const btn = (label, action, opts = {}) => `${label} `;
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 `
Loading ${esc(what)}. Nothing is being requested; this is a demonstration state.
${opts.skeleton ? `
${' '.repeat(opts.skeleton)}
` : ''}
`;
if (s === 'empty') return `No ${esc(what)} yet.
${esc(opts.emptyHelp || 'When there is something here you will see it listed with its agent, project, and workspace.')}
${opts.emptyAction ? actionLink(opts.emptyAction, 'btn primary') : ''}
`;
if (s === 'error') return `Could not load ${esc(what)}. The service refused the request: audit recording is unavailable, so the operation was not attempted (fail closed).
Your view is unchanged. ${btn('Try again', 'retry', {cls:'primary'})} ${link('#/settings', 'Check settings', 'btn')}
`;
return content;
}
const legend = () => `Legend ${['implemented','planned','proposed','mockup'].map(feat).join('')}
FEATURES.md `;
// Building blocks
function waitingList(ctx, variant = 'list') {
const items = D.waiting.map(w => {
const s = sessionOf(w.session);
return `${icon(kindIcon[w.kind] || 'conversation')}
${ago(w.since)} ${scope(s)} ${badge(s.state)}
${esc(w.detail)}
${w.actions.map((a, i) => actionLink(a, i === 0 ? 'btn primary' : 'btn')).join('')}
`;
}).join('');
return stateWrap(ctx, 'items waiting on you', `${items} `, {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 => `
${avatar(s.agent)}${esc(agentOf(s.agent).name)}
${esc(s.project)}/ ${esc(s.workspace)}
${badge(s.state)}
${esc(s.goal.text)} ${esc(s.goal.report)}
${esc(s.controller)}gen ${s.generation}
${ago(s.since)}
${opts.compact ? '' : `${{matching:'Matches', changed:'Changed', unknown:'Unknown'}[s.config]} `}
`).join('');
return stateWrap(ctx, 'sessions', `Agent sessions across projects Agent Project / workspace Status Goal Controller Since ${opts.compact ? '' : 'Config '}${rows}
`, {skeleton: 5, emptyAction: ['Launch an agent', '#/launch']});
}
function sessionCard(s) {
return `${esc(s.goal.text)}
${esc(s.goal.report)}
${esc(s.controller)} gen ${s.generation} ${ago(s.since)} `;
}
function sessionCards(ctx, list = D.sessions) {
return stateWrap(ctx, 'sessions', `${list.map(sessionCard).join('')}
`, {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 `${esc(title)} ${list.length} ${list.length ? list.map(sessionCard).join('') : 'None
'} `;
}).join('');
return stateWrap(ctx, 'sessions', `${cols}
`, {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 `${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')}${s ? `${esc(e.text)} ` : esc(e.text)}
${ago(e.when)}${s ? ` · ${esc(s.project)}/${esc(s.workspace)}` : ''} `; }).join('');
return stateWrap(ctx, 'activity', `${items} `, {skeleton: 6});
}
function quickLaunch() {
return ``;
}
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 `Control board ${running().length} sessions active across ${D.projects.length} projects. ${D.waiting.length} waiting on you. ${feat('proposed')}
Waiting on you ${D.waiting.length} ${waitingList(ctx)}
Running now ${running().length} ${sessionsTable(ctx, running())}
Recently settled ${sessionsTable(ctx, recent(), {compact: true})}
Activity ${eventStream(ctx, 6)} ${quickLaunch()} `;
},
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 `Sessions Every conversation is bound to one agent, project, and workspace. ${feat('planned')}
${chips.map(([k, l]) => `${esc(l)} `).join('')}
${sessionsTable(ctx, list)}${list.length} of ${D.sessions.length} sessions shown. Legacy sessions are not listed until adopted by a reviewed assignment.
`;
},
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 `Control board / Sessions / ${esc(agentOf(s.agent).name)}
${avatar(s.agent)}
${esc(agentOf(s.agent).name)} in ${esc(s.project)}/ ${esc(s.workspace)} ${badge(s.state)} · started ${stamp(s.started)} · ${s.turns} turns · session ${esc(s.id)}
${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')}
${w ? `Waiting on you: ${esc(w.title)} ${esc(w.detail)}
${w.actions.map((a, i) => actionLink(a, i === 0 ? 'btn primary' : 'btn')).join('')}${btn('Mark handled (simulated)', 'handled:' + w.id)}
` : ''}
Conversation ${stateWrap(ctx, 'transcript', `${t.map(m => `${esc(m.who === 'agent' ? agentOf(s.agent).name : m.who)} ${esc(m.text)}
${m.tool ? `${icon('extensions')}${esc(m.tool)}
` : ''} `).join('')} `, {skeleton: 3})}
Goal ${feat('implemented')} ${esc(s.goal.text)}
${esc(s.goal.status.replace('_', ' '))} ${esc(s.goal.report)}
${btn('Stop goal', 'goal-stop:' + s.id)}${btn('Clear', 'goal-clear:' + s.id)}
Control ${feat('planned')} Controller ${esc(s.controller)} Generation ${s.generation} Observers ${[...s.observers, ...(ctx.local.observing.has(s.id) ? ['you'] : [])].join(', ') || 'none'} Harness ${esc(agentOf(s.agent).harness)} · ${esc(agentOf(s.agent).model)} One controlling connection at a time. Observers cannot send control input; transfer is explicit and records the generation.
Steering queue ${feat('proposed')} ${queue.map(q => `${q.state} ${esc(q.text)} ${btn(q.state === 'started' ? 'Interrupt' : 'Cancel', 'cancel:' + s.id + ':' + q.id)} `).join('') || 'Queue is empty. '} A started turn may finish its current tool call before it stops. Queued items are removed immediately.
Configuration ${feat('planned')} ${{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]}
${btn('Check now', 'config-check:' + s.id)}
Evidence ${feat('implemented')} ${D.runs.filter(r => r.agent === s.agent).slice(0, 3).map(r => `${esc(r.id)} ${esc(r.outcome)}, ${esc(r.duration)} `).join('') || 'No headless runs for this agent. '}
`;
},
projects(ctx) {
return `Projects A project registers agents and owns its workspaces. Membership in a project does not open every workspace. ${feat('planned')}
${btn(icon('plus', 'New project'), 'new-project', {cls: 'primary'})}
${stateWrap(ctx, 'projects', `${D.projects.map(p => `
Workspaces ${p.workspaces.map(w => `${esc(w.name)}${w.status === 'retired' ? ' retired ' : ''} `).join('')}
Agents ${p.agents.map(a => `${avatar(a)}${esc(agentOf(a).name)} `).join('')}
${D.sessions.filter(s => s.project === p.id && !['settled','stopped'].includes(s.state)).length} active sessions ${D.missions.filter(m => m.project === p.id).length} missions `).join('')}
`, {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 `Control board / Projects / ${esc(p.name)}
${esc(p.name)} Policy: ${esc(p.policy)} · ${p.workspaces.length} workspaces · ${p.agents.length} registered agents
Workspaces ${feat('planned')} Workspace Status Active sessions Actions ${p.workspaces.map(w => `${esc(w.name)} ${w.status === 'retired' ? 'Retired ' : 'Active '} ${D.sessions.filter(s => s.project === p.id && s.workspace === w.id && !['settled','stopped'].includes(s.state)).length} ${w.status === 'retired' ? btn('Reopen', 'reopen:' + w.id) : btn('Retire', 'retire:' + w.id)}${link('#/launch?project=' + p.id + '&workspace=' + w.id, 'Launch', 'btn')} `).join('')}
Registrations ${feat('planned')} Agent Workspace Role Status ${regs.map(r => `${avatar(r.agent.id)}${esc(r.agent.name)} ${esc(r.workspace)} ${esc(r.role)} Active `).join('')}
Roles are ceilings narrowed by registration. A role label never grants access to another workspace.
Missions ${feat('implemented')} ${D.missions.filter(m => m.project === p.id).map(m => `${esc(m.title)} ${m.workspace ? m.workspace : 'project level'} · owner ${esc(m.owner)} ${esc(m.criteria)} `).join('')}
Decisions ${feat('planned')} ${D.decisions.map(d => `${esc(d.id)} ${esc(d.date)} · ${esc(d.by)} · ${esc(d.kind)} ${esc(d.text)} `).join('')} `;
},
agents(ctx) {
return `Agents One reusable definition per agent. Registrations place it in projects and workspaces without creating a second identity. ${feat('planned')}
${btn(icon('plus', 'New agent'), 'new-agent', {cls: 'primary'})}
${stateWrap(ctx, 'agents', `${D.agents.map(a => `
SOUL ${esc(a.soul)} (one canonical file) Skills ${a.skills.join(', ') || 'none'} Registered ${a.registrations.map(r => `${esc(r[0])}/ ${esc(r[1])} ${esc(r[2])} `).join('')} Sessions ${D.sessions.filter(s => s.agent === a.id && !['settled','stopped'].includes(s.state)).length} active ${link('#/launch?agent=' + a.id, 'Launch', 'btn primary')}${btn('Edit SOUL', 'edit-soul:' + a.id)} `).join('')}
`, {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 `Work 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')}
All ${D.projects.map(p => `${esc(p.name)} `).join('')}
${stateWrap(ctx, 'tasks', `${cols.map(([k, label]) => { const ts = list.filter(t => t.column === k); return `
${label} ${ts.length} ${ts.map(t => `${esc(t.title)} ${t.assignee ? avatar(t.assignee) + esc(agentOf(t.assignee).name === t.assignee ? t.assignee : agentOf(t.assignee).name) : 'unassigned '} Mission ${t.mission ? esc(D.missions.find(m => m.id === t.mission)?.title) : 'ad hoc (still needs purpose and criteria)'} Depends on ${t.deps.map(d => `${esc(d)}`).join(', ') || 'nothing'} Evidence ${esc(t.evidence) || 'none yet '} ${btn('Assign', 'assign:' + t.id)}${btn('Move', 'move:' + t.id)}${k === 'review' ? btn('Accept', 'accept:' + t.id, {cls: 'primary'}) : ''}
`).join('') || 'None
'}`; }).join('')}
`, {skeleton: 4})}`;
},
runs(ctx) {
return `Runs and evidence Run records are write-once. Prune leaves a receipt. Nothing here can be edited. ${feat('implemented')}
${stateWrap(ctx, 'runs', `Run Task Agent / workspace Outcome Duration Tools Evidence ${D.runs.map(r => `${esc(r.id)}${esc(r.task)} ${esc(agentOf(r.agent).name)} · ${esc(r.workspace)} ${esc(r.outcome)} (exit ${r.exit}) ${esc(r.duration)} ${esc(r.tools)} ${esc(r.evidence)} ${btn('Show', 'show-run:' + r.id)} `).join('')}
`, {skeleton: 5})}
Releases ${feat('implemented')} ${D.releases.map(r => `${esc(r.id)} ${esc(r.state)} ${esc(r.image)}${r.activated ? ' · activated ' + stamp(r.activated) + ' by ' + esc(r.by) : ''} ${r.state === 'packaged' ? btn('Activate', 'activate:' + r.id) : r.state === 'previous' ? btn('Roll back to', 'rollback:' + r.id) : ''} `).join('')}
Command evidence ${feat('planned')} 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).
Actor rocko · execution s-00a7d195 Command npm test (allowlisted)Limits workspace acme-ops/ops, no network Outcome exit 0, 48 s `;
},
messages(ctx) {
const sel = ctx.params.id || D.messages[0].id; const m = D.messages.find(x => x.id === sel) || D.messages[0];
return `Messages Every message addresses a workspace. ${D.messages.filter(x => x.unread).length} unread. ${feat('proposed')}
${stateWrap(ctx, 'messages', ``, {skeleton: 4})}
${esc(agentOf(m.from).name === m.from ? m.from : agentOf(m.from).name)} to ${esc(m.to)} ${esc(m.project)}/${esc(m.workspace)} ${esc(m.text)}
${stamp(m.when)}
Reply in ${esc(m.project)}/${esc(m.workspace)} Send ${link('#/sessions', 'Open session', 'btn')}
Transport: mosaic comms (proposed). Today's transport is the tmux agent-send wrapper. Delivery never grants reassignment authority.
`;
},
library(ctx) {
return `Library Skills, extensions, and packages with their lifecycle and role ceilings. ${feat('planned')}
${stateWrap(ctx, 'skills', `Name Version State Ceiling Note Actions ${D.skills.map(s => `${esc(s.id)} ${esc(s.version)} ${esc(s.state)} ${esc(s.ceiling)} ${esc(s.note)} ${s.state === 'active' ? btn('Deactivate', 'deactivate:' + s.id) : btn('Activate', 'activate-skill:' + s.id)}${btn('Uninstall', 'uninstall:' + s.id, {disabled: s.ceiling === 'extension'})} `).join('')}
`, {skeleton: 5})}
Packages ${feat('planned')} packages/mosaic (registry schemas, validate and list), packages/config, packages/agent, packages/auth. Extensions remain the canonical source until M20 packaging is chartered.
`;
},
settings(ctx) {
const palettes = B.palettes.map(p => `${esc(p.name)} `).join('');
return `Settings Appearance is yours. Accounts show metadata only; credentials never appear in the interface.
Accounts ${feat('implemented')} ${feat('planned')} Account Provider Kind Status Default for Credential ${D.accounts.map(a => `${esc(a.id)} ${esc(a.provider)} ${esc(a.kind)} ${esc(a.status)} ${a.expires ? ` until ${esc(a.expires)} ` : ''}${a.defaultFor.map(x => esc(agentOf(x).name)).join(', ') || 'none '} ${esc(a.credential)} `).join('')}
${btn('Add account', 'add-account')}${btn('Refresh status', 'auth-status')}
Roles ${feat('planned')} Reader : permitted work records and scoped file inspection.Contributor : plus assigned file changes and result submission.Reviewer : evidence inspection and independent routine acceptance.Coordinator : within-plan decomposition and assignment changes.Not a rank hierarchy. Role files change only through reviewed commits.
Setup and presets ${feat('proposed')} Site homelab · instance main · system auth internal · break-glass admin set. Presets: Software factory Personal assistant Journal
${btn('Reconfigure', 'reconfigure')}${btn('Add user', 'add-user')}
Prototype controls ${feat('mockup')} Demonstration state${['normal','loading','empty','error'].map(s => `${s[0].toUpperCase() + s.slice(1)} `).join('')} Changes how lists render so reviewers can see loading, empty, and error handling. Nothing is requested or saved.
${legend()} `;
},
launch(ctx) {
const p = ctx.params;
const wsOptions = pid => (projectOf(pid).workspaces || []).map(w => `${esc(w.name)}${w.status === 'retired' ? ' (retired)' : ''} `).join('');
return `Launch Name the agent, project, and workspace. Resume reopens the existing conversation; Fresh starts a new one without importing old chat. ${feat('planned')}
Launch form
Agent${D.agents.map(a => `${esc(a.name)} · ${esc(a.type)} `).join('')}
Project${D.projects.map(pr => `${esc(pr.name)} `).join('')}
Workspace${wsOptions(p.project || D.projects[0].id)}
Conversation Resume (default; first use creates and announces) Fresh (new conversation; checked work state only)
Work decision for Fresh Continue selected assignments Select specific assignments Abandon this agent's assignments here (missions remain) None: conversation and inspection only
Launch (simulated) ${link('#/', 'Cancel', 'btn')}
What the service will do If this scope already has a running session, you get a conflict with an offer to connect. Nothing is launched twice. If the workspace is retired or policy is missing, the launch refuses with the reason. Refusals are not routed around. Launch inputs are recorded with a fingerprint so later configuration changes can be detected. `;
},
notFound(ctx) {
return `Page not found Nothing lives at ${esc(ctx.route)}. Your work is unaffected.
${link('#/', 'Back to the control board')} ${link('#/sessions', 'All sessions')} ${link('#/work', 'Work')} `;
},
};
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;
})();