@volter/teams 0.1.21 → 0.1.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volter/teams",
3
- "version": "0.1.21",
3
+ "version": "0.1.22",
4
4
  "type": "module",
5
5
  "description": "Volter Teams, the neutral team plane: machine identity and daemon core, the local door and server link, admission from machine grants, product doors, and the Teams server's neutral routes (company RFC 0008).",
6
6
  "license": "MIT",
package/viewer/index.html CHANGED
@@ -7,13 +7,14 @@
7
7
  <title>Viewer</title>
8
8
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@xterm/xterm@5.5.0/css/xterm.css">
9
9
  <style>
10
- :root { color-scheme: dark; --bg: #0b0d10; --panel: #13161b; --line: #262b33; --text: #e6e8eb; --muted: #8b939e; --accent: #7aa2f7; }
10
+ /* The page's scheme is the host's when it sets one (`--scui-color-scheme` through the theme door), else dark. */
11
+ :root { --scui-color-scheme: dark; color-scheme: var(--scui-color-scheme); --bg: #0b0d10; --panel: #13161b; --line: #262b33; --text: #e6e8eb; --muted: #8b939e; --accent: #7aa2f7; }
11
12
  * { box-sizing: border-box; }
12
13
  html, body { height: 100%; margin: 0; background: var(--bg); color: var(--text); font: 13px/1.4 ui-sans-serif, system-ui, -apple-system, sans-serif; }
13
14
  body { display: flex; flex-direction: column; }
14
- header { display: flex; align-items: center; gap: 8px; padding: 6px 10px; border-bottom: 1px solid var(--line); background: var(--panel); min-height: 32px; }
15
- header .target { font-family: ui-monospace, Menlo, monospace; color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
16
- header .scope { margin-left: auto; font-size: 11px; padding: 1px 6px; border: 1px solid var(--line); border-radius: 4px; color: var(--muted); }
15
+ body > header { display: flex; align-items: center; gap: 8px; padding: 6px 10px; border-bottom: 1px solid var(--line); background: var(--panel); min-height: 32px; }
16
+ body > header .target { font-family: ui-monospace, Menlo, monospace; color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
17
+ body > header .scope { margin-left: auto; font-size: 11px; padding: 1px 6px; border: 1px solid var(--line); border-radius: 4px; color: var(--muted); }
17
18
  #status { padding: 4px 10px; color: var(--muted); font-size: 12px; border-bottom: 1px solid var(--line); }
18
19
  #status:empty { display: none; }
19
20
  main { flex: 1; min-height: 0; display: flex; }
@@ -34,11 +35,14 @@
34
35
  button.action:disabled { opacity: .5; cursor: default; }
35
36
  label.action input { display: none; }
36
37
  /* supercode-ui follows this page's dark scheme; its composer spans the page instead of its 420px default. */
37
- #session { flex: 1; min-width: 0; min-height: 0; display: flex; --scui-color-scheme: dark; }
38
+ #session { flex: 1; min-width: 0; min-height: 0; display: flex; }
38
39
  #session .session-view { flex: 1; min-width: 0; display: flex; flex-direction: column; }
39
40
  #session .session-view > section { flex: 1; min-height: 0; overflow: auto; }
40
41
  #session .session-composer { border-top: 1px solid var(--line); padding: 8px; --scui-width: 100%; --scui-height: auto; }
41
42
  #session .session-composer > * { flex: 1; min-width: 0; }
43
+ /* The host's header carries the session's details when it hides this one. */
44
+ #session.header-hidden .scui-team-transcript > header { display: none; }
45
+ body:has(#session:not([hidden])) { background: var(--scui-bg, var(--volter-surface-page, var(--bg))); color: var(--scui-fg, var(--text)); }
42
46
  [hidden] { display: none !important; }
43
47
  </style>
44
48
  </head>
package/viewer/viewer.mjs CHANGED
@@ -10,13 +10,32 @@ const $ = (id) => document.getElementById(id);
10
10
  const decoder = new TextDecoder();
11
11
  const status = (text) => { $('status').textContent = text; };
12
12
 
13
- let token = new URLSearchParams(location.hash.slice(1)).get('token');
13
+ const fragment = new URLSearchParams(location.hash.slice(1));
14
+ let token = fragment.get('token');
14
15
  history.replaceState(null, '', location.pathname);
15
16
  let waiting = null;
16
17
 
17
18
  const embedded = window.parent !== window;
18
19
  const ask = (type) => { if (embedded) window.parent.postMessage({ type }, '*'); };
20
+ // The host's style, as supercode-ui is themed: its `--volter-*` brand tokens and `--scui-*` component tokens, set on
21
+ // the session's root (every component reads them from an ancestor). Nothing else is taken from the host.
22
+ function applyTheme(properties) {
23
+ if (!properties || typeof properties !== 'object') return;
24
+ for (const [name, value] of Object.entries(properties)) {
25
+ if (!/^--(volter|scui)-[a-z0-9-]+$/.test(name) || typeof value !== 'string' || value.length > 400) continue;
26
+ // on the page (its background) and on the session root (which sets its own scheme default)
27
+ for (const element of [document.documentElement, $('session')]) element.style.setProperty(name, value);
28
+ }
29
+ }
30
+ // The session header is the host's to show: framed, it is hidden unless the host asks for it (the host's own header
31
+ // carries the session's details, which this page posts to it as `viewer-session`).
32
+ function showHeader(shown) { $('session').classList.toggle('header-hidden', !shown); }
33
+ try { applyTheme(JSON.parse(atob((fragment.get('theme') ?? '').replaceAll('-', '+').replaceAll('_', '/')) || 'null')); } catch { /* no theme */ }
34
+ showHeader(fragment.has('header') ? fragment.get('header') !== 'hidden' : !embedded);
35
+
19
36
  window.addEventListener('message', (event) => {
37
+ if (event.data?.type === 'viewer-theme') { applyTheme(event.data.properties); return; }
38
+ if (event.data?.type === 'viewer-header') { showHeader(!event.data.hidden); return; }
20
39
  if (event.data?.type !== 'delegated-token' || typeof event.data.token !== 'string') return;
21
40
  token = event.data.token;
22
41
  if (waiting) { const resume = waiting; waiting = null; resume(); }
@@ -86,7 +105,18 @@ async function showSession(session, control) {
86
105
  },
87
106
  });
88
107
  const headers = async () => ({ Authorization: `Bearer ${await freshToken()}` });
89
- const apply = (body) => view.update({ descriptor: body.descriptor, messages: body.messages.map((message) => message.value), members: body.names?.members ?? [], machines: body.names?.machines ?? [], status: 'ready', error: null });
108
+ const HARNESSES = { 'claude-code': 'Claude Code', codex: 'Codex', gemini: 'Gemini CLI', goose: 'Goose', opencode: 'OpenCode', pi: 'Pi', grok: 'Grok' };
109
+ const apply = (body) => {
110
+ view.update({ descriptor: body.descriptor, messages: body.messages.map((message) => message.value), members: body.names?.members ?? [], machines: body.names?.machines ?? [], status: 'ready', error: null });
111
+ // What the host's own header shows about this session: who owns it, what runs it, where, and what this viewer may do.
112
+ const descriptor = body.descriptor ?? {};
113
+ const owner = (body.names?.members ?? []).find((member) => member.principal?.id === descriptor.user?.principal_id)?.principal?.display_name ?? descriptor.user?.reported_user ?? null;
114
+ if (embedded) window.parent.postMessage({ type: 'viewer-session', session: {
115
+ id: descriptor.id ?? session.id, title: descriptor.title ?? null, harness: descriptor.harness ?? null, harnessLabel: HARNESSES[descriptor.harness] ?? descriptor.harness ?? null,
116
+ owner, machines: (descriptor.machine_ids ?? []).map((id) => (body.names?.machines ?? []).find((machine) => machine.id === id)?.name ?? id),
117
+ scope: control ? 'control' : 'view', working: body.working ?? null,
118
+ } }, '*');
119
+ };
90
120
  let reading = null;
91
121
  const read = () => (reading ??= (async () => {
92
122
  try {