maqcli 0.8.0 → 0.9.0

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.
@@ -1,737 +1,633 @@
1
1
  /**
2
- * webui.ts — the self-contained control surface the daemon serves at `/app`
3
- * (and `/`). Zero build step, zero dependencies: one HTML document with
4
- * inline CSS + vanilla JS. It renders the SAME normalized event stream the
2
+ * webui.ts — the self-contained "Megalodon" control surface the daemon serves
3
+ * at `/app` (and `/`). Zero build step, zero dependencies: one HTML document
4
+ * with inline CSS + vanilla JS. It renders the SAME normalized event stream the
5
5
  * mobile app consumes, so there is no second source of truth.
6
6
  *
7
- * Design system: dark OLED canvas, two elevation surfaces, one signal accent
8
- * (teal "alive/running", not the generic AI purple/blue gradient). Type:
9
- * Space Grotesk for the brand mark + section labels (used sparingly), Inter
10
- * for UI/body, JetBrains Mono for the console/data surfaces. Signature
11
- * element: a live depth-of-work TIMELINE phases render as connected nodes
12
- * that fill in as work progresses, with orchestration rounds (parallel/loop/
13
- * safe) nesting as a sub-rail beneath the active phase — replacing static
14
- * chips with something that actually shows depth of work over time.
7
+ * This is a from-spec rebuild (maq_cli_ui_spec Phases 4-6). The recommended
8
+ * SPA stack (React + Monaco + Framer + react-resizable-panels) is deliberately
9
+ * NOT used: the daemon ships one static asset with no build pipeline, so the
10
+ * same UX is delivered dependency-free
11
+ * - real drag-resizable 3-panel shell (custom pointer-drag splitters)
12
+ * - universal preview: JSON tree / code+line-numbers / markdown / sandboxed
13
+ * HTML / images (replaces Monaco + react-markdown + react-json-view)
14
+ * - send button with a toggle-up mode picker popover (Single/Parallel/Loop/
15
+ * Safe), mode icon on the button face, selection persisted
16
+ * - orchestration visualizations derived from the event stream (parallel
17
+ * round dashboard / loop progress / safe split→merge→validate rail)
18
+ * - custom animated cursor follower + micro-interactions (CSS, not Framer)
15
19
  *
16
- * Layout: a named-grid-area app shell. Sidebar collapses to a drawer under
17
- * 900px; the preview panel becomes a full-screen sheet under 1200px; the
18
- * composer sticks to the bottom on narrow viewports. Motion is 150/220ms
19
- * ease-out and fully disabled under prefers-reduced-motion.
20
+ * Theme (Megalodon): deep matte black base, controlled blue-white accents (not
21
+ * neon), sparse red for status/danger, white primary text, no gradients except
22
+ * intentional depth cues. Everything references CSS custom-property tokens.
20
23
  *
21
24
  * Auth: the daemon requires a Bearer token for /v1/*. The page reads it from
22
- * ?token=… or a prompt, keeps it in memory only, and streams SSE via fetch +
23
- * ReadableStream (so the token rides in the Authorization header, never the
24
- * URL).
25
+ * ?key= / ?token= or a prompt, keeps it in memory only, and streams SSE via
26
+ * fetch + ReadableStream (token in the Authorization header, never the URL).
25
27
  */
26
28
  export function webuiHtml(version) {
27
- // NB: the client script uses only single/double quotes and string
28
- // concatenation — no backticks / ${} — so it lives safely inside this
29
- // template literal (a backtick here previously broke the TS build twice).
29
+ // NB: client script uses only single/double quotes + string concatenation —
30
+ // no backticks / ${} — so it lives safely inside this template literal.
30
31
  return `<!doctype html>
31
32
  <html lang="en"><head>
32
33
  <meta charset="utf-8">
33
34
  <meta name="viewport" content="width=device-width, initial-scale=1">
34
- <title>MAQ · control</title>
35
+ <title>MAQ · Megalodon</title>
35
36
  <style>
36
- @media (prefers-reduced-motion: no-preference){
37
- :root{ --dur-1:150ms; --dur-2:220ms; --ease:cubic-bezier(.2,.7,.2,1); }
38
- }
39
- @media (prefers-reduced-motion: reduce){
40
- :root{ --dur-1:0ms; --dur-2:0ms; --ease:linear; }
41
- *{ animation-duration:0ms !important; transition-duration:0ms !important; }
42
- }
37
+ @media (prefers-reduced-motion: no-preference){ :root{ --dur-1:140ms; --dur-2:240ms; --ease:cubic-bezier(.2,.8,.2,1) } }
38
+ @media (prefers-reduced-motion: reduce){ :root{ --dur-1:0ms; --dur-2:0ms; --ease:linear } *{animation-duration:0ms !important;transition-duration:0ms !important} }
43
39
  :root{
44
- /* --- color tokens --- */
45
- --bg:#05060a; --surface:#0d0f16; --surface-2:#12151f; --border:#1c2130; --border-soft:#161a26;
46
- --ink:#e8ebf3; --ink-dim:#8890a3; --ink-faint:#565d70;
47
- --accent:#4fd1c5; --accent-ink:#04211d; --accent-soft:rgba(79,209,197,.14); --accent-line:rgba(79,209,197,.4);
48
- --warn:#f2b84b; --warn-soft:rgba(242,184,75,.14);
49
- --danger:#f2555a; --danger-soft:rgba(242,85,90,.14);
50
- --ok:#34d399; --ok-soft:rgba(52,211,153,.14);
51
- /* --- type scale --- */
40
+ /* Megalodon: deep matte black + blue-white + sparse red */
41
+ --bg:#04070d; --surface:#090d16; --surface-2:#0e131f; --surface-3:#141a29;
42
+ --border:#1a2233; --border-strong:#28324a; --edge-soft:#131926;
43
+ --ink:#eef2fb; --ink-dim:#8a94ab; --ink-faint:#565f76;
44
+ --accent:#4d8dff; --accent-2:#93b8ff; --accent-ink:#04122e;
45
+ --accent-soft:rgba(77,141,255,.13); --accent-line:rgba(77,141,255,.42); --accent-glow:rgba(77,141,255,.28);
46
+ --danger:#ff4d5e; --danger-soft:rgba(255,77,94,.13);
47
+ --ok:#37d9a0; --ok-soft:rgba(55,217,160,.13);
48
+ --warn:#f5b544; --warn-soft:rgba(245,181,68,.13);
52
49
  --fs-11:.6875rem; --fs-12:.75rem; --fs-13:.8125rem; --fs-15:.9375rem; --fs-19:1.1875rem; --fs-24:1.5rem;
53
50
  --font-display:'Space Grotesk',ui-sans-serif,system-ui,sans-serif;
54
51
  --font-body:'Inter',ui-sans-serif,-apple-system,'Segoe UI',Roboto,sans-serif;
55
52
  --font-mono:'JetBrains Mono',ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
56
- /* --- spacing --- */
57
- --sp-1:4px; --sp-2:8px; --sp-3:12px; --sp-4:16px; --sp-5:20px; --sp-6:24px; --sp-8:32px;
58
- --radius:10px; --radius-sm:7px; --radius-lg:14px;
59
- }
60
- *{box-sizing:border-box}
61
- html,body{height:100%}
62
- body{
63
- margin:0; background:var(--bg); color:var(--ink); font-family:var(--font-body);
64
- font-size:var(--fs-13); line-height:1.55; -webkit-font-smoothing:antialiased;
53
+ --sp-1:4px; --sp-2:8px; --sp-3:12px; --sp-4:16px; --sp-5:20px; --sp-6:24px;
54
+ --r:10px; --r-sm:7px; --r-lg:14px;
55
+ --wl:266px; --wr:0px; --shadow:0 16px 48px rgba(0,0,0,.5);
65
56
  }
57
+ *{box-sizing:border-box} html,body{height:100%}
58
+ body{margin:0;background:var(--bg);color:var(--ink);font:var(--fs-13)/1.55 var(--font-body);-webkit-font-smoothing:antialiased}
59
+ @media (pointer:fine) and (prefers-reduced-motion: no-preference){ body{cursor:none} a,button,[role=button],input,textarea,select,.resize{cursor:none} }
66
60
  h1,h2,h3,h4{font-family:var(--font-display);font-weight:600;margin:0;letter-spacing:-.01em}
67
- .mono{font-family:var(--font-mono)}
68
- a{color:var(--accent)}
61
+ a{color:var(--accent-2)}
69
62
  button,input,select,textarea{font-family:inherit;color:inherit}
70
- :focus-visible{outline:2px solid var(--accent);outline-offset:2px;border-radius:4px}
63
+ :focus-visible{outline:2px solid var(--accent);outline-offset:2px;border-radius:5px}
71
64
  .sr-only{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}
72
- .eyebrow{font-size:var(--fs-11);text-transform:uppercase;letter-spacing:.09em;color:var(--ink-faint);font-weight:600}
73
-
74
- /* ---------------- app shell ---------------- */
75
- #app{
76
- display:grid;height:100vh;
77
- grid-template-columns:248px 1fr 0px;
78
- grid-template-rows:56px 1fr;
79
- grid-template-areas:"topbar topbar topbar" "sidebar main preview";
80
- transition:grid-template-columns var(--dur-2) var(--ease);
81
- }
82
- #app.preview-open{grid-template-columns:248px 1fr 380px}
83
- @media (max-width:1200px){
84
- #app.preview-open{grid-template-columns:248px 1fr}
85
- #app.preview-open #preview{position:fixed;inset:56px 0 0 0;z-index:50;width:100%}
86
- }
65
+ .eyebrow{font-size:var(--fs-11);text-transform:uppercase;letter-spacing:.1em;color:var(--ink-faint);font-weight:600}
66
+ ::-webkit-scrollbar{width:9px;height:9px} ::-webkit-scrollbar-thumb{background:var(--border-strong);border-radius:9px} ::-webkit-scrollbar-track{background:transparent}
67
+
68
+ /* custom cursor */
69
+ #cur,#curDot{position:fixed;top:0;left:0;pointer-events:none;z-index:9999;border-radius:50%;translate:-50% -50%}
70
+ #cur{width:26px;height:26px;border:1.5px solid var(--accent-line);transition:width var(--dur-1) var(--ease),height var(--dur-1) var(--ease),background var(--dur-1) var(--ease),border-color var(--dur-1) var(--ease)}
71
+ #cur.hot{width:40px;height:40px;background:var(--accent-soft);border-color:var(--accent)}
72
+ #cur.press{width:20px;height:20px}
73
+ #curDot{width:5px;height:5px;background:var(--accent)}
74
+ @media (pointer:coarse),(prefers-reduced-motion: reduce){ #cur,#curDot{display:none !important} }
75
+
76
+ /* app shell */
77
+ #app{display:grid;height:100vh;grid-template-rows:54px 1fr;
78
+ grid-template-columns:var(--wl) 5px 1fr 5px var(--wr);
79
+ grid-template-areas:"top top top top top" "side rl main rr prev"}
80
+ #app.no-prev{grid-template-columns:var(--wl) 5px 1fr 0px 0px}
87
81
  @media (max-width:900px){
88
- #app{grid-template-columns:1fr}
89
- #app{grid-template-areas:"topbar" "main"}
90
- #sidebar{position:fixed;inset:56px 0 0 0;z-index:60;width:280px;transform:translateX(-100%);
91
- transition:transform var(--dur-2) var(--ease)}
92
- #sidebar.open{transform:translateX(0)}
93
- #scrim{position:fixed;inset:56px 0 0 0;background:#000a;z-index:55;display:none}
94
- #scrim.show{display:block}
95
- #app.preview-open #preview{inset:56px 0 0 0}
82
+ #app{grid-template-columns:1fr;grid-template-areas:"top" "main"}
83
+ #side{position:fixed;inset:54px auto 0 0;width:284px;z-index:60;transform:translateX(-100%);transition:transform var(--dur-2) var(--ease)}
84
+ #side.open{transform:none} #rl,#rr,#prev{display:none}
85
+ #scrim{position:fixed;inset:54px 0 0 0;background:#000b;z-index:55;display:none} #scrim.on{display:block}
86
+ #app.prev-sheet #prev{display:flex;position:fixed;inset:54px 0 0 0;z-index:58;width:100%}
96
87
  }
97
88
 
98
- /* ---------------- topbar ---------------- */
99
- #topbar{
100
- grid-area:topbar;display:flex;align-items:center;gap:var(--sp-3);padding:0 var(--sp-4);
101
- border-bottom:1px solid var(--border);background:var(--surface);
102
- }
103
- .menu-btn{display:none}
104
- @media (max-width:900px){ .menu-btn{display:inline-flex} }
105
- .brand{display:flex;align-items:center;gap:var(--sp-2)}
106
- .brand-mark{width:22px;height:22px;border-radius:6px;background:var(--accent-soft);border:1px solid var(--accent-line);
107
- display:flex;align-items:center;justify-content:center;color:var(--accent);font-family:var(--font-display);
108
- font-weight:700;font-size:12px}
109
- .brand h1{font-size:var(--fs-15)}
110
- .status-dot{width:7px;height:7px;border-radius:50%;background:var(--ink-faint);flex:none}
111
- .status-dot.on{background:var(--ok);box-shadow:0 0 0 3px var(--ok-soft)}
89
+ /* topbar */
90
+ #top{grid-area:top;display:flex;align-items:center;gap:var(--sp-3);padding:0 var(--sp-4);
91
+ border-bottom:1px solid var(--border);background:linear-gradient(180deg,var(--surface-2),var(--surface))}
92
+ .menu-b{display:none} @media(max-width:900px){.menu-b{display:inline-flex}}
93
+ .brand{display:flex;align-items:center;gap:9px}
94
+ .fin{width:26px;height:26px;color:var(--accent)} .fin svg{width:100%;height:100%}
95
+ .brand h1{font-size:var(--fs-15);letter-spacing:.14em}
96
+ .brand .q{color:var(--danger)}
97
+ .live{display:flex;align-items:center;gap:6px;font-size:var(--fs-11);color:var(--ink-faint)}
98
+ .dot{width:7px;height:7px;border-radius:50%;background:var(--ink-faint)}
99
+ .dot.on{background:var(--ok);box-shadow:0 0 0 3px var(--ok-soft)}
112
100
  .grow{flex:1}
113
- .topbar-nav{display:flex;gap:var(--sp-1);flex-wrap:nowrap;overflow-x:auto;scrollbar-width:none}
114
- .topbar-nav::-webkit-scrollbar{display:none}
115
- .tbtn{
116
- background:transparent;color:var(--ink-dim);border:1px solid transparent;border-radius:var(--radius-sm);
117
- padding:6px 11px;cursor:pointer;font-size:var(--fs-12);font-weight:500;white-space:nowrap;
118
- display:inline-flex;align-items:center;gap:6px;transition:background var(--dur-1) var(--ease),color var(--dur-1) var(--ease);
119
- }
120
- .tbtn:hover{background:var(--surface-2);color:var(--ink)}
121
- .tbtn.accent{color:var(--accent)}
122
- .tbtn svg{width:14px;height:14px;flex:none}
123
-
124
- /* ---------------- sidebar ---------------- */
125
- #sidebar{grid-area:sidebar;background:var(--surface);border-right:1px solid var(--border);overflow-y:auto;
126
- display:flex;flex-direction:column}
127
- .side-sec{padding:var(--sp-3) var(--sp-4) var(--sp-2)}
128
- .side-sec-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:var(--sp-1)}
129
- .count-badge{background:var(--surface-2);border:1px solid var(--border);border-radius:999px;color:var(--ink-dim);
130
- font-size:10px;padding:1px 7px;font-weight:600}
131
- .count-badge.alert{background:var(--warn-soft);border-color:var(--warn);color:var(--warn)}
132
- .list{display:flex;flex-direction:column}
133
- .row{padding:9px var(--sp-4);border-bottom:1px solid var(--border-soft);cursor:pointer;
134
- display:flex;flex-direction:column;gap:3px;transition:background var(--dur-1) var(--ease)}
135
- .row:hover{background:var(--surface-2)}
136
- .row.active{background:var(--surface-2);box-shadow:inset 3px 0 0 var(--accent)}
137
- .row-title{font-size:var(--fs-13);color:var(--ink);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
138
- .row-meta{font-size:var(--fs-11);color:var(--ink-faint);display:flex;align-items:center;gap:6px;flex-wrap:wrap}
139
- .tag{border:1px solid var(--border);border-radius:999px;padding:0 6px;font-size:10px;font-weight:600}
140
- .tag.running{color:var(--accent);border-color:var(--accent-line)}
141
- .tag.done{color:var(--ok);border-color:var(--ok)}
142
- .tag.error,.tag.cancelled{color:var(--danger);border-color:var(--danger)}
143
- .tag.paused{color:var(--warn);border-color:var(--warn)}
144
- .empty-state{padding:var(--sp-6) var(--sp-4);text-align:center;color:var(--ink-faint);font-size:var(--fs-12)}
145
- .empty-state svg{width:28px;height:28px;color:var(--ink-faint);margin-bottom:var(--sp-2);opacity:.6}
146
- .skeleton{background:linear-gradient(90deg,var(--surface-2) 25%,var(--border) 37%,var(--surface-2) 63%);
147
- background-size:400% 100%;animation:shimmer 1.4s ease infinite;border-radius:6px;height:13px;margin:4px var(--sp-4)}
148
- @keyframes shimmer{0%{background-position:100% 0}100%{background-position:0 0}}
149
- @media (prefers-reduced-motion: reduce){ .skeleton{animation:none;opacity:.5} }
150
-
151
- /* ---------------- main / composer ---------------- */
101
+ .tnav{display:flex;gap:3px;overflow-x:auto;scrollbar-width:none} .tnav::-webkit-scrollbar{display:none}
102
+ .tb{background:transparent;color:var(--ink-dim);border:1px solid transparent;border-radius:var(--r-sm);
103
+ padding:6px 11px;font-size:var(--fs-12);font-weight:500;white-space:nowrap;display:inline-flex;align-items:center;gap:6px;
104
+ transition:background var(--dur-1) var(--ease),color var(--dur-1) var(--ease),transform var(--dur-1) var(--ease)}
105
+ .tb:hover{background:var(--surface-3);color:var(--ink)} .tb:active{transform:scale(.96)}
106
+ .tb.accent{color:var(--accent-2)} .tb svg{width:14px;height:14px;flex:none}
107
+ .kbd{border:1px solid var(--border-strong);border-radius:4px;padding:0 5px;font:10px var(--font-mono);color:var(--ink-faint)}
108
+
109
+ /* resize handles */
110
+ .resize{background:transparent;position:relative} .resize::after{content:'';position:absolute;inset:0 2px;border-radius:3px;transition:background var(--dur-1) var(--ease)}
111
+ .resize:hover::after,.resize.drag::after{background:var(--accent-line)}
112
+ @media(max-width:900px){.resize{display:none}}
113
+
114
+ /* sidebar */
115
+ #side{grid-area:side;background:var(--surface);border-right:1px solid var(--border);overflow-y:auto;display:flex;flex-direction:column}
116
+ .s-sec{padding:var(--sp-3) var(--sp-4) var(--sp-2)} .s-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:6px}
117
+ .cbadge{background:var(--surface-3);border:1px solid var(--border);border-radius:999px;color:var(--ink-dim);font-size:10px;padding:1px 7px;font-weight:600}
118
+ .cbadge.alert{background:var(--warn-soft);border-color:var(--warn);color:var(--warn)}
119
+ .grp-label{font-size:10px;color:var(--ink-faint);text-transform:uppercase;letter-spacing:.08em;padding:8px var(--sp-4) 3px}
120
+ .row{padding:9px var(--sp-4);border-bottom:1px solid var(--edge-soft);display:flex;flex-direction:column;gap:3px;
121
+ transition:background var(--dur-1) var(--ease)}
122
+ .row[tabindex]{cursor:pointer} .row:hover{background:var(--surface-2)} .row.active{background:var(--surface-3);box-shadow:inset 3px 0 0 var(--accent)}
123
+ .row-t{font-size:var(--fs-13);color:var(--ink);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
124
+ .row-m{font-size:var(--fs-11);color:var(--ink-faint);display:flex;align-items:center;gap:7px;flex-wrap:wrap}
125
+ .tag{border:1px solid var(--border);border-radius:999px;padding:0 7px;font-size:10px;font-weight:600}
126
+ .tag.running{color:var(--accent-2);border-color:var(--accent-line)} .tag.done{color:var(--ok);border-color:var(--ok)}
127
+ .tag.error,.tag.cancelled{color:var(--danger);border-color:var(--danger)} .tag.paused{color:var(--warn);border-color:var(--warn)}
128
+ .empty{padding:var(--sp-6) var(--sp-4);text-align:center;color:var(--ink-faint);font-size:var(--fs-12)}
129
+ .empty svg{width:26px;height:26px;opacity:.5;margin-bottom:6px}
130
+ .skel{background:linear-gradient(90deg,var(--surface-2) 25%,var(--surface-3) 37%,var(--surface-2) 63%);background-size:400% 100%;
131
+ animation:sh 1.4s ease infinite;border-radius:6px;height:12px;margin:6px var(--sp-4)}
132
+ @keyframes sh{0%{background-position:100% 0}100%{background-position:0 0}}
133
+ @media(prefers-reduced-motion:reduce){.skel{animation:none;opacity:.5}}
134
+
135
+ /* main */
152
136
  #main{grid-area:main;display:flex;flex-direction:column;min-width:0;background:var(--bg)}
153
137
  #composer{padding:var(--sp-4) var(--sp-5);border-bottom:1px solid var(--border)}
154
- .mode-rail{display:grid;grid-template-columns:repeat(4,1fr);gap:var(--sp-2);margin-bottom:var(--sp-3)}
155
- @media (max-width:640px){ .mode-rail{grid-template-columns:repeat(2,1fr)} }
156
- .mode-card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);
157
- padding:var(--sp-2) var(--sp-3);cursor:pointer;text-align:left;transition:border-color var(--dur-1) var(--ease),
158
- background var(--dur-1) var(--ease)}
159
- .mode-card:hover{border-color:var(--ink-faint)}
160
- .mode-card.sel{border-color:var(--accent);background:var(--accent-soft)}
161
- .mode-card h4{font-size:var(--fs-12);color:var(--ink);font-family:var(--font-body);font-weight:600}
162
- .mode-card p{margin:2px 0 0;font-size:var(--fs-11);color:var(--ink-faint);line-height:1.4}
163
- .mode-card.sel h4{color:var(--accent)}
164
- .composer-row{display:flex;gap:var(--sp-2);align-items:flex-end}
165
- #goal{
166
- flex:1;resize:vertical;min-height:46px;max-height:180px;background:var(--surface);color:var(--ink);
167
- border:1px solid var(--border);border-radius:var(--radius);padding:11px 13px;font:var(--fs-13)/1.5 var(--font-body);
168
- }
169
- #goal::placeholder{color:var(--ink-faint)}
170
- #goal:focus{outline:none;border-color:var(--accent)}
171
- .send-btn{
172
- background:var(--accent);color:var(--accent-ink);font-weight:700;border:none;border-radius:var(--radius);
173
- padding:12px 18px;cursor:pointer;font-size:var(--fs-13);display:inline-flex;align-items:center;gap:7px;
174
- transition:filter var(--dur-1) var(--ease)
175
- }
176
- .send-btn:hover{filter:brightness(1.08)}
177
- .send-btn:disabled{opacity:.45;cursor:not-allowed;filter:none}
178
- .send-btn svg{width:14px;height:14px}
179
- .opts-row{display:flex;gap:var(--sp-4);align-items:center;margin-top:var(--sp-3);flex-wrap:wrap}
180
- .opt{display:flex;align-items:center;gap:6px;color:var(--ink-dim);font-size:var(--fs-12)}
181
- .opt select,.opt input[type=number]{background:var(--surface);color:var(--ink);border:1px solid var(--border);
182
- border-radius:6px;padding:3px 7px;font-size:var(--fs-12)}
183
- .opt input[type=checkbox]{accent-color:var(--accent);width:14px;height:14px}
184
- .kbd-hint{color:var(--ink-faint);font-size:var(--fs-11);margin-left:auto}
185
- .kbd-hint kbd{background:var(--surface-2);border:1px solid var(--border);border-radius:4px;padding:1px 5px;
186
- font-family:var(--font-mono);font-size:10px}
187
-
188
- /* ---------------- timeline (signature element) ---------------- */
138
+ .comp-row{display:flex;gap:var(--sp-2);align-items:flex-end}
139
+ #goal{flex:1;resize:none;height:48px;max-height:190px;background:var(--surface);color:var(--ink);border:1px solid var(--border);
140
+ border-radius:var(--r);padding:12px 14px;font:var(--fs-13)/1.5 var(--font-body);transition:border-color var(--dur-1) var(--ease)}
141
+ #goal::placeholder{color:var(--ink-faint)} #goal:focus{outline:none;border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-soft)}
142
+ /* split send button + toggle-up mode picker */
143
+ .send-wrap{position:relative;display:flex;flex:none}
144
+ .send{background:var(--accent);color:var(--accent-ink);font-weight:700;border:none;border-radius:var(--r) 0 0 var(--r);
145
+ padding:12px 15px 12px 16px;font-size:var(--fs-13);display:inline-flex;align-items:center;gap:8px;
146
+ transition:filter var(--dur-1) var(--ease),transform var(--dur-1) var(--ease)}
147
+ .send:hover{filter:brightness(1.09)} .send:active{transform:scale(.97)} .send:disabled{opacity:.45;filter:none}
148
+ .send .mi{width:15px;height:15px}
149
+ .mode-toggle{background:var(--accent);color:var(--accent-ink);border:none;border-left:1px solid rgba(4,18,46,.25);
150
+ border-radius:0 var(--r) var(--r) 0;padding:0 9px;display:inline-flex;align-items:center;transition:filter var(--dur-1) var(--ease)}
151
+ .mode-toggle:hover{filter:brightness(1.09)} .mode-toggle svg{width:14px;height:14px;transition:transform var(--dur-1) var(--ease)}
152
+ .mode-toggle.open svg{transform:rotate(180deg)}
153
+ .mode-pop{position:absolute;bottom:calc(100% + 8px);right:0;width:288px;background:var(--surface-2);border:1px solid var(--border-strong);
154
+ border-radius:var(--r-lg);box-shadow:var(--shadow);padding:6px;display:none;z-index:40;animation:pop var(--dur-2) var(--ease)}
155
+ .mode-pop.open{display:block}
156
+ @keyframes pop{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}}
157
+ .mopt{display:flex;gap:10px;padding:9px 10px;border-radius:9px;cursor:pointer;align-items:flex-start}
158
+ .mopt:hover,.mopt.sel{background:var(--surface-3)} .mopt.sel{box-shadow:inset 0 0 0 1px var(--accent-line)}
159
+ .mopt .ic{width:17px;height:17px;color:var(--ink-dim);flex:none;margin-top:1px} .mopt.sel .ic{color:var(--accent)}
160
+ .mopt h4{font-size:var(--fs-12);font-family:var(--font-body);color:var(--ink)} .mopt p{margin:2px 0 0;font-size:var(--fs-11);color:var(--ink-faint);line-height:1.4}
161
+ .opts{display:flex;gap:var(--sp-4);align-items:center;margin-top:var(--sp-3);flex-wrap:wrap;color:var(--ink-dim);font-size:var(--fs-12)}
162
+ .opts label{display:flex;align-items:center;gap:6px}
163
+ .opts select,.opts input[type=number]{background:var(--surface);color:var(--ink);border:1px solid var(--border);border-radius:6px;padding:3px 7px;font-size:var(--fs-12)}
164
+ .opts input[type=checkbox]{accent-color:var(--accent);width:14px;height:14px}
165
+ .hint{margin-left:auto;color:var(--ink-faint);font-size:var(--fs-11)}
166
+
167
+ /* timeline (signature) + orchestration viz */
189
168
  #timeline{padding:var(--sp-3) var(--sp-5);border-bottom:1px solid var(--border);overflow-x:auto}
190
- .track{display:flex;align-items:center;min-height:34px}
169
+ .track{display:flex;align-items:center;min-height:32px}
191
170
  .node{display:flex;align-items:center;flex:none}
192
- .node-dot{width:20px;height:20px;border-radius:50%;border:2px solid var(--border);background:var(--surface);
193
- display:flex;align-items:center;justify-content:center;flex:none;transition:border-color var(--dur-2) var(--ease),
194
- background var(--dur-2) var(--ease)}
195
- .node-dot svg{width:11px;height:11px;opacity:0}
196
- .node.done .node-dot{border-color:var(--ok);background:var(--ok-soft)}
197
- .node.done .node-dot svg{opacity:1;color:var(--ok)}
198
- .node.active .node-dot{border-color:var(--accent);background:var(--accent-soft);
199
- box-shadow:0 0 0 4px var(--accent-soft)}
200
- .node.active .node-dot{animation:pulse 1.6s ease-in-out infinite}
201
- @keyframes pulse{0%,100%{box-shadow:0 0 0 3px var(--accent-soft)}50%{box-shadow:0 0 0 7px transparent}}
171
+ .node-dot{width:20px;height:20px;border-radius:50%;border:2px solid var(--border-strong);background:var(--surface);display:flex;align-items:center;justify-content:center;flex:none;
172
+ transition:border-color var(--dur-2) var(--ease),background var(--dur-2) var(--ease)}
173
+ .node-dot svg{width:11px;height:11px;opacity:0} .node.done .node-dot{border-color:var(--ok);background:var(--ok-soft)} .node.done .node-dot svg{opacity:1;color:var(--ok)}
174
+ .node.active .node-dot{border-color:var(--accent);background:var(--accent-soft);animation:pulse 1.6s ease-in-out infinite}
175
+ @keyframes pulse{0%,100%{box-shadow:0 0 0 3px var(--accent-soft)}50%{box-shadow:0 0 0 8px transparent}}
202
176
  .node-label{font-size:var(--fs-12);color:var(--ink-faint);margin-left:7px;white-space:nowrap}
203
- .node.done .node-label{color:var(--ok)}
204
- .node.active .node-label{color:var(--accent);font-weight:600}
205
- .node-line{width:28px;height:1px;background:var(--border);flex:none;margin:0 4px}
206
- .node.done + .node-line{background:var(--ok)}
207
- .sub-rail{display:flex;align-items:center;gap:6px;margin-top:6px;margin-left:26px;flex-wrap:wrap}
208
- .sub-chip{font-size:10px;color:var(--accent);background:var(--accent-soft);border:1px solid var(--accent-line);
209
- border-radius:999px;padding:2px 8px;font-weight:600}
210
-
211
- /* ---------------- console ---------------- */
212
- #console{flex:1;overflow-y:auto;padding:var(--sp-3) var(--sp-5);display:flex;flex-direction:column;gap:6px}
213
- .ev-card{display:flex;gap:var(--sp-2);padding:7px 9px;border-radius:8px;border:1px solid transparent;
214
- font-size:var(--fs-12);animation:fade-in var(--dur-2) var(--ease)}
215
- @keyframes fade-in{from{opacity:0;transform:translateY(2px)}to{opacity:1;transform:translateY(0)}}
216
- @media (prefers-reduced-motion: reduce){ .ev-card{animation:none} }
217
- .ev-ic{width:16px;height:16px;flex:none;margin-top:1px}
218
- .ev-ic svg{width:100%;height:100%}
219
- .ev-body{flex:1;min-width:0}
220
- .ev-text{white-space:pre-wrap;word-break:break-word;font-family:var(--font-mono);font-size:11.5px;line-height:1.5}
221
- .ev-time{color:var(--ink-faint);font-size:10px;margin-left:7px}
222
- .c-accent{color:var(--accent)} .c-dim{color:var(--ink-faint)} .c-ok{color:var(--ok)}
223
- .c-danger{color:var(--danger)} .c-warn{color:var(--warn)}
224
- .ev-card.bg-danger{background:var(--danger-soft)} .ev-card.bg-warn{background:var(--warn-soft)}
225
-
226
- /* ---------------- preview panel ---------------- */
227
- #preview{grid-area:preview;background:var(--surface);border-left:1px solid var(--border);overflow-y:auto;min-width:0}
228
- .panel-head{display:flex;align-items:center;justify-content:space-between;padding:var(--sp-3) var(--sp-4);
229
- border-bottom:1px solid var(--border);position:sticky;top:0;background:var(--surface);z-index:2}
230
- .panel-head h3{font-size:var(--fs-13)}
231
- .icon-btn{background:transparent;border:1px solid var(--border);border-radius:7px;color:var(--ink-dim);
232
- cursor:pointer;padding:5px 9px;font-size:var(--fs-12);display:inline-flex;align-items:center;gap:5px}
233
- .icon-btn:hover{color:var(--ink);border-color:var(--ink-faint)}
234
-
235
- pre.data{white-space:pre-wrap;word-break:break-word;font-family:var(--font-mono);font-size:11.5px;color:var(--ink);
236
- padding:var(--sp-4);margin:0;line-height:1.6}
237
-
238
- /* ---------------- full-screen overlays ---------------- */
239
- .overlay{position:fixed;inset:0;background:var(--bg);z-index:100;display:none;flex-direction:column}
240
- .overlay.show{display:flex}
241
- .overlay-head{display:flex;align-items:center;gap:var(--sp-3);padding:var(--sp-3) var(--sp-5);
242
- border-bottom:1px solid var(--border);background:var(--surface)}
243
- .back-btn{background:var(--surface-2);border:1px solid var(--border);color:var(--ink);border-radius:var(--radius-sm);
244
- padding:7px 12px;cursor:pointer;font-size:var(--fs-12);display:inline-flex;align-items:center;gap:6px;font-weight:500}
245
- .back-btn:hover{border-color:var(--accent);color:var(--accent)}
246
- .overlay-body{flex:1;overflow-y:auto;padding:var(--sp-5)}
177
+ .node.done .node-label{color:var(--ok)} .node.active .node-label{color:var(--accent-2);font-weight:600}
178
+ .node-line{width:26px;height:1px;background:var(--border-strong);margin:0 4px} .node.done + .node-line{background:var(--ok)}
179
+ .sub-rail{display:flex;align-items:center;gap:6px;margin-top:8px;flex-wrap:wrap}
180
+ .sub-chip{font-size:10px;color:var(--accent-2);background:var(--accent-soft);border:1px solid var(--accent-line);border-radius:999px;padding:2px 9px;font-weight:600}
181
+ .sub-chip.merge{color:var(--warn);background:var(--warn-soft);border-color:var(--warn)}
182
+
183
+ /* console */
184
+ #console{flex:1;overflow-y:auto;padding:var(--sp-3) var(--sp-5);display:flex;flex-direction:column;gap:5px}
185
+ .ev{display:flex;gap:9px;padding:6px 9px;border-radius:8px;border-left:2px solid transparent;animation:fade var(--dur-2) var(--ease)}
186
+ @keyframes fade{from{opacity:0;transform:translateY(2px)}to{opacity:1;transform:none}}
187
+ @media(prefers-reduced-motion:reduce){.ev{animation:none}}
188
+ .ev .ic{width:15px;height:15px;flex:none;margin-top:1px} .ev .ic svg{width:100%;height:100%}
189
+ .ev .tx{white-space:pre-wrap;word-break:break-word;font:11.5px/1.55 var(--font-mono);flex:1}
190
+ .ev .t{color:var(--ink-faint);font-size:10px;margin-left:7px}
191
+ .c-accent{color:var(--accent-2)} .c-dim{color:var(--ink-dim)} .c-ok{color:var(--ok)} .c-danger{color:var(--danger)} .c-warn{color:var(--warn)}
192
+ .ev.bg-danger{background:var(--danger-soft);border-left-color:var(--danger)} .ev.bg-warn{background:var(--warn-soft);border-left-color:var(--warn)}
193
+
194
+ /* preview */
195
+ #prev{grid-area:prev;background:var(--surface);border-left:1px solid var(--border);overflow:hidden;min-width:0;display:flex;flex-direction:column}
196
+ .p-head{display:flex;align-items:center;gap:8px;padding:var(--sp-3) var(--sp-4);border-bottom:1px solid var(--border)}
197
+ .p-head h3{font-size:var(--fs-13);flex:1} .p-type{font-size:10px;color:var(--ink-faint);text-transform:uppercase;letter-spacing:.06em}
198
+ .ibtn{background:transparent;border:1px solid var(--border);border-radius:7px;color:var(--ink-dim);padding:5px 9px;font-size:var(--fs-12);display:inline-flex;align-items:center;gap:5px;transition:border-color var(--dur-1) var(--ease),color var(--dur-1) var(--ease)}
199
+ .ibtn:hover{color:var(--ink);border-color:var(--accent-line)}
200
+ #prevBody{flex:1;overflow:auto}
201
+ pre.code{margin:0;padding:0;font:11.5px/1.6 var(--font-mono);display:flex}
202
+ .gutter{color:var(--ink-faint);text-align:right;padding:var(--sp-3) 8px;user-select:none;border-right:1px solid var(--border);background:var(--surface-2)}
203
+ .codelines{padding:var(--sp-3) var(--sp-4);white-space:pre;overflow-x:auto;color:var(--ink);flex:1}
204
+ .md{padding:var(--sp-4);line-height:1.65} .md h1,.md h2,.md h3{margin:.8em 0 .3em} .md code{background:var(--surface-3);padding:1px 5px;border-radius:4px;font:12px var(--font-mono)}
205
+ .md pre{background:var(--surface-2);border:1px solid var(--border);border-radius:8px;padding:10px;overflow-x:auto;font:12px var(--font-mono)}
206
+ .md ul{padding-left:1.2em} .md a{color:var(--accent-2)}
207
+ .jt{padding:var(--sp-4);font:12px/1.6 var(--font-mono)} .jt .k{color:var(--accent-2)} .jt .s{color:var(--ok)} .jt .n{color:var(--warn)} .jt .b{color:var(--danger)}
208
+ .jt .tog{cursor:pointer;color:var(--ink-faint);user-select:none} .jt .nest{padding-left:16px;border-left:1px solid var(--edge-soft)}
209
+ .jt .col{display:none} .jt.open>.col{display:block}
210
+ iframe.htmlprev{width:100%;height:100%;border:none;background:#fff}
211
+
212
+ /* overlays */
213
+ .ov{position:fixed;inset:0;background:var(--bg);z-index:100;display:none;flex-direction:column}
214
+ .ov.show{display:flex}
215
+ .ov-head{display:flex;align-items:center;gap:var(--sp-3);padding:var(--sp-3) var(--sp-5);border-bottom:1px solid var(--border);background:var(--surface)}
216
+ .back{background:var(--surface-3);border:1px solid var(--border);color:var(--ink);border-radius:var(--r-sm);padding:7px 12px;font-size:var(--fs-12);font-weight:500;display:inline-flex;align-items:center;gap:6px;transition:border-color var(--dur-1) var(--ease),color var(--dur-1) var(--ease)}
217
+ .back:hover{border-color:var(--accent);color:var(--accent-2)}
218
+ .ov-body{flex:1;overflow-y:auto;padding:var(--sp-5)}
247
219
  .fgrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:var(--sp-3)}
248
- .fcard{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius-lg);padding:var(--sp-4);
249
- cursor:pointer;transition:border-color var(--dur-1) var(--ease),transform var(--dur-1) var(--ease);text-align:left}
250
- .fcard:hover{border-color:var(--accent-line);transform:translateY(-1px)}
251
- .fcard .eyebrow{color:var(--accent)}
252
- .fcard h4{font-size:var(--fs-13);margin:var(--sp-1) 0 4px;font-family:var(--font-body)}
253
- .fcard p{margin:0;font-size:var(--fs-12);color:var(--ink-dim);line-height:1.5}
254
-
255
- .req-card{padding:10px var(--sp-4);border-bottom:1px solid var(--border-soft);border-left:3px solid transparent}
256
- .req-card.destructive{border-left-color:var(--danger);background:var(--danger-soft)}
257
- .req-card.major{border-left-color:var(--warn);background:var(--warn-soft)}
258
- .req-action{font-size:var(--fs-12);color:var(--ink)}
259
- .req-reason{font-size:var(--fs-11);color:var(--ink-faint);margin:3px 0 8px}
260
- .req-btns{display:flex;gap:6px}
261
- .btn-sm{border:none;border-radius:6px;padding:5px 12px;cursor:pointer;font-size:11px;font-weight:600}
262
- .btn-approve{background:var(--ok);color:#04231a}
263
- .btn-deny{background:var(--danger);color:#2e0507}
264
-
265
- .sec-block{margin-bottom:var(--sp-6)}
266
- .sec-head{display:flex;align-items:center;gap:var(--sp-2);margin-bottom:var(--sp-2)}
267
- .sec-head h4{font-size:var(--fs-13);font-family:var(--font-body)}
268
- .sec-list{display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:6px;margin-bottom:var(--sp-2)}
269
- .sec-item{background:var(--surface-2);border:1px solid var(--border);border-radius:7px;padding:7px 10px;
270
- font-family:var(--font-mono);font-size:11.5px;color:var(--ink);word-break:break-all}
220
+ .fcard{background:var(--surface);border:1px solid var(--border);border-radius:var(--r-lg);padding:var(--sp-4);text-align:left;
221
+ transition:border-color var(--dur-1) var(--ease),transform var(--dur-1) var(--ease)}
222
+ .fcard:hover{border-color:var(--accent-line);transform:translateY(-2px)} .fcard:active{transform:none}
223
+ .fcard .eyebrow{color:var(--accent-2)} .fcard h4{font-size:var(--fs-13);margin:5px 0 4px;font-family:var(--font-body)} .fcard p{margin:0;font-size:var(--fs-12);color:var(--ink-dim);line-height:1.5}
224
+ .req{padding:10px var(--sp-4);border-bottom:1px solid var(--edge-soft);border-left:3px solid transparent}
225
+ .req.destructive{border-left-color:var(--danger);background:var(--danger-soft)} .req.major{border-left-color:var(--warn);background:var(--warn-soft)}
226
+ .req .a{font-size:var(--fs-12);color:var(--ink)} .req .r{font-size:var(--fs-11);color:var(--ink-faint);margin:3px 0 8px}
227
+ .req .btns{display:flex;gap:6px} .bs{border:none;border-radius:6px;padding:5px 12px;font-size:11px;font-weight:600} .ok-b{background:var(--ok);color:#04231a} .no-b{background:var(--danger);color:#2e0507}
228
+ .sec-blk{margin-bottom:var(--sp-6)} .sec-h{display:flex;align-items:center;gap:8px;margin-bottom:8px} .sec-h h4{font-size:var(--fs-13);font-family:var(--font-body)}
229
+ .sec-list{display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:6px;margin-bottom:8px}
230
+ .sec-i{background:var(--surface-2);border:1px solid var(--border);border-radius:7px;padding:7px 10px;font:11.5px var(--font-mono);color:var(--ink);word-break:break-all}
271
231
  .sec-note{color:var(--ink-faint);font-size:var(--fs-11);line-height:1.5}
272
- .posture-row{display:flex;gap:var(--sp-2);margin-bottom:var(--sp-5);flex-wrap:wrap}
273
- .posture-pill{display:inline-flex;align-items:center;gap:6px;background:var(--ok-soft);border:1px solid var(--ok);
274
- color:var(--ok);border-radius:999px;padding:5px 12px;font-size:var(--fs-12);font-weight:600}
275
- .posture-pill svg{width:12px;height:12px}
232
+ .posture{display:flex;gap:var(--sp-2);margin-bottom:var(--sp-5);flex-wrap:wrap}
233
+ .pill{display:inline-flex;align-items:center;gap:6px;background:var(--ok-soft);border:1px solid var(--ok);color:var(--ok);border-radius:999px;padding:5px 12px;font-size:var(--fs-12);font-weight:600}
234
+ .pill svg{width:12px;height:12px}
235
+ /* command palette */
236
+ .cmdk-bd{position:fixed;inset:0;background:rgba(2,4,9,.62);z-index:200;display:none;justify-content:center;padding-top:12vh}
237
+ .cmdk-bd.show{display:flex}
238
+ .cmdk{width:min(560px,92vw);height:max-content;background:var(--surface-2);border:1px solid var(--border-strong);border-radius:var(--r-lg);box-shadow:var(--shadow);overflow:hidden;animation:pop var(--dur-2) var(--ease)}
239
+ .cmdk-in{display:flex;align-items:center;gap:10px;padding:13px 16px;border-bottom:1px solid var(--border)} .cmdk-in svg{width:16px;height:16px;color:var(--ink-faint)}
240
+ #cmdkInput{flex:1;background:transparent;border:none;outline:none;color:var(--ink);font-size:var(--fs-15)} #cmdkInput::placeholder{color:var(--ink-faint)}
241
+ .cmdk-list{max-height:52vh;overflow-y:auto;padding:6px}
242
+ .cmdk-it{display:flex;align-items:center;gap:11px;padding:9px 10px;border-radius:8px;cursor:pointer} .cmdk-it.sel,.cmdk-it:hover{background:var(--surface-3)}
243
+ .cmdk-it .ic{width:15px;height:15px;color:var(--ink-faint);flex:none} .cmdk-it.sel .ic{color:var(--accent)}
244
+ .cmdk-it .m{flex:1;min-width:0} .cmdk-it .tt{font-size:var(--fs-13);color:var(--ink)} .cmdk-it .ss{font-size:var(--fs-11);color:var(--ink-faint)}
245
+ .cmdk-it .cat{font-size:10px;color:var(--ink-faint);text-transform:uppercase}
276
246
  </style></head>
277
247
  <body>
248
+ <div id="cur"></div><div id="curDot"></div>
278
249
  <div id="app">
279
- <header id="topbar">
280
- <button class="menu-btn icon-btn" id="menuBtn" aria-label="Open sidebar" onclick="toggleSidebar()">
281
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 6h18M3 12h18M3 18h18"/></svg>
282
- </button>
250
+ <header id="top">
251
+ <button class="menu-b ibtn" aria-label="Toggle sidebar" onclick="toggleSide()"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="16" height="16"><path d="M3 6h18M3 12h18M3 18h18"/></svg></button>
283
252
  <div class="brand">
284
- <span class="brand-mark" aria-hidden="true">M</span>
285
- <h1>MAQ</h1>
286
- <span class="status-dot" id="statusDot" role="status" aria-label="Disconnected"></span>
253
+ <span class="fin" aria-hidden="true"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M2 15c5 1 8-1 11-6 1 3 1 5 0 7 2 0 4-1 5-3 0 4-3 7-8 7-4 0-8-2-8-5zm11-7c-3 2-6 3-9 2 2-3 6-4 9-2z"/></svg></span>
254
+ <h1>MAQ<span class="q"> ▴</span></h1>
255
+ <span class="sr-only">version ${version}</span>
256
+ <span class="live"><span class="dot" id="dot"></span><span id="liveTxt">connecting</span></span>
287
257
  </div>
288
258
  <span class="grow"></span>
289
- <nav class="topbar-nav" aria-label="Tools">
290
- <button class="tbtn accent" onclick="openFeatures()">
291
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="7" height="7" rx="1"/><rect x="14" y="3" width="7" height="7" rx="1"/><rect x="3" y="14" width="7" height="7" rx="1"/><rect x="14" y="14" width="7" height="7" rx="1"/></svg>
292
- Features
293
- </button>
294
- <button class="tbtn" onclick="feat('agents')">Agents</button>
295
- <button class="tbtn" onclick="feat('connectivity')">Connectivity</button>
296
- <button class="tbtn" onclick="feat('doctor')">Doctor</button>
297
- <button class="tbtn" onclick="feat('models')">Models</button>
298
- <button class="tbtn" onclick="openSecurity()">
299
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 2 4 5v6c0 5 3.5 8.5 8 11 4.5-2.5 8-6 8-11V5l-8-3z"/></svg>
300
- Security
301
- </button>
302
- <button class="tbtn" id="previewToggle" onclick="togglePreview()">Preview</button>
259
+ <nav class="tnav" aria-label="Tools">
260
+ <button class="tb" onclick="openPalette()"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="7"/><path d="M21 21l-4.3-4.3"/></svg>Search<span class="kbd">⌘K</span></button>
261
+ <button class="tb accent" onclick="openFeatures()"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="7" height="7" rx="1"/><rect x="14" y="3" width="7" height="7" rx="1"/><rect x="3" y="14" width="7" height="7" rx="1"/><rect x="14" y="14" width="7" height="7" rx="1"/></svg>Features</button>
262
+ <button class="tb" onclick="openSecurity()"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 2 4 5v6c0 5 3.5 8.5 8 11 4.5-2.5 8-6 8-11V5l-8-3z"/></svg>Security</button>
263
+ <button class="tb" id="prevToggle" onclick="togglePreview()"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="4" width="18" height="16" rx="2"/><path d="M15 4v16"/></svg>Preview</button>
303
264
  </nav>
304
265
  </header>
266
+ <div id="scrim" onclick="toggleSide()"></div>
305
267
 
306
- <div id="scrim" onclick="toggleSidebar()"></div>
307
-
308
- <aside id="sidebar" aria-label="Sessions and agents">
309
- <div class="side-sec">
310
- <div class="side-sec-head">
311
- <span class="eyebrow">Request box</span>
312
- <span class="count-badge" id="reqCount">0</span>
313
- </div>
314
- <div class="list" id="requests" role="region" aria-label="Pending permission requests"></div>
315
- </div>
316
- <div class="side-sec">
317
- <div class="side-sec-head"><span class="eyebrow">Sessions</span></div>
318
- <div class="list" id="sessions" role="region" aria-label="Session history"></div>
319
- </div>
320
- <div class="side-sec">
321
- <div class="side-sec-head"><span class="eyebrow">Agents</span></div>
322
- <div class="list" id="agents" role="region" aria-label="Detected worker agents"></div>
323
- </div>
268
+ <aside id="side" aria-label="History">
269
+ <div class="s-sec"><div class="s-head"><span class="eyebrow">Request box</span><span class="cbadge" id="reqCount">0</span></div><div id="requests" role="region" aria-label="Pending requests"></div></div>
270
+ <div class="s-sec"><div class="s-head"><span class="eyebrow">History</span></div></div>
271
+ <div id="sessions" role="region" aria-label="Session history"></div>
272
+ <div class="s-sec"><div class="s-head"><span class="eyebrow">Agents</span></div><div id="agents" role="region" aria-label="Agents"></div></div>
324
273
  </aside>
325
274
 
275
+ <div class="resize" id="rl" role="separator" aria-label="Resize sidebar" aria-orientation="vertical"></div>
276
+
326
277
  <main id="main">
327
278
  <div id="composer">
328
- <div class="mode-rail" id="modes" role="radiogroup" aria-label="Execution mode"></div>
329
- <div class="composer-row">
279
+ <div class="comp-row">
330
280
  <label class="sr-only" for="goal">Goal</label>
331
- <textarea id="goal" placeholder="Describe a goal — e.g. add pagination to the users endpoint and add tests"></textarea>
332
- <button class="send-btn" id="send" onclick="startTask()">
333
- Send
334
- <svg viewBox="0 0 24 24" fill="currentColor"><path d="M3 12l18-9-6 9 6 9-18-9z"/></svg>
335
- </button>
281
+ <textarea id="goal" placeholder="Describe a goal — the Headroom master decomposes and dispatches it"></textarea>
282
+ <div class="send-wrap">
283
+ <button class="send" id="send" onclick="startTask()"><span class="mi" id="sendIcon"></span><span id="sendLabel">Send</span></button>
284
+ <button class="mode-toggle" id="modeToggle" aria-haspopup="menu" aria-expanded="false" aria-label="Choose execution mode" onclick="togglePop(event)"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4"><path d="M6 15l6-6 6 6"/></svg></button>
285
+ <div class="mode-pop" id="modePop" role="menu" aria-label="Execution mode"></div>
286
+ </div>
336
287
  </div>
337
- <div class="opts-row">
338
- <label class="opt">target
339
- <select id="target">
340
- <option value="none">none (raw)</option><option value="auto">auto</option>
341
- <option value="claude-code">claude-code</option><option value="codex">codex</option>
342
- <option value="gemini">gemini</option>
343
- </select>
344
- </label>
345
- <label class="opt"><input type="checkbox" id="dry"> dry-run</label>
346
- <label class="opt">concurrency <input type="number" id="conc" value="4" min="1" max="16" style="width:52px"></label>
347
- <span class="kbd-hint"><kbd>Ctrl</kbd>+<kbd>Enter</kbd> to send</span>
288
+ <div class="opts">
289
+ <label>target <select id="target"><option value="none">none (raw)</option><option value="auto">auto</option><option value="claude-code">claude-code</option><option value="codex">codex</option><option value="gemini">gemini</option></select></label>
290
+ <label><input type="checkbox" id="dry"> dry-run</label>
291
+ <label>concurrency <input type="number" id="conc" value="4" min="1" max="16" style="width:52px"></label>
292
+ <span class="hint"><span class="kbd">⌘↵</span> to send</span>
348
293
  </div>
349
294
  </div>
350
- <div id="timeline" aria-label="Progress timeline"><div class="track" id="track"></div><div id="subrail"></div></div>
295
+ <div id="timeline" aria-label="Progress"><div class="track" id="track"></div><div id="subrail"></div></div>
351
296
  <div id="console" role="log" aria-live="polite" aria-label="Live activity"></div>
352
297
  </main>
353
298
 
354
- <aside id="preview" aria-label="Preview panel">
355
- <div class="panel-head"><h3>Preview</h3><button class="icon-btn" onclick="togglePreview()">Close</button></div>
356
- <div id="previewBody"></div>
299
+ <div class="resize" id="rr" role="separator" aria-label="Resize preview" aria-orientation="vertical"></div>
300
+
301
+ <aside id="prev" aria-label="Preview">
302
+ <div class="p-head"><h3>Preview</h3><span class="p-type" id="prevType"></span><button class="ibtn" onclick="togglePreview()">Close</button></div>
303
+ <div id="prevBody"></div>
357
304
  </aside>
358
305
  </div>
359
306
 
360
- <div class="overlay" id="features" role="dialog" aria-label="All features">
361
- <div class="overlay-head">
362
- <button class="back-btn" onclick="closeFeatures()">
363
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="14" height="14"><path d="M19 12H5M12 19l-7-7 7-7"/></svg>
364
- Back to home
365
- </button>
366
- <h3 style="margin-left:4px">All features</h3>
367
- <span class="grow"></span>
368
- <span class="eyebrow" style="text-transform:none;letter-spacing:0">everything MAQ can do — click to run</span>
369
- </div>
370
- <div class="overlay-body"><div class="fgrid" id="featureGrid"></div></div>
307
+ <div class="ov" id="features" role="dialog" aria-label="All features">
308
+ <div class="ov-head"><button class="back" onclick="closeFeatures()"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="14" height="14"><path d="M19 12H5M12 19l-7-7 7-7"/></svg>Back to home</button><h3 style="margin-left:4px">All features</h3><span class="grow"></span><span class="eyebrow" style="text-transform:none;letter-spacing:0">everything MAQ can do — click to run</span></div>
309
+ <div class="ov-body"><div class="fgrid" id="featureGrid"></div></div>
371
310
  </div>
372
-
373
- <div class="overlay" id="security" role="dialog" aria-label="Security rules">
374
- <div class="overlay-head">
375
- <button class="back-btn" onclick="closeSecurity()">
376
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="14" height="14"><path d="M19 12H5M12 19l-7-7 7-7"/></svg>
377
- Back to home
378
- </button>
379
- <h3 style="margin-left:4px">Security rules</h3>
380
- <span class="grow"></span>
381
- <span class="eyebrow" style="text-transform:none;letter-spacing:0">enforced at the code level — same rules the terminal ('maq security') shows and enforces</span>
382
- </div>
383
- <div class="overlay-body" id="securityBody"></div>
311
+ <div class="ov" id="security" role="dialog" aria-label="Security rules">
312
+ <div class="ov-head"><button class="back" onclick="closeSecurity()"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="14" height="14"><path d="M19 12H5M12 19l-7-7 7-7"/></svg>Back to home</button><h3 style="margin-left:4px">Security rules</h3><span class="grow"></span><span class="eyebrow" style="text-transform:none;letter-spacing:0">enforced at the code level — same rules the terminal ('maq security') shows</span></div>
313
+ <div class="ov-body" id="securityBody"></div>
384
314
  </div>
385
-
386
- <script>
315
+ <div class="cmdk-bd" id="cmdk" role="dialog" aria-label="Command palette">
316
+ <div class="cmdk"><div class="cmdk-in"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="7"/><path d="M21 21l-4.3-4.3"/></svg><input id="cmdkInput" placeholder="Search features and sessions…" autocomplete="off"><span class="kbd">esc</span></div><div class="cmdk-list" id="cmdkList"></div></div>
317
+ </div>
318
+ <script>${CLIENT_JS}</script>
319
+ </body></html>`;
320
+ }
321
+ const CLIENT_JS = String.raw `
387
322
  "use strict";
388
- var base = location.origin;
389
- var token = new URLSearchParams(location.search).get('token') || sessionStorage.getItem('maqToken') || '';
390
- if(!token){ token = prompt('Daemon auth token (printed by: maq serve — or the launcher):') || ''; }
391
- sessionStorage.setItem('maqToken', token);
323
+ var base=location.origin;
324
+ var qp=new URLSearchParams(location.search);
325
+ var token=qp.get('key')||qp.get('token')||sessionStorage.getItem('maqToken')||'';
326
+ if(!token){ token=prompt('Session auth key (the 9-digit key printed by: maq serve):')||''; }
327
+ sessionStorage.setItem('maqToken',token);
392
328
 
393
- var ICONS = {
329
+ var I={
394
330
  play:'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M8 5v14l11-7z"/></svg>',
395
- chevron:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 18l6-6-6-6"/></svg>',
331
+ single:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M5 12h14M13 6l6 6-6 6"/></svg>',
332
+ parallel:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 7h6M4 12h6M4 17h6M14 12h6M10 7c4 0 0 5 4 5M10 17c4 0 0-5 4-5"/></svg>',
333
+ loop:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 12a8 8 0 0 1 14-5M20 12a8 8 0 0 1-14 5"/><path d="M18 3v4h-4M6 21v-4h4"/></svg>',
334
+ safe:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 3v4M6 21v-6M18 21v-6M12 15v-4M6 15a3 3 0 0 1 0-6M18 15a3 3 0 0 0 0-6M6 9h12"/></svg>',
335
+ chev:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 18l6-6-6-6"/></svg>',
396
336
  dot:'<svg viewBox="0 0 24 24" fill="currentColor"><circle cx="12" cy="12" r="4"/></svg>',
397
337
  check:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M5 13l4 4L19 7"/></svg>',
398
338
  warn:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 9v4M12 17h.01M10.3 3.9L2.7 17a2 2 0 0 0 1.7 3h15.2a2 2 0 0 0 1.7-3L13.7 3.9a2 2 0 0 0-3.4 0z"/></svg>',
399
339
  x:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M18 6L6 18M6 6l12 12"/></svg>',
400
- tool:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"/></svg>',
340
+ tool:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.8-3.8a6 6 0 0 1-7.9 7.9l-6.9 6.9a2.1 2.1 0 0 1-3-3l6.9-6.9a6 6 0 0 1 7.9-7.9z"/></svg>',
401
341
  flag:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 22V4M4 4h11l-1.5 4L15 12H4"/></svg>',
402
342
  verified:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 2l3 1.5 3.3-.3 1 3.1 2.7 2-1.3 3 1.3 3-2.7 2-1 3.1-3.3-.3L12 22l-3-1.5-3.3.3-1-3.1-2.7-2 1.3-3-1.3-3 2.7-2 1-3.1 3.3.3z"/><path d="M9 12l2 2 4-4"/></svg>',
403
343
  circle:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="9"/></svg>',
404
- empty:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="3" y="7" width="18" height="13" rx="2"/><path d="M3 7l3-4h12l3 4"/></svg>'
344
+ box:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="3" y="7" width="18" height="13" rx="2"/><path d="M3 7l3-4h12l3 4"/></svg>'
405
345
  };
406
-
407
- var MODES = [
408
- {id:'', h:'Single', p:'one Scout&#8594;Plan&#8594;Execute&#8594;Verify pass'},
409
- {id:'parallel',h:'Parallel', p:'split, run at once, join &amp; repeat'},
410
- {id:'loop', h:'Loop', p:'refine &amp; retry until it verifies'},
411
- {id:'safe', h:'Safe', p:'light split &#8594; merge &#8594; validate'}
346
+ var MODES=[
347
+ {id:'',h:'Single',ic:I.single,p:'one Scout→Plan→Execute→Verify pass'},
348
+ {id:'parallel',h:'Parallel',ic:I.parallel,p:'split, run at once, join by summary, repeat'},
349
+ {id:'loop',h:'Loop',ic:I.loop,p:'refine & retry until it verifies'},
350
+ {id:'safe',h:'Safe',ic:I.safe,p:'light split merge validate, isolated'}
412
351
  ];
413
- var mode = 'parallel';
414
- var activeId = null;
415
- var connected = false;
416
-
417
- function h(tag,cls,html){ var e=document.createElement(tag); if(cls)e.className=cls; if(html!=null)e.innerHTML=html; return e; }
418
- function headers(){ return {authorization:'Bearer '+token,'content-type':'application/json'}; }
419
- function api(path,opts){ opts=opts||{}; opts.headers=headers(); return fetch(base+path,opts); }
420
- function setConnected(v){
421
- connected=v;
422
- var dot=document.getElementById('statusDot');
423
- dot.className='status-dot'+(v?' on':'');
424
- dot.setAttribute('aria-label', v?'Connected':'Disconnected');
425
- }
426
- function emptyState(icon,title,hint){
427
- return '<div class="empty-state">'+(ICONS[icon]||ICONS.circle)+'<div>'+title+'</div>'+
428
- (hint?'<div style="margin-top:2px;opacity:.8">'+hint+'</div>':'')+'</div>';
429
- }
430
- function skeletons(n){
431
- var s=''; for(var i=0;i<n;i++)s+='<div class="skeleton" style="width:'+(60+((i*17)%35))+'%"></div>';
432
- return s;
433
- }
434
-
435
- function toggleSidebar(){
436
- document.getElementById('sidebar').classList.toggle('open');
437
- document.getElementById('scrim').classList.toggle('show');
438
- }
352
+ var mode=sessionStorage.getItem('maqMode')||'parallel';
353
+ var activeId=null, connected=false, evList=[];
354
+
355
+ function h(t,c,html){var e=document.createElement(t);if(c)e.className=c;if(html!=null)e.innerHTML=html;return e;}
356
+ function esc(s){var d=document.createElement('div');d.textContent=String(s==null?'':s);return d.innerHTML;}
357
+ function hd(){return {authorization:'Bearer '+token,'content-type':'application/json'};}
358
+ function api(p,o){o=o||{};o.headers=hd();return fetch(base+p,o);}
359
+ function setLive(v){connected=v;document.getElementById('dot').className='dot'+(v?' on':'');document.getElementById('liveTxt').textContent=v?'connected':'offline';}
360
+ function empty(ic,t,hint){return '<div class="empty">'+(I[ic]||I.circle)+'<div>'+t+'</div>'+(hint?'<div style="margin-top:2px;opacity:.8">'+hint+'</div>':'')+'</div>';}
361
+ function skel(n){var s='';for(var i=0;i<n;i++)s+='<div class="skel" style="width:'+(58+((i*19)%36))+'%"></div>';return s;}
362
+ function ago(ts){try{var d=(Date.now()-new Date(ts).getTime())/1000;if(d<60)return Math.max(1,d|0)+'s ago';if(d<3600)return (d/60|0)+'m ago';if(d<86400)return (d/3600|0)+'h ago';return (d/86400|0)+'d ago';}catch(e){return '';}}
363
+ function fmtT(ts){try{return new Date(ts).toLocaleTimeString([], {hour:'2-digit',minute:'2-digit',second:'2-digit'});}catch(e){return '';}}
364
+ function toggleSide(){document.getElementById('side').classList.toggle('open');document.getElementById('scrim').classList.toggle('on');}
365
+
366
+ /* ---- custom cursor follower ---- */
367
+ (function(){
368
+ var cur=document.getElementById('cur'),dot=document.getElementById('curDot');
369
+ if(!cur)return; var tx=0,ty=0,cx=0,cy=0;
370
+ addEventListener('mousemove',function(e){tx=e.clientX;ty=e.clientY;dot.style.transform='translate('+tx+'px,'+ty+'px) translate(-50%,-50%)';
371
+ var t=e.target; cur.classList.toggle('hot', !!(t.closest && t.closest('button,a,[role=button],.row[tabindex],.fcard,.mopt,.cmdk-it')));});
372
+ addEventListener('mousedown',function(){cur.classList.add('press');});
373
+ addEventListener('mouseup',function(){cur.classList.remove('press');});
374
+ (function loop(){cx+=(tx-cx)*.18;cy+=(ty-cy)*.18;cur.style.transform='translate('+cx+'px,'+cy+'px) translate(-50%,-50%)';requestAnimationFrame(loop);})();
375
+ })();
376
+
377
+ /* ---- resizable panels ---- */
378
+ (function(){
379
+ var app=document.getElementById('app');
380
+ var wl=sessionStorage.getItem('maqWL'); if(wl)app.style.setProperty('--wl',wl);
381
+ function drag(handle,varName,fromLeft){
382
+ var el=document.getElementById(handle); if(!el)return;
383
+ el.addEventListener('pointerdown',function(e){
384
+ e.preventDefault();el.classList.add('drag');el.setPointerCapture(e.pointerId);
385
+ function mv(ev){
386
+ var v = fromLeft ? ev.clientX : (innerWidth-ev.clientX);
387
+ v=Math.max(180,Math.min(520,v));
388
+ app.style.setProperty(varName, v+'px');
389
+ sessionStorage.setItem(varName==='--wl'?'maqWL':'maqWR', v+'px');
390
+ }
391
+ function up(){el.classList.remove('drag');removeEventListener('pointermove',mv);removeEventListener('pointerup',up);}
392
+ addEventListener('pointermove',mv);addEventListener('pointerup',up);
393
+ });
394
+ el.addEventListener('dblclick',function(){app.style.removeProperty(varName);sessionStorage.removeItem(varName==='--wl'?'maqWL':'maqWR');});
395
+ }
396
+ drag('rl','--wl',true); drag('rr','--wr',false);
397
+ })();
439
398
 
440
- function renderModes(){
441
- var c=document.getElementById('modes'); c.innerHTML='';
399
+ /* ---- mode picker (toggle-up popover) ---- */
400
+ function renderModePop(){
401
+ var pop=document.getElementById('modePop');pop.innerHTML='';
442
402
  MODES.forEach(function(m){
443
- var d=h('button','mode-card'+(m.id===mode?' sel':''),'<h4>'+m.h+'</h4><p>'+m.p+'</p>');
444
- d.setAttribute('role','radio'); d.setAttribute('aria-checked', m.id===mode?'true':'false');
445
- d.onclick=function(){ mode=m.id; renderModes(); };
446
- c.appendChild(d);
403
+ var d=h('div','mopt'+(m.id===mode?' sel':''),'<span class="ic">'+m.ic+'</span><div><h4>'+m.h+'</h4><p>'+m.p+'</p></div>');
404
+ d.setAttribute('role','menuitemradio');d.setAttribute('aria-checked',m.id===mode?'true':'false');
405
+ d.onclick=function(){mode=m.id;sessionStorage.setItem('maqMode',mode);renderModePop();syncSendFace();closePop();};
406
+ pop.appendChild(d);
447
407
  });
448
408
  }
409
+ function syncSendFace(){
410
+ var m=MODES.filter(function(x){return x.id===mode;})[0]||MODES[0];
411
+ document.getElementById('sendIcon').innerHTML=m.ic;
412
+ document.getElementById('sendLabel').textContent='Send · '+m.h;
413
+ }
414
+ function togglePop(e){e.stopPropagation();var p=document.getElementById('modePop'),t=document.getElementById('modeToggle');
415
+ var open=p.classList.toggle('open');t.classList.toggle('open',open);t.setAttribute('aria-expanded',open?'true':'false');}
416
+ function closePop(){var p=document.getElementById('modePop'),t=document.getElementById('modeToggle');p.classList.remove('open');t.classList.remove('open');t.setAttribute('aria-expanded','false');}
417
+ document.addEventListener('click',function(e){ if(!e.target.closest('.send-wrap'))closePop(); });
449
418
 
450
- /* ---------------- signature element: depth-of-work timeline ---------------- */
451
- var PHASES=[
452
- {id:'scout',label:'Scout'},{id:'plan',label:'Plan'},{id:'execute',label:'Execute'},{id:'verify',label:'Verify'}
453
- ];
419
+ /* ---- timeline + orchestration viz ---- */
420
+ var PH=[{id:'scout',l:'Scout'},{id:'plan',l:'Plan'},{id:'execute',l:'Execute'},{id:'verify',l:'Verify'}];
454
421
  function renderTimeline(events){
455
- var done={}, active=null, orchRounds=[];
422
+ var done={},active=null,orchRounds=[],parallel=0,loopN=0,safe={};
456
423
  events.forEach(function(e){
457
- var p=e.data&&e.data.phase;
458
- if(!p)return;
459
- if(/^(parallel-round|loop-iteration|safe-)/.test(p)){
460
- if(e.type==='phase.started' && orchRounds.indexOf(p)===-1) orchRounds.push(p);
461
- return;
462
- }
463
- if(e.type==='phase.started')active=p;
464
- if(e.type==='phase.done'){ done[p]=true; if(active===p)active=null; }
465
- });
466
- var track=document.getElementById('track'); track.innerHTML='';
467
- PHASES.forEach(function(ph,i){
468
- var state = done[ph.id] ? 'done' : (active===ph.id ? 'active' : '');
469
- var node=h('div','node'+(state?' '+state:''));
470
- node.innerHTML='<span class="node-dot">'+ICONS.check+'</span><span class="node-label">'+ph.label+'</span>';
471
- track.appendChild(node);
472
- if(i<PHASES.length-1) track.appendChild(h('div','node-line'));
424
+ var p=e.data&&e.data.phase; if(!p)return;
425
+ if(/^parallel-round/.test(p)){ if(e.type==='phase.started'){parallel++;if(orchRounds.indexOf(p)<0)orchRounds.push(p);} return; }
426
+ if(/^loop-iteration/.test(p)){ if(e.type==='phase.started'){loopN++;if(orchRounds.indexOf(p)<0)orchRounds.push(p);} return; }
427
+ if(/^safe-/.test(p)){ if(e.type==='phase.done')safe[p.replace('safe-','')]=true; if(orchRounds.indexOf(p)<0)orchRounds.push(p); return; }
428
+ if(e.type==='phase.started')active=p; if(e.type==='phase.done'){done[p]=true;if(active===p)active=null;}
473
429
  });
474
- var sub=document.getElementById('subrail');
475
- if(orchRounds.length){
476
- sub.innerHTML='<div class="sub-rail">'+orchRounds.map(function(r){return '<span class="sub-chip">'+r+'</span>';}).join('')+'</div>';
477
- } else { sub.innerHTML=''; }
430
+ var tr=document.getElementById('track');tr.innerHTML='';
431
+ PH.forEach(function(ph,i){var st=done[ph.id]?'done':(active===ph.id?'active':'');var n=h('div','node'+(st?' '+st:''),'<span class="node-dot">'+I.check+'</span><span class="node-label">'+ph.l+'</span>');tr.appendChild(n);if(i<PH.length-1)tr.appendChild(h('div','node-line'));});
432
+ var sr=document.getElementById('subrail');
433
+ if(mode==='safe'&&orchRounds.length){
434
+ var stages=['split','merge','validate'];sr.innerHTML='<div class="sub-rail">'+stages.map(function(s){return '<span class="sub-chip'+(s==='merge'?' merge':'')+'">'+(safe[s]?'✓ ':'')+s+'</span>';}).join('')+'</div>';
435
+ } else if(parallel&&mode==='parallel'){ sr.innerHTML='<div class="sub-rail"><span class="sub-chip">parallel · round '+parallel+'</span></div>'; }
436
+ else if(loopN&&mode==='loop'){ sr.innerHTML='<div class="sub-rail"><span class="sub-chip">loop · iteration '+loopN+'</span></div>'; }
437
+ else if(orchRounds.length){ sr.innerHTML='<div class="sub-rail">'+orchRounds.slice(-4).map(function(r){return '<span class="sub-chip">'+r+'</span>';}).join('')+'</div>'; }
438
+ else sr.innerHTML='';
478
439
  }
479
440
 
480
- function fmtTime(ts){
481
- try{ var d=new Date(ts); return d.toLocaleTimeString([], {hour:'2-digit',minute:'2-digit',second:'2-digit'}); }catch(e){ return ''; }
482
- }
483
-
484
- function renderEvent(e){
441
+ function evView(e){
485
442
  switch(e.type){
486
- case 'task.started': return [ICONS.play,'c-accent',(e.data.goal||e.data.task||'')+(e.data.mode?' ['+e.data.mode+']':''),false];
487
- case 'phase.started': return [ICONS.chevron,'c-accent',(e.data.phase||'')+' …',false];
488
- case 'phase.done': return [ICONS.dot,'c-dim',(e.data.phase||'')+(e.data.reason?' '+e.data.reason:''),false];
489
- case 'agent.event': return [ICONS.dot,'c-dim',e.data.note||JSON.stringify(e.data),false];
490
- case 'agent.stdout': return [ICONS.dot,'c-dim',e.data.text||'',false];
491
- case 'agent.stderr': return [ICONS.warn,'c-warn',e.data.text||'',true];
492
- case 'tool.call': return [ICONS.tool,'c-accent','tool: '+(e.data.tool||JSON.stringify(e.data.command||e.data.raw||{})),false];
493
- case 'task.done': return [e.data.verified?ICONS.verified:ICONS.flag, e.data.verified?'c-ok':'c-warn',
494
- 'done · verified='+e.data.verified+(e.data.mode?' · '+e.data.mode:'')+(e.data.summary?' · '+e.data.summary:''), !e.data.verified];
495
- case 'task.error': return [ICONS.x,'c-danger','error: '+(e.data.message||''),true];
496
- case 'task.cancelled': return [ICONS.x,'c-danger','cancelled',true];
497
- default: return [ICONS.circle,'c-dim',e.type,false];
443
+ case 'task.started':return [I.play,'c-accent',(e.data.goal||e.data.task||'')+(e.data.mode?' ['+e.data.mode+']':''),0];
444
+ case 'phase.started':return [I.chev,'c-accent',(e.data.phase||'')+' …',0];
445
+ case 'phase.done':return [I.dot,'c-dim',(e.data.phase||'')+(e.data.reason?' '+e.data.reason:''),0];
446
+ case 'agent.event':return [I.dot,'c-dim',e.data.note||JSON.stringify(e.data),0];
447
+ case 'agent.stdout':return [I.dot,'c-dim',e.data.text||'',0];
448
+ case 'agent.stderr':return [I.warn,'c-warn',e.data.text||'',1];
449
+ case 'tool.call':return [I.tool,'c-accent','tool: '+(e.data.tool||JSON.stringify(e.data.command||e.data.raw||{})),0];
450
+ case 'task.done':return [e.data.verified?I.verified:I.flag,e.data.verified?'c-ok':'c-warn','done · verified='+e.data.verified+(e.data.mode?' · '+e.data.mode:'')+(e.data.summary?' · '+e.data.summary:''),e.data.verified?0:1];
451
+ case 'task.error':return [I.x,'c-danger','error: '+(e.data.message||''),2];
452
+ case 'task.cancelled':return [I.x,'c-danger','cancelled',2];
453
+ default:return [I.circle,'c-dim',e.type,0];
498
454
  }
499
455
  }
500
456
  function pushEvent(e){
501
- var c=document.getElementById('console');
502
- var first=c.firstElementChild;
503
- if(first && first.classList && first.classList.contains('empty-state-wrap')) c.innerHTML='';
504
- var parts=renderEvent(e);
505
- var card=h('div','ev-card'+(parts[3]?(parts[1]==='c-danger'?' bg-danger':' bg-warn'):''));
506
- card.innerHTML='<span class="ev-ic '+parts[1]+'">'+parts[0]+'</span>'+
507
- '<span class="ev-body"><span class="ev-text '+parts[1]+'"></span><span class="ev-time">'+fmtTime(e.ts)+'</span></span>';
508
- card.querySelector('.ev-text').textContent = parts[2];
509
- c.appendChild(card);
510
- c.scrollTop=c.scrollHeight;
511
- }
512
- function clearConsole(){
513
- document.getElementById('console').innerHTML =
514
- '<div class="empty-state-wrap">'+emptyState('empty','Pick a mode, describe a goal, and press Send.',
515
- 'The live Scout&#8594;Plan&#8594;Execute&#8594;Verify stream renders here.')+'</div>';
457
+ var c=document.getElementById('console');var f=c.firstElementChild;if(f&&f.classList&&f.classList.contains('emptywrap'))c.innerHTML='';
458
+ var p=evView(e);var card=h('div','ev'+(p[3]===2?' bg-danger':(p[3]===1?' bg-warn':'')));
459
+ card.innerHTML='<span class="ic '+p[1]+'">'+p[0]+'</span><span class="tx '+p[1]+'"></span><span class="t">'+fmtT(e.ts)+'</span>';
460
+ card.querySelector('.tx').textContent=p[2];c.appendChild(card);c.scrollTop=c.scrollHeight;
516
461
  }
462
+ function clearConsole(){document.getElementById('console').innerHTML='<div class="emptywrap">'+empty('box','Pick a mode, describe a goal, and Send.','The live Scout→Plan→Execute→Verify stream renders here.')+'</div>';}
517
463
 
518
- var evList=[];
519
464
  async function streamEvents(id){
520
- evList=[];
521
- clearConsole();
522
- document.getElementById('console').innerHTML='';
523
- renderTimeline([]);
465
+ evList=[];document.getElementById('console').innerHTML='';renderTimeline([]);
524
466
  var res=await fetch(base+'/v1/sessions/'+id+'/events',{headers:{authorization:'Bearer '+token}});
525
- if(!res.ok||!res.body){ pushEvent({type:'task.error',ts:new Date().toISOString(),data:{message:'stream failed '+res.status}}); return; }
526
- var reader=res.body.getReader(), dec=new TextDecoder(), buf='';
527
- while(true){
528
- var r=await reader.read(); if(r.done)break;
529
- buf+=dec.decode(r.value,{stream:true});
530
- var blocks=buf.split('\\n\\n'); buf=blocks.pop();
531
- for(var i=0;i<blocks.length;i++){
532
- var lines=blocks[i].split('\\n');
533
- for(var j=0;j<lines.length;j++){
534
- var ln=lines[j];
535
- if(ln.indexOf('data:')===0){
536
- var payload=ln.slice(5).trim(); if(!payload)continue;
537
- try{ var ev=JSON.parse(payload); if(ev.type==='stream.end'){continue;} evList.push(ev); pushEvent(ev); renderTimeline(evList); }catch(err){}
538
- }
539
- }
540
- }
541
- if(id!==activeId)break;
542
- }
467
+ if(!res.ok||!res.body){pushEvent({type:'task.error',ts:new Date().toISOString(),data:{message:'stream '+res.status}});return;}
468
+ var rd=res.body.getReader(),dec=new TextDecoder(),buf='';
469
+ while(true){var r=await rd.read();if(r.done)break;buf+=dec.decode(r.value,{stream:true});
470
+ var bl=buf.split('\n\n');buf=bl.pop();
471
+ for(var i=0;i<bl.length;i++){var ls=bl[i].split('\n');for(var j=0;j<ls.length;j++){var ln=ls[j];
472
+ if(ln.indexOf('data:')===0){var pl=ln.slice(5).trim();if(!pl)continue;try{var ev=JSON.parse(pl);if(ev.type==='stream.end')continue;evList.push(ev);pushEvent(ev);renderTimeline(evList);}catch(x){}}}}
473
+ if(id!==activeId)break;}
543
474
  refreshSessions();
544
475
  }
545
476
 
546
477
  async function startTask(){
547
- var goalEl=document.getElementById('goal');
548
- var goal=goalEl.value.trim(); if(!goal)return;
549
- var btn=document.getElementById('send'); btn.disabled=true;
550
- var body={task:goal, target:document.getElementById('target').value, dryRun:document.getElementById('dry').checked,
551
- maxConcurrency:Number(document.getElementById('conc').value)||4};
478
+ var g=document.getElementById('goal');var goal=g.value.trim();if(!goal)return;
479
+ var btn=document.getElementById('send');btn.disabled=true;
480
+ var body={task:goal,target:document.getElementById('target').value,dryRun:document.getElementById('dry').checked,maxConcurrency:Number(document.getElementById('conc').value)||4};
552
481
  if(mode)body.mode=mode;
553
- try{
554
- var res=await api('/v1/sessions',{method:'POST',body:JSON.stringify(body)});
555
- var s=await res.json();
556
- if(!res.ok){ pushEvent({type:'task.error',ts:new Date().toISOString(),data:{message:s.error||('HTTP '+res.status)}}); return; }
557
- activeId=s.id; goalEl.value='';
558
- await refreshSessions();
559
- streamEvents(s.id);
560
- }catch(e){ pushEvent({type:'task.error',ts:new Date().toISOString(),data:{message:String(e)}}); }
561
- finally{ btn.disabled=false; }
482
+ try{var res=await api('/v1/sessions',{method:'POST',body:JSON.stringify(body)});var s=await res.json();
483
+ if(!res.ok){pushEvent({type:'task.error',ts:new Date().toISOString(),data:{message:s.error||('HTTP '+res.status)}});return;}
484
+ activeId=s.id;g.value='';await refreshSessions();streamEvents(s.id);
485
+ }catch(e){pushEvent({type:'task.error',ts:new Date().toISOString(),data:{message:String(e)}});}
486
+ finally{btn.disabled=false;}
562
487
  }
563
488
 
489
+ /* ---- sessions (history grouped by day, timestamps, click-to-restore) ---- */
490
+ var sLoaded=false;
491
+ function groupKey(ts){try{var d=new Date(ts);var t=new Date();if(d.toDateString()===t.toDateString())return 'Today';var y=new Date(t-864e5);if(d.toDateString()===y.toDateString())return 'Yesterday';return d.toLocaleDateString([], {month:'short',day:'numeric'});}catch(e){return 'Earlier';}}
564
492
  function sessionRow(s){
565
- var it=h('div','row'+(s.id===activeId?' active':''));
566
- it.setAttribute('tabindex','0'); it.setAttribute('role','button');
567
- it.setAttribute('aria-label','Session: '+s.task);
568
- var verifiedMark = s.verified===true?' · '+ICONS.check.replace('<svg','<svg style=\\'width:10px;height:10px;vertical-align:-1px\\''):(s.verified===false?' · unverified':'');
569
- it.innerHTML =
570
- '<div class="row-title">'+escText(s.task)+'</div>'+
571
- '<div class="row-meta"><span class="tag '+s.status+'">'+s.status+'</span>'+
572
- (s.mode?'<span>'+s.mode+'</span>':'')+'<span>'+(s.target||'auto')+'</span><span>'+s.eventCount+' ev</span>'+verifiedMark+'</div>';
573
- it.onclick=function(){ activeId=s.id; refreshSessions(); openPreview(s.id); streamEvents(s.id); if(window.innerWidth<=900)toggleSidebar(); };
574
- it.onkeydown=function(ev){ if(ev.key==='Enter')it.click(); };
493
+ var it=h('div','row'+(s.id===activeId?' active':''));it.setAttribute('tabindex','0');it.setAttribute('role','button');it.setAttribute('aria-label','Open session '+s.task);
494
+ var vm=s.verified===true?' · ✓':(s.verified===false?' · ✗':'');
495
+ it.innerHTML='<div class="row-t">'+esc(s.task)+'</div><div class="row-m"><span class="tag '+s.status+'">'+s.status+'</span>'+(s.mode?'<span>'+s.mode+'</span>':'')+'<span>'+(s.target||'auto')+'</span><span>'+ago(s.updatedAt||s.createdAt)+'</span>'+vm+'</div>';
496
+ it.onclick=function(){activeId=s.id;refreshSessions();openPreview(s.id);streamEvents(s.id);if(innerWidth<=900)toggleSide();};
497
+ it.onkeydown=function(ev){if(ev.key==='Enter')it.click();};
575
498
  return it;
576
499
  }
577
- function escText(s){ var d=document.createElement('div'); d.textContent=String(s==null?'':s); return d.innerHTML; }
578
-
579
- var sessionsLoaded=false;
580
500
  async function refreshSessions(){
581
- var box=document.getElementById('sessions');
582
- if(!sessionsLoaded) box.innerHTML = skeletons(3);
583
- try{
584
- var res=await api('/v1/sessions'); var d=await res.json();
585
- setConnected(true);
586
- var list=(d.sessions||[]);
587
- sessionsLoaded=true;
588
- box.innerHTML='';
589
- if(!list.length){ box.innerHTML=emptyState('empty','No sessions yet','Send a goal to start one.'); return; }
590
- list.slice().reverse().forEach(function(s){ box.appendChild(sessionRow(s)); });
591
- }catch(e){ setConnected(false); if(!sessionsLoaded) box.innerHTML=emptyState('warn','Can\\'t reach the daemon',''); }
501
+ var box=document.getElementById('sessions');if(!sLoaded)box.innerHTML=skel(3);
502
+ try{var d=await (await api('/v1/sessions')).json();setLive(true);var list=(d.sessions||[]);sLoaded=true;box.innerHTML='';
503
+ if(!list.length){box.innerHTML=empty('box','No sessions yet','Send a goal to start one.');return;}
504
+ var groups={},order=[];list.slice().reverse().forEach(function(s){var k=groupKey(s.updatedAt||s.createdAt);if(!groups[k]){groups[k]=[];order.push(k);}groups[k].push(s);});
505
+ order.forEach(function(k){box.appendChild(h('div','grp-label',k));groups[k].forEach(function(s){box.appendChild(sessionRow(s));});});
506
+ }catch(e){setLive(false);if(!sLoaded)box.innerHTML=empty('warn','Can\'t reach the daemon','');}
592
507
  }
593
508
 
594
- async function openPreview(id){
595
- document.getElementById('app').classList.add('preview-open');
596
- var box=document.getElementById('previewBody'); box.innerHTML=skeletons(4);
597
- try{
598
- var res=await api('/v1/sessions/'+id); var d=await res.json();
599
- var pre=h('pre','data');
600
- pre.textContent=JSON.stringify({id:d.id,task:d.task,status:d.status,mode:d.mode,verified:d.verified,error:d.error},null,2);
601
- box.innerHTML=''; box.appendChild(pre);
602
- }catch(e){ box.innerHTML=emptyState('warn','Could not load session',String(e)); }
509
+ /* ---- universal preview ---- */
510
+ function detectType(text){var t=(text||'').trim();if(!t)return 'empty';if((t[0]==='{'||t[0]==='[')){try{JSON.parse(t);return 'json';}catch(e){}}if(/^<!doctype|^<html|^<div|^<body/i.test(t))return 'html';if(/^#\s|\n#\s|\*\*|^- |\n- /.test(t))return 'markdown';return 'code';}
511
+ function jsonTree(v,key){
512
+ var wrap=h('div','jt open');var isObj=v&&typeof v==='object';
513
+ if(isObj){var isArr=Array.isArray(v);var keys=isArr?v.map(function(_,i){return i;}):Object.keys(v);
514
+ var head=h('div',null,'<span class="tog">▾</span> '+(key!=null?'<span class="k">'+esc(key)+'</span>: ':'')+(isArr?'[':'{')+'<span class="c-dim"> '+keys.length+' </span>'+(isArr?']':'}'));
515
+ head.querySelector('.tog').onclick=function(){wrap.classList.toggle('open');this.textContent=wrap.classList.contains('open')?'▾':'▸';};
516
+ wrap.appendChild(head);var col=h('div','col nest');keys.forEach(function(k){col.appendChild(jsonTree(v[k],k));});wrap.appendChild(col);
517
+ } else {
518
+ var cls=typeof v==='number'?'n':(typeof v==='boolean'?'b':'s');var val=typeof v==='string'?'"'+v+'"':String(v);
519
+ wrap.innerHTML=(key!=null?'<span class="k">'+esc(key)+'</span>: ':'')+'<span class="'+cls+'">'+esc(val)+'</span>';wrap.className='jt';
520
+ }
521
+ return wrap;
603
522
  }
604
- function togglePreview(){ document.getElementById('app').classList.toggle('preview-open'); }
605
-
606
- async function feat(kind){
607
- document.getElementById('app').classList.add('preview-open');
608
- var box=document.getElementById('previewBody'); box.innerHTML=skeletons(5);
609
- try{
610
- var out;
611
- if(kind==='agents'){ out=await (await api('/v1/agents')).json(); renderAgents(out.agents||[]); }
612
- else if(kind==='connectivity'){ out=await (await api('/v1/connectivity')).json(); }
613
- else { out=await (await api('/v1/exec',{method:'POST',body:JSON.stringify({argv:[kind]})})).json(); }
614
- var pre=h('pre','data');
615
- pre.textContent=(out.stdout!=null?out.stdout:'')+(out.stderr?('\\n'+out.stderr):'')||JSON.stringify(out,null,2);
616
- box.innerHTML=''; box.appendChild(pre);
617
- }catch(e){ box.innerHTML=emptyState('warn','Could not run '+kind,String(e)); }
523
+ function mdToHtml(t){
524
+ var FE=String.fromCharCode(96,96,96);var parts=t.split(FE);var out='';
525
+ for(var i=0;i<parts.length;i++){ if(i%2){out+='<pre>'+esc(parts[i].replace(/^\w*\n/,''))+'</pre>';continue;}
526
+ var s=esc(parts[i]);
527
+ s=s.replace(/^### (.*)$/gm,'<h3>$1</h3>').replace(/^## (.*)$/gm,'<h2>$1</h2>').replace(/^# (.*)$/gm,'<h1>$1</h1>');
528
+ s=s.replace(/\*\*([^*]+)\*\*/g,'<strong>$1</strong>').replace(/\[([^\]]+)\]\(([^)]+)\)/g,'<a href="$2" target="_blank" rel="noopener">$1</a>');
529
+ s=s.replace(/^- (.*)$/gm,'<li>$1</li>').replace(/(<li>[\s\S]*?<\/li>)/g,'<ul>$1</ul>');
530
+ var C=String.fromCharCode(96);s=s.replace(new RegExp(C+'([^'+C+']+)'+C,'g'),'<code>$1</code>');
531
+ s=s.replace(/\n{2,}/g,'</p><p>');out+='<p>'+s+'</p>';
532
+ }
533
+ return out;
618
534
  }
619
- function renderAgents(agents){
620
- var box=document.getElementById('agents');
621
- if(!agents.length){ box.innerHTML=emptyState('empty','No agents detected','Install/log in to an AI CLI.'); return; }
535
+ function renderPreview(content,forceType){
536
+ var box=document.getElementById('prevBody');var ty=forceType||detectType(content);document.getElementById('prevType').textContent=ty;
622
537
  box.innerHTML='';
623
- agents.forEach(function(a){
624
- var it=h('div','row');
625
- var statusText = a.installed?(a.authenticated?'authenticated':'installed, logged out'):'not found';
626
- var tagCls = a.installed?(a.authenticated?'done':'paused'):'cancelled';
627
- it.innerHTML='<div class="row-title">'+escText(a.name)+'</div><div class="row-meta"><span class="tag '+tagCls+'">'+statusText+'</span></div>';
628
- box.appendChild(it);
629
- });
630
- }
631
-
632
- async function openFeatures(){
633
- document.getElementById('features').classList.add('show');
634
- var grid=document.getElementById('featureGrid'); grid.innerHTML=skeletons(6);
635
- try{
636
- var cat=await (await api('/v1/commands')).json();
637
- var cmds=(cat.maq||[]); grid.innerHTML='';
638
- if(!cmds.length){ grid.innerHTML=emptyState('empty','No commands available',''); return; }
639
- cmds.forEach(function(c){
640
- var card=h('button','fcard');
641
- card.innerHTML='<div class="eyebrow">'+escText(c.category)+'</div><h4>'+escText(c.name)+'</h4><p>'+escText(c.summary)+'</p>';
642
- card.onclick=function(){ runFeature(c); };
643
- grid.appendChild(card);
644
- });
645
- }catch(e){ grid.innerHTML=emptyState('warn','Could not load features',String(e)); }
538
+ if(ty==='json'){try{var v=JSON.parse(content);box.appendChild(jsonTree(v));return;}catch(e){ty='code';}}
539
+ if(ty==='markdown'){var m=h('div','md');m.innerHTML=mdToHtml(content);box.appendChild(m);return;}
540
+ if(ty==='html'){var f=document.createElement('iframe');f.className='htmlprev';f.setAttribute('sandbox','');f.srcdoc=content;box.appendChild(f);return;}
541
+ if(ty==='image'){var img=document.createElement('img');img.src=content;img.style.maxWidth='100%';box.appendChild(img);return;}
542
+ if(ty==='empty'){box.innerHTML=empty('box','Nothing to preview','Select a session or run a feature.');return;}
543
+ var lines=String(content).split('\n');var pre=h('pre','code');var g=h('div','gutter');var cl=h('div','codelines');
544
+ g.textContent=lines.map(function(_,i){return i+1;}).join('\n');cl.textContent=content;pre.appendChild(g);pre.appendChild(cl);box.appendChild(pre);
646
545
  }
647
- function closeFeatures(){ document.getElementById('features').classList.remove('show'); }
648
-
649
- async function openSecurity(){
650
- document.getElementById('security').classList.add('show');
651
- var box=document.getElementById('securityBody'); box.innerHTML=skeletons(8);
652
- try{
653
- var d=await (await api('/v1/security')).json();
654
- var r=d.rules, evs=(d.events||[]);
655
- var html='';
656
- html+='<div class="posture-row">';
657
- html+='<span class="posture-pill">'+ICONS.check+' permission posture: '+r.permissionMode+'</span>';
658
- html+='<span class="posture-pill">'+ICONS.check+' secret env scrubbing: on</span>';
659
- html+='<span class="posture-pill">'+ICONS.check+' prompt-injection scanning: on</span>';
660
- html+='</div>';
661
-
662
- html+=secBlock('Protected paths', 'never writable — cannot be overridden by config', r.protectedPaths,
663
- 'Enforced in sandbox.ts checkWrite(), unconditionally, before any tier logic — a compromised agent cannot argue past this with a clever prompt.');
664
- html+=secBlock('Protected file patterns', 'matched by name, anywhere in the project', r.protectedNamePatterns, '');
665
- if(r.extraProtectedPaths && r.extraProtectedPaths.length){
666
- html+=secBlock('Project-added protected paths', '', r.extraProtectedPaths, '');
667
- }
668
- html+=secBlock('Network egress allowlist', 'default-deny everything else', r.netAllowlist,
669
- 'Enforced in tools.ts http_get and available to any outbound call site via security.ts checkEgress(). Not just a suggestion in a prompt.');
670
- html+=secBlock('Prompt-injection scanning', '', r.promptInjectionScanning,
671
- 'README, manifest, git commits, AGENTS.md and skill files are scanned for override/exfiltration/jailbreak patterns before they reach a model. Findings are surfaced loudly, never silently dropped.');
672
-
673
- html+='<div class="sec-block"><div class="sec-head"><h4>Recent findings</h4><span class="count-badge'+(evs.length?' alert':'')+'">'+evs.length+'</span></div>';
674
- if(!evs.length){ html+='<div class="sec-note">None recorded yet — findings appear here as tasks run.</div>'; }
675
- else {
676
- html+='<div>'+evs.slice().reverse().map(function(e){
677
- var cls=e.severity==='high'?'c-danger':'c-warn';
678
- return '<div class="ev-card"><span class="ev-ic '+cls+'">'+ICONS.warn+'</span><span class="ev-text '+cls+'">'+escText(e.kind)+': '+escText(e.detail)+'</span></div>';
679
- }).join('')+'</div>';
680
- }
681
- html+='</div>';
682
- box.innerHTML = html;
683
- }catch(e){ box.innerHTML=emptyState('warn','Could not load security rules',String(e)); }
684
- }
685
- function secBlock(title,note,items,footer){
686
- var html='<div class="sec-block"><div class="sec-head"><h4>'+title+'</h4><span class="count-badge">'+(items?items.length:0)+'</span>'+
687
- (note?'<span class="sec-note">'+note+'</span>':'')+'</div>';
688
- html+='<div class="sec-list">'+(items||[]).map(function(p){return '<div class="sec-item">'+escText(p)+'</div>';}).join('')+'</div>';
689
- if(footer) html+='<div class="sec-note">'+footer+'</div>';
690
- html+='</div>';
691
- return html;
692
- }
693
- function closeSecurity(){ document.getElementById('security').classList.remove('show'); }
694
-
695
- function runFeature(c){
696
- closeFeatures();
697
- if(c.needsInput==='task'||c.needsInput==='query'){
698
- var g=document.getElementById('goal'); g.focus();
699
- if(c.name==='orchestrate'){ mode='parallel'; renderModes(); }
700
- g.placeholder='['+c.name+'] '+c.summary;
701
- } else {
702
- feat(c.name);
703
- }
546
+ function openPreview(id){
547
+ document.getElementById('app').classList.remove('no-prev');document.getElementById('app').classList.add('prev-sheet');
548
+ var box=document.getElementById('prevBody');box.innerHTML=skel(5);document.getElementById('prevType').textContent='';
549
+ api('/v1/sessions/'+id).then(function(r){return r.json();}).then(function(d){
550
+ renderPreview(JSON.stringify({id:d.id,task:d.task,status:d.status,mode:d.mode,verified:d.verified,error:d.error,events:(d.events||[]).length},null,2),'json');
551
+ }).catch(function(e){box.innerHTML=empty('warn','Could not load session',String(e));});
704
552
  }
553
+ function togglePreview(){var a=document.getElementById('app');a.classList.toggle('no-prev');a.classList.toggle('prev-sheet');
554
+ if(!a.classList.contains('no-prev')&&!document.getElementById('prevBody').innerHTML.trim())renderPreview('','empty');}
705
555
 
556
+ /* ---- feature explorer / exec ---- */
557
+ async function feat(kind){
558
+ document.getElementById('app').classList.remove('no-prev');document.getElementById('app').classList.add('prev-sheet');
559
+ var box=document.getElementById('prevBody');box.innerHTML=skel(5);document.getElementById('prevType').textContent='';
560
+ try{var out;
561
+ if(kind==='agents'){out=await (await api('/v1/agents')).json();renderAgents(out.agents||[]);renderPreview(JSON.stringify(out,null,2),'json');return;}
562
+ if(kind==='connectivity'){out=await (await api('/v1/connectivity')).json();renderPreview(JSON.stringify(out,null,2),'json');return;}
563
+ out=await (await api('/v1/exec',{method:'POST',body:JSON.stringify({argv:[kind]})})).json();
564
+ var txt=(out.stdout!=null?out.stdout:'')+(out.stderr?('\n'+out.stderr):'');renderPreview(txt||JSON.stringify(out,null,2));
565
+ }catch(e){box.innerHTML=empty('warn','Could not run '+kind,String(e));}
566
+ }
567
+ function renderAgents(agents){var box=document.getElementById('agents');if(!agents.length){box.innerHTML=empty('box','No agents','Log in to an AI CLI.');return;}box.innerHTML='';
568
+ agents.forEach(function(a){var st=a.installed?(a.authenticated?'authenticated':'logged out'):'not found';var tg=a.installed?(a.authenticated?'done':'paused'):'cancelled';
569
+ box.appendChild(h('div','row','<div class="row-t">'+esc(a.name)+'</div><div class="row-m"><span class="tag '+tg+'">'+st+'</span></div>'));});}
570
+
571
+ async function openFeatures(){document.getElementById('features').classList.add('show');var g=document.getElementById('featureGrid');g.innerHTML=skel(6);
572
+ try{var cat=await (await api('/v1/commands')).json();var cmds=(cat.maq||[]);g.innerHTML='';
573
+ cmds.forEach(function(c){var card=h('button','fcard','<div class="eyebrow">'+esc(c.category)+'</div><h4>'+esc(c.name)+'</h4><p>'+esc(c.summary)+'</p>');card.onclick=function(){runFeature(c);};g.appendChild(card);});
574
+ }catch(e){g.innerHTML=empty('warn','Could not load features',String(e));}}
575
+ function closeFeatures(){document.getElementById('features').classList.remove('show');}
576
+ function runFeature(c){closeFeatures();if(c.needsInput==='task'||c.needsInput==='query'){var g=document.getElementById('goal');g.focus();if(c.name==='orchestrate'){mode='parallel';renderModePop();syncSendFace();}g.placeholder='['+c.name+'] '+c.summary;}else feat(c.name);}
577
+
578
+ async function openSecurity(){document.getElementById('security').classList.add('show');var box=document.getElementById('securityBody');box.innerHTML=skel(8);
579
+ try{var d=await (await api('/v1/security')).json();var r=d.rules,evs=(d.events||[]);var html='';
580
+ html+='<div class="posture"><span class="pill">'+I.check+' posture: '+r.permissionMode+'</span><span class="pill">'+I.check+' secret scrubbing: on</span><span class="pill">'+I.check+' injection scanning: on</span></div>';
581
+ html+=secBlk('Protected paths','never writable — cannot be overridden by config',r.protectedPaths,'Enforced in sandbox.ts checkWrite(), unconditionally, before any tier logic.');
582
+ html+=secBlk('Protected file patterns','matched by name anywhere',r.protectedNamePatterns,'');
583
+ if(r.extraProtectedPaths&&r.extraProtectedPaths.length)html+=secBlk('Project-added protected paths','',r.extraProtectedPaths,'');
584
+ html+=secBlk('Network egress allowlist','default-deny everything else',r.netAllowlist,'Enforced in tools.ts http_get via security.ts checkEgress().');
585
+ html+=secBlk('Prompt-injection scanning','',r.promptInjectionScanning,'README, manifest, git commits, AGENTS.md and skill files scanned before reaching a model.');
586
+ html+='<div class="sec-blk"><div class="sec-h"><h4>Recent findings</h4><span class="cbadge'+(evs.length?' alert':'')+'">'+evs.length+'</span></div>';
587
+ html+=evs.length?('<div>'+evs.slice().reverse().map(function(e){var cl=e.severity==='high'?'c-danger':'c-warn';return '<div class="ev"><span class="ic '+cl+'">'+I.warn+'</span><span class="tx '+cl+'">'+esc(e.kind)+': '+esc(e.detail)+'</span></div>';}).join('')+'</div>'):'<div class="sec-note">None recorded yet.</div>';
588
+ html+='</div>';box.innerHTML=html;
589
+ }catch(e){box.innerHTML=empty('warn','Could not load security rules',String(e));}}
590
+ function secBlk(t,note,items,foot){var html='<div class="sec-blk"><div class="sec-h"><h4>'+t+'</h4><span class="cbadge">'+(items?items.length:0)+'</span>'+(note?'<span class="sec-note">'+note+'</span>':'')+'</div>';
591
+ html+='<div class="sec-list">'+(items||[]).map(function(p){return '<div class="sec-i">'+esc(p)+'</div>';}).join('')+'</div>';if(foot)html+='<div class="sec-note">'+foot+'</div>';return html+'</div>';}
592
+ function closeSecurity(){document.getElementById('security').classList.remove('show');}
593
+
594
+ /* ---- request box ---- */
706
595
  async function loadRequests(){
707
- try{
708
- var d=await (await api('/v1/requests')).json();
709
- var box=document.getElementById('requests'); var pend=(d.pending||[]);
710
- var badge=document.getElementById('reqCount');
711
- badge.textContent=String(pend.length);
712
- badge.className='count-badge'+(pend.length?' alert':'');
713
- if(!pend.length){ box.innerHTML=emptyState('empty','No pending requests',''); return; }
714
- box.innerHTML='';
715
- pend.forEach(function(r){
716
- var it=h('div','req-card '+r.risk);
717
- it.innerHTML='<div class="req-action">'+escText(r.action)+' — '+escText(r.detail||'')+'</div>'+
718
- '<div class="req-reason">'+escText(r.risk)+' · '+escText(r.reason||'')+'</div>'+
719
- '<div class="req-btns"><button class="btn-sm btn-approve">Approve</button><button class="btn-sm btn-deny">Deny</button></div>';
720
- it.querySelector('.btn-approve').onclick=function(){ decideReq(r.id,'approve'); };
721
- it.querySelector('.btn-deny').onclick=function(){ decideReq(r.id,'deny'); };
722
- box.appendChild(it);
723
- });
596
+ try{var d=await (await api('/v1/requests')).json();var box=document.getElementById('requests');var pend=(d.pending||[]);
597
+ var b=document.getElementById('reqCount');b.textContent=String(pend.length);b.className='cbadge'+(pend.length?' alert':'');
598
+ if(!pend.length){box.innerHTML=empty('box','No pending requests','');return;}box.innerHTML='';
599
+ pend.forEach(function(r){var it=h('div','req '+r.risk,'<div class="a">'+esc(r.action)+''+esc(r.detail||'')+'</div><div class="r">'+esc(r.risk)+' · '+esc(r.reason||'')+'</div><div class="btns"><button class="bs ok-b">Approve</button><button class="bs no-b">Deny</button></div>');
600
+ it.querySelector('.ok-b').onclick=function(){decideReq(r.id,'approve');};it.querySelector('.no-b').onclick=function(){decideReq(r.id,'deny');};box.appendChild(it);});
724
601
  }catch(e){}
725
602
  }
726
- async function decideReq(id,action){
727
- try{ await api('/v1/requests/'+id,{method:'POST',body:JSON.stringify({action:action})}); loadRequests(); }catch(e){}
728
- }
729
-
730
- clearConsole();
731
- renderModes(); renderTimeline([]); refreshSessions(); feat('agents'); loadRequests();
732
- setInterval(refreshSessions, 5000); setInterval(loadRequests, 3000);
733
- document.getElementById('goal').addEventListener('keydown', function(e){ if((e.metaKey||e.ctrlKey)&&e.key==='Enter')startTask(); });
734
- document.addEventListener('keydown', function(e){ if(e.key==='Escape'){ closeFeatures(); closeSecurity(); } });
735
- </script>
736
- </body></html>`;
737
- }
603
+ async function decideReq(id,a){try{await api('/v1/requests/'+id,{method:'POST',body:JSON.stringify({action:a})});loadRequests();}catch(e){}}
604
+
605
+ /* ---- command palette ---- */
606
+ var cmdkItems=[],cmdkSel=0;
607
+ async function openPalette(){var bd=document.getElementById('cmdk');bd.classList.add('show');var inp=document.getElementById('cmdkInput');inp.value='';inp.focus();
608
+ cmdkItems=[];try{var cat=await (await api('/v1/commands')).json();(cat.maq||[]).forEach(function(c){cmdkItems.push({t:c.name,s:c.summary,cat:c.category,run:function(){closePalette();runFeature(c);}});});}catch(e){}
609
+ try{var d=await (await api('/v1/sessions')).json();(d.sessions||[]).slice().reverse().slice(0,8).forEach(function(s){cmdkItems.push({t:s.task,s:s.status+' · '+(s.mode||'single'),cat:'session',run:function(){closePalette();activeId=s.id;refreshSessions();openPreview(s.id);streamEvents(s.id);}});});}catch(e){}
610
+ paletteFilter('');}
611
+ function closePalette(){document.getElementById('cmdk').classList.remove('show');}
612
+ function paletteFilter(q){q=q.toLowerCase();var list=document.getElementById('cmdkList');var m=cmdkItems.filter(function(it){return (it.t+' '+it.s+' '+it.cat).toLowerCase().indexOf(q)>=0;});cmdkSel=0;
613
+ if(!m.length){list.innerHTML='<div class="empty" style="padding:26px">No matches</div>';return;}list.innerHTML='';
614
+ m.forEach(function(it,i){var el=h('div','cmdk-it'+(i===0?' sel':''),'<span class="ic">'+I.chev+'</span><div class="m"><div class="tt">'+esc(it.t)+'</div><div class="ss">'+esc(it.s)+'</div></div><span class="cat">'+esc(it.cat)+'</span>');el.onclick=it.run;el.dataset.i=i;list.appendChild(el);});
615
+ list._m=m;}
616
+ document.getElementById('cmdkInput').addEventListener('input',function(e){paletteFilter(e.target.value);});
617
+ document.getElementById('cmdkInput').addEventListener('keydown',function(e){var list=document.getElementById('cmdkList');var m=list._m||[];
618
+ if(e.key==='ArrowDown'){e.preventDefault();cmdkSel=Math.min(m.length-1,cmdkSel+1);}
619
+ else if(e.key==='ArrowUp'){e.preventDefault();cmdkSel=Math.max(0,cmdkSel-1);}
620
+ else if(e.key==='Enter'){if(m[cmdkSel])m[cmdkSel].run();return;}
621
+ else return;
622
+ [].forEach.call(list.children,function(c,i){c.classList.toggle('sel',i===cmdkSel);});});
623
+
624
+ /* ---- init ---- */
625
+ clearConsole();renderModePop();syncSendFace();renderTimeline([]);refreshSessions();feat('agents');loadRequests();
626
+ document.getElementById('app').classList.add('no-prev');
627
+ setInterval(refreshSessions,5000);setInterval(loadRequests,3000);
628
+ document.getElementById('goal').addEventListener('keydown',function(e){if((e.metaKey||e.ctrlKey)&&e.key==='Enter')startTask();});
629
+ document.addEventListener('keydown',function(e){
630
+ if((e.metaKey||e.ctrlKey)&&e.key.toLowerCase()==='k'){e.preventDefault();openPalette();}
631
+ if(e.key==='Escape'){closeFeatures();closeSecurity();closePalette();closePop();}
632
+ });
633
+ `;