march-cli 0.1.36 → 0.1.38

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.
Files changed (58) hide show
  1. package/package.json +1 -1
  2. package/src/agent/code-search/tool.mjs +1 -1
  3. package/src/agent/runner/runner-utils.mjs +20 -0
  4. package/src/agent/runner.mjs +16 -18
  5. package/src/agent/runtime/remote-ui-client.mjs +0 -1
  6. package/src/agent/runtime/runner-process-client.mjs +7 -0
  7. package/src/agent/runtime/runner-process-factory.mjs +7 -3
  8. package/src/agent/runtime/runner-runtime-host.mjs +2 -2
  9. package/src/agent/runtime/ui-event-bridge.mjs +0 -2
  10. package/src/agent/session/session-options.mjs +2 -2
  11. package/src/agent/tools.mjs +5 -23
  12. package/src/agent/turn/turn-events.mjs +41 -0
  13. package/src/agent/turn/turn-runner.mjs +5 -2
  14. package/src/cli/args.mjs +0 -3
  15. package/src/cli/commands/registry/slash-command-registry.mjs +2 -0
  16. package/src/cli/fallback-ui.mjs +0 -2
  17. package/src/cli/input/history-store.mjs +65 -3
  18. package/src/cli/input/mode-state.mjs +1 -1
  19. package/src/cli/repl-loop.mjs +75 -25
  20. package/src/cli/startup/app-runtime.mjs +72 -31
  21. package/src/cli/startup/create-runtime-runner.mjs +5 -46
  22. package/src/cli/startup/startup-session.mjs +3 -13
  23. package/src/cli/tui/input/history-navigation-controller.mjs +56 -0
  24. package/src/cli/turn/turn-input-preparer.mjs +0 -1
  25. package/src/cli/ui.mjs +9 -6
  26. package/src/cli/workspace/command.mjs +147 -0
  27. package/src/cli/workspace/output-router.mjs +108 -0
  28. package/src/cli/workspace/project-runtime.mjs +92 -0
  29. package/src/config/features.mjs +0 -1
  30. package/src/context/engine.mjs +4 -2
  31. package/src/context/system-core/base.md +4 -1
  32. package/src/extensions/lifecycle-adapter.mjs +1 -1
  33. package/src/history/runner.mjs +11 -0
  34. package/src/history/store.mjs +129 -0
  35. package/src/history/tool.mjs +39 -0
  36. package/src/lsp/client.mjs +12 -5
  37. package/src/lsp/service.mjs +15 -3
  38. package/src/main.mjs +5 -2
  39. package/src/notification/desktop-notifier.mjs +16 -8
  40. package/src/web-ui/command.mjs +2 -2
  41. package/src/web-ui/dist/assets/index-BQtl1uQs.css +1 -0
  42. package/src/web-ui/dist/assets/index-DrlJis_D.js +1845 -0
  43. package/src/web-ui/dist/index.html +13 -0
  44. package/src/web-ui/runtime-host.mjs +5 -25
  45. package/src/web-ui/session-manager.mjs +2 -2
  46. package/src/web-ui/src/components/timeline/TimelineBlocks.tsx +2 -10
  47. package/src/web-ui/src/mockData.ts +1 -8
  48. package/src/web-ui/src/model.ts +0 -2
  49. package/src/web-ui/src/runtime/client.ts +0 -1
  50. package/src/web-ui/src/runtime/runtimeTimeline.ts +1 -3
  51. package/src/web-ui/src/styles/shell.css +1 -2
  52. package/src/web-ui/src/timelineAdapter.ts +1 -2
  53. package/src/workspace/project-id.mjs +14 -0
  54. package/src/workspace/project-registry.mjs +74 -0
  55. package/src/workspace/session-index.mjs +75 -0
  56. package/src/workspace/supervisor.mjs +172 -0
  57. package/src/cli/permissions.mjs +0 -103
  58. package/src/cli/tui/permission-request-ui.mjs +0 -18
@@ -3,9 +3,10 @@ import { LspDiagnosticStore } from "./diagnostic-store.mjs";
3
3
  import { resolveLspServerStatus } from "./servers.mjs";
4
4
 
5
5
  export class LspService {
6
- constructor({ cwd, onEvent = null }) {
6
+ constructor({ cwd, onEvent = null, onStatusChange = null }) {
7
7
  this.cwd = cwd;
8
8
  this.onEvent = onEvent;
9
+ this.onStatusChange = onStatusChange;
9
10
  this.store = new LspDiagnosticStore();
10
11
  this.clients = new Map();
11
12
  this.spawning = new Map();
@@ -19,6 +20,7 @@ export class LspService {
19
20
  if (result.status === "unavailable") {
20
21
  this.unavailable.set(result.id, result);
21
22
  this.#emitOnce(`unavailable:${result.id}:${result.reason}`, result);
23
+ this.#emitStatusChange(result);
22
24
  return result;
23
25
  }
24
26
 
@@ -34,7 +36,9 @@ export class LspService {
34
36
  return { status: "starting", id: server.id, root: server.root };
35
37
  }
36
38
 
37
- this.#emitOnce(`starting:${key}`, { status: "starting", id: server.id, root: server.root, managed: server.managed });
39
+ const startingEvent = { status: "starting", id: server.id, root: server.root, managed: server.managed };
40
+ this.#emitOnce(`starting:${key}`, startingEvent);
41
+ this.#emitStatusChange(startingEvent);
38
42
  const task = this.#startClient(server, key).then((client) => {
39
43
  client?.touchFile(path);
40
44
  return client;
@@ -76,18 +80,22 @@ export class LspService {
76
80
  cwd: server.root,
77
81
  initialization: server.initialization,
78
82
  store: this.store,
83
+ onStatusChange: (event) => this.#emitStatusChange(event),
79
84
  });
80
85
  try {
81
86
  await client.start();
82
87
  this.clients.set(key, client);
83
88
  this.unavailable.delete(server.id);
84
- this.#emitOnce(`attached:${key}`, { status: "attached", id: server.id, root: server.root, managed: server.managed });
89
+ const attachedEvent = { status: "attached", id: server.id, root: server.root, managed: server.managed };
90
+ this.#emitOnce(`attached:${key}`, attachedEvent);
91
+ this.#emitStatusChange(attachedEvent);
85
92
  return client;
86
93
  } catch (err) {
87
94
  client.status = "failed";
88
95
  const event = { status: "failed", id: server.id, root: server.root, reason: err.message };
89
96
  this.unavailable.set(server.id, event);
90
97
  this.#emitOnce(`failed:${key}:${err.message}`, event);
98
+ this.#emitStatusChange(event);
91
99
  return null;
92
100
  }
93
101
  }
@@ -97,6 +105,10 @@ export class LspService {
97
105
  this.announced.add(key);
98
106
  this.onEvent?.(event);
99
107
  }
108
+
109
+ #emitStatusChange(event) {
110
+ this.onStatusChange?.(event);
111
+ }
100
112
  }
101
113
 
102
114
  function summarizeStatus(servers) {
package/src/main.mjs CHANGED
@@ -26,13 +26,12 @@ export async function run(argv) {
26
26
 
27
27
  const config = loadConfig(cwd);
28
28
  const stateRoot = join(homedir(), ".march");
29
- const useRuntimeProcess = process.env.MARCH_RUNTIME_PROCESS !== "0";
30
29
  installNetworkEnvironment(config.network);
31
30
 
32
31
  const earlyCommand = await runEarlyCliCommand(args, { config, cwd, stateRoot });
33
32
  if (earlyCommand.handled) return earlyCommand.code;
34
33
 
35
- const app = await createCliAppRuntime({ args, config, cwd, argv, stateRoot, useRuntimeProcess });
34
+ const app = await createCliAppRuntime({ args, config, cwd, argv, stateRoot });
36
35
  if (!app.ok) return app.code;
37
36
 
38
37
  const gatewayDaemonCommand = await maybeRunGatewayDaemonCommand(args, {
@@ -78,6 +77,10 @@ export async function run(argv) {
78
77
  runner: app.runner,
79
78
  memoryStore: app.memoryStore,
80
79
  currentProject: app.currentProject,
80
+ currentProjectInfo: app.currentProjectInfo,
81
+ workspaceSupervisor: app.workspaceSupervisor,
82
+ workspaceOutputRouter: app.workspaceOutputRouter,
83
+ stateRoot,
81
84
  sessionState: app.sessionState,
82
85
  sessionsRoot: app.sessionsRoot,
83
86
  projectMarchDir: app.projectMarchDir,
@@ -12,6 +12,7 @@ export function createDesktopTurnNotifier({
12
12
  writeBell = () => process.stdout.write("\x07"),
13
13
  toastNotifier = nodeNotifier,
14
14
  config = {},
15
+ onActivation = null,
15
16
  } = {}) {
16
17
  const channels = resolveNotificationChannels(config);
17
18
  const minDurationMs = normalizeNonNegativeInteger(config.minDurationMs, 0);
@@ -31,7 +32,7 @@ export function createDesktopTurnNotifier({
31
32
  if (channels.desktop) {
32
33
  results.push({
33
34
  channel: "desktop",
34
- ...(await sendDesktopNotification({ platform, spawnProcess, toastNotifier, ...payload })),
35
+ ...(await sendDesktopNotification({ platform, spawnProcess, toastNotifier, onActivation, activation: normalizedEvent.activation, ...payload })),
35
36
  });
36
37
  }
37
38
  if (channels.bell) results.push({ channel: "bell", ...sendBellNotification({ writeBell }) });
@@ -52,14 +53,14 @@ export function createDesktopTurnNotifier({
52
53
  };
53
54
  }
54
55
 
55
- export async function sendDesktopNotification({ platform = process.platform, spawnProcess = spawn, toastNotifier = nodeNotifier, title, message, iconPath = DEFAULT_NOTIFICATION_ICON_PATH, sound = true }) {
56
+ export async function sendDesktopNotification({ platform = process.platform, spawnProcess = spawn, toastNotifier = nodeNotifier, title, message, iconPath = DEFAULT_NOTIFICATION_ICON_PATH, sound = true, activation = null, onActivation = null }) {
56
57
  if (platform !== "win32") return { ok: false, reason: "unsupported-platform" };
57
58
 
58
59
  const safeTitle = normalizeNotificationText(title) || "March";
59
60
  const safeMessage = normalizeNotificationText(message) || "Turn finished";
60
61
 
61
62
  try {
62
- const toastResult = await sendWindowsToastNotification({ toastNotifier, title: safeTitle, message: safeMessage, iconPath, sound });
63
+ const toastResult = await sendWindowsToastNotification({ toastNotifier, title: safeTitle, message: safeMessage, iconPath, sound, activation, onActivation });
63
64
  if (toastResult.ok) return { ok: true };
64
65
 
65
66
  const script = buildWindowsNotificationScript({ title: safeTitle, message: safeMessage, iconPath });
@@ -140,18 +141,19 @@ export function buildWindowsNotificationScript({ title, message, timeoutMs = DEF
140
141
  return buildWindowsBalloonScript({ title, message, timeoutMs, iconPath });
141
142
  }
142
143
 
143
- export function buildWindowsToastOptions({ title, message, iconPath = DEFAULT_NOTIFICATION_ICON_PATH, sound = true }) {
144
+ export function buildWindowsToastOptions({ title, message, iconPath = DEFAULT_NOTIFICATION_ICON_PATH, sound = true, activation = null }) {
144
145
  return {
145
146
  title,
146
147
  message,
147
148
  icon: iconPath,
148
149
  appID: "March",
149
150
  sound,
150
- wait: false,
151
+ wait: Boolean(activation),
152
+ ...(activation ? { activation } : {}),
151
153
  };
152
154
  }
153
155
 
154
- function sendWindowsToastNotification({ toastNotifier = nodeNotifier, title, message, iconPath, sound }) {
156
+ function sendWindowsToastNotification({ toastNotifier = nodeNotifier, title, message, iconPath, sound, activation = null, onActivation = null }) {
155
157
  return new Promise((resolve) => {
156
158
  const notify = toastNotifier?.notify;
157
159
  if (typeof notify !== "function") {
@@ -161,12 +163,13 @@ function sendWindowsToastNotification({ toastNotifier = nodeNotifier, title, mes
161
163
 
162
164
  let settled = false;
163
165
  const timeout = setTimeout(() => finish({ ok: false, reason: "toast-timeout" }), DEFAULT_BALLOON_TIMEOUT_MS + 5000);
164
- notify.call(toastNotifier, buildWindowsToastOptions({ title, message, iconPath, sound }), (err) => {
166
+ notify.call(toastNotifier, buildWindowsToastOptions({ title, message, iconPath, sound, activation }), (err, response, metadata) => {
165
167
  if (err) {
166
168
  finish({ ok: false, reason: err?.message ?? String(err) });
167
169
  return;
168
170
  }
169
- finish({ ok: true });
171
+ if (isNotificationActivation(response, metadata)) onActivation?.(activation);
172
+ finish({ ok: true, activated: isNotificationActivation(response, metadata) });
170
173
  });
171
174
 
172
175
  function finish(result) {
@@ -178,6 +181,11 @@ function sendWindowsToastNotification({ toastNotifier = nodeNotifier, title, mes
178
181
  });
179
182
  }
180
183
 
184
+ function isNotificationActivation(response, metadata) {
185
+ const text = `${response ?? ""} ${metadata?.activationType ?? ""} ${metadata?.activationKind ?? ""}`.toLowerCase();
186
+ return /activate|click|contentsclicked|buttonclicked/.test(text);
187
+ }
188
+
181
189
  function resolveNotificationChannels(config) {
182
190
  return {
183
191
  desktop: config.desktop !== false,
@@ -6,11 +6,11 @@ import { createWebSessionManager, resolveWorkspace } from "./session-manager.mjs
6
6
  const DEFAULT_HOST = "127.0.0.1";
7
7
  const DEFAULT_PORT = 4174;
8
8
 
9
- export async function runWebUiCommand(args, { config, cwd, stateRoot, useRuntimeProcess = true } = {}) {
9
+ export async function runWebUiCommand(args, { config, cwd, stateRoot } = {}) {
10
10
  const host = args.host ?? DEFAULT_HOST;
11
11
  assertLoopbackHost(host);
12
12
  const port = Number.parseInt(args.port ?? "", 10) || DEFAULT_PORT;
13
- const runtime = createWebSessionManager({ args, config, launchCwd: cwd, stateRoot, useRuntimeProcess });
13
+ const runtime = createWebSessionManager({ args, config, launchCwd: cwd, stateRoot });
14
14
  const initialWorkspace = resolveInitialWorkspace(args, cwd);
15
15
  if (initialWorkspace) await runtime.createSession(initialWorkspace);
16
16
 
@@ -0,0 +1 @@
1
+ @layer theme.palette{:root{--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light dark;--font-sans:"Segoe UI Variable Text", "Segoe UI Variable", "Segoe UI", -apple-system, BlinkMacSystemFont, Roboto, "Helvetica Neue", Arial, DengXian, "等线", "Microsoft YaHei UI", sans-serif;--font-mono:ui-monospace, "SFMono-Regular", "Cascadia Code", "JetBrains Mono", "Fira Code", Consolas, "Liberation Mono", Menlo, monospace;--font-weight-ui-normal:500;--font-weight-content-normal:600;--font-weight-ui-medium:600;--font-weight-ui-strong:700;--palette-white:#fff;--palette-slate-50:#f8fafc;--palette-slate-100:#f1f5f9;--palette-slate-200:#e2e8f0;--palette-slate-400:#94a3b8;--palette-slate-500:#64748b;--palette-slate-700:#334155;--palette-slate-900:#0f172a;--palette-slate-950:#020617;--palette-blue-400:#60a5fa;--palette-blue-600:#2563eb;--palette-green-700:#15803d;--palette-amber-600:#d97706;--palette-red-600:#dc2626}@media (prefers-color-scheme:dark){:root{--lightningcss-light: ;--lightningcss-dark:initial}}}@layer theme.semantic{:root{--color-bg-page:#f3f4f6;--color-bg-surface:var(--palette-white);--color-bg-sidebar:#ffffffbd;--color-bg-subtle:var(--palette-slate-50);--color-text-primary:var(--palette-slate-900);--color-text-muted:var(--palette-slate-500);--color-text-inverse:var(--palette-white);--color-border-subtle:#0f172a14;--color-border-strong:#0f172a24;--color-accent:var(--palette-blue-600);--color-accent-soft:#2563eb14;--color-success:var(--palette-green-700);--color-warning:var(--palette-amber-600);--color-danger:var(--palette-red-600);--color-hover:#0000000d;--color-shadow-drawer:#00000026;--color-scrim:#0000004d}@media (prefers-color-scheme:dark){:root{--color-bg-page:var(--palette-slate-900);--color-bg-surface:var(--palette-slate-950);--color-bg-sidebar:var(--palette-slate-900);--color-bg-subtle:var(--palette-slate-900);--color-text-primary:var(--palette-slate-50);--color-text-muted:var(--palette-slate-400);--color-border-subtle:#94a3b81f;--color-border-strong:#94a3b833;--color-accent:var(--palette-blue-400);--color-accent-soft:#60a5fa24;--color-hover:#94a3b814}}}@layer theme.component{:root{--shell-left-width:260px;--shell-right-width:280px;--shell-mobile-panel-width:75vw;--header-height:36px;--composer-min-height:46px;--radius-row:6px;--radius-control:8px;--radius-composer:12px;--space-panel:8px;--tree-indent:16px}}@layer base{*{box-sizing:border-box}html,body,#root{height:100%;margin:0;overflow:hidden}body{background:var(--color-bg-page);color:var(--color-text-primary);font-family:var(--font-sans);font-weight:var(--font-weight-ui-normal);-webkit-font-smoothing:antialiased;text-rendering:optimizelegibility}button,textarea{font:inherit}button{cursor:pointer}}@layer components{.app-shell{grid-template-columns:var(--shell-left-width) minmax(0, 1fr) var(--shell-right-width);background:var(--color-bg-page);height:100dvh;color:var(--color-text-primary);isolation:isolate;grid-template-rows:minmax(0,1fr) auto;grid-template-areas:"sidebar main right""sidebar footer right";display:grid;overflow:hidden}.panel{background:var(--color-bg-sidebar);min-height:0;overflow:hidden}.left-panel{border-right:1px solid var(--color-border-subtle);grid-area:sidebar}.right-panel{border-left:1px solid var(--color-border-subtle);flex-direction:column;grid-area:right;display:flex}.projects-header,.right-header,.main-header{height:var(--header-height);border-bottom:1px solid var(--color-border-subtle);background:var(--color-bg-sidebar);color:var(--color-text-muted);font-size:11px;font-weight:var(--font-weight-ui-medium);text-transform:uppercase;justify-content:space-between;align-items:center;padding:0 16px;display:flex}.projects-header h3{font:inherit;letter-spacing:.5px;margin:0}.menu-button{border-radius:var(--radius-control);width:28px;height:28px;color:var(--color-text-muted);background:0 0;border:0;justify-content:center;align-items:center;gap:3px;display:inline-flex}.menu-button span{background:currentColor;border-radius:999px;width:3.6px;height:3.6px}.menu-button:hover,.header-button:hover,.session-row:hover,.activity-row:hover{background:var(--color-hover)}.projects-body{height:calc(100% - var(--header-height));padding:var(--space-panel) 0;overflow:hidden}.project-tree-host{--trees-accent-override:var(--color-accent);--trees-bg-override:var(--color-bg-sidebar);--trees-bg-muted-override:var(--color-hover);--trees-border-color-override:var(--color-border-subtle);--trees-fg-override:var(--color-text-primary);--trees-fg-muted-override:var(--color-text-muted);--trees-selected-bg-override:var(--color-accent-soft);--trees-selected-fg-override:var(--color-accent);--trees-font-family-override:var(--font-sans);--trees-font-size-override:13px;--trees-border-radius-override:var(--radius-row);--trees-item-height:30px;--trees-item-padding-x-override:8px;--trees-item-margin-x-override:8px;--trees-item-row-gap-override:4px;--trees-level-gap-override:var(--tree-indent);--trees-padding-inline-override:0px;--trees-scrollbar-gutter-override:6px;height:100%}.timeline{background:var(--color-bg-surface);flex-direction:column;grid-area:main;min-width:0;min-height:0;display:flex;overflow:hidden}.main-header{background:var(--color-bg-surface)}.runtime-pill{color:var(--color-text-muted);background:var(--color-bg-subtle);border-radius:999px;padding:2px 7px;font-size:12px}.runtime-pill.connected{color:var(--color-accent);background:var(--color-accent-soft)}.timeline-scroll{flex:1;min-height:0;padding:16px 18px 24px;overflow:auto}.session-title{align-items:baseline;gap:8px;margin:0 0 14px 40px;display:flex}.session-title h1{font-size:17px;font-weight:var(--font-weight-ui-strong);letter-spacing:-.01em;margin:0}.session-title span{color:var(--color-text-muted);font-size:12px}.timeline-list{max-width:920px;margin:0 auto}.message-row{grid-template-columns:30px minmax(0,1fr);gap:10px;padding:10px 0;display:grid}.message-row+.message-row{border-top:1px solid var(--color-border-subtle)}.agent-dot{border-radius:var(--radius-control);background:var(--palette-slate-700);width:28px;height:28px;color:var(--color-text-inverse);place-items:center;font-size:12px;font-weight:800;display:grid}.agent-dot.march{background:var(--color-accent)}.message-body{min-width:0;color:var(--color-text-primary);font-size:14px;font-weight:var(--font-weight-content-normal);padding-top:3px;line-height:1.55}.message-body p{margin:0}.message-body time{color:var(--color-text-muted);margin-top:4px;font-size:11px;display:block}.timeline-aux{border:1px solid var(--color-border-subtle);border-radius:var(--radius-control);background:var(--color-bg-subtle);padding:7px 9px}.timeline-aux summary,.aux-title{align-items:center;gap:8px;min-height:20px;display:flex}.timeline-aux summary{cursor:pointer}.timeline-aux span,.timeline-aux em{color:var(--color-text-muted);font-size:12px;font-style:normal}.timeline-aux strong{text-overflow:ellipsis;white-space:nowrap;min-width:0;font-size:13px;font-weight:var(--font-weight-ui-medium);overflow:hidden}.timeline-aux p{color:var(--color-text-muted);margin:7px 0 0;font-size:13px}.tool-block span,.diff-inline,.terminal-block pre{font-family:var(--font-mono)}.diff-inline{border-left:2px solid var(--color-border-strong);margin-top:7px;padding-left:10px;font-size:13px}.diff-line{padding:2px 0}.diff-line.add{color:var(--color-success)}.diff-line.remove,.error-block strong{color:var(--color-danger)}.diff-line.keep{color:var(--color-text-muted)}.terminal-block pre{color:var(--color-text-muted);white-space:pre-wrap;margin:7px 0 0;font-size:12px}.right-body{flex:1;min-height:0;padding:12px;overflow:auto}.workspace-picker{gap:7px;padding-bottom:10px;display:grid}.workspace-picker label{color:var(--color-text-muted);font-size:11px;font-weight:var(--font-weight-ui-medium);text-transform:uppercase}.workspace-input-row{grid-template-columns:minmax(0,1fr) auto;gap:4px;display:grid}.workspace-input-row input{border:1px solid var(--color-border-subtle);border-radius:var(--radius-control);background:var(--color-bg-surface);min-width:0;color:var(--color-text-primary);padding:7px 8px}.workspace-input-row button,.fs-entry-row button,.fs-row{border-radius:var(--radius-control);color:var(--color-text-muted);background:0 0;border:0}.workspace-input-row button{background:var(--color-accent-soft);color:var(--color-accent);padding:0 8px}.workspace-input-row button:disabled{opacity:.45}.workspace-path{text-overflow:ellipsis;white-space:nowrap;min-width:0;color:var(--color-text-muted);font-size:12px;overflow:hidden}.fs-entry-row{grid-template-columns:minmax(0,1fr) auto;gap:4px;display:grid}.fs-entry-row button,.fs-row{text-align:left;min-height:28px;padding:0 7px}.fs-entry-row button:first-child{text-overflow:ellipsis;white-space:nowrap;color:var(--color-text-primary);overflow:hidden}.provider-quota{border-top:1px solid var(--color-border-subtle);gap:9px;margin-top:12px;padding-top:12px;display:grid}.provider-quota-header{justify-content:space-between;align-items:center;gap:8px;font-size:12px;display:flex}.provider-quota-header span{color:var(--color-text-primary);font-weight:var(--font-weight-ui-medium)}.provider-quota-header time,.quota-row em{color:var(--color-text-muted);font-style:normal}.quota-row{gap:5px;font-size:12px;display:grid}.quota-row-main{justify-content:space-between;align-items:center;gap:8px;display:flex}.quota-row-main span{color:var(--color-text-primary)}.quota-row-main strong{color:var(--color-accent);font-size:12px}.quota-bar{background:var(--color-bg-surface);height:6px;box-shadow:inset 0 0 0 1px var(--color-border-subtle);border-radius:999px;overflow:hidden}.quota-bar span{border-radius:inherit;background:linear-gradient(90deg, var(--color-accent), color-mix(in srgb, var(--color-accent) 72%, white));height:100%;display:block}.session-row,.activity-row{border-radius:var(--radius-control);width:100%;min-height:34px;color:var(--color-text-primary);text-align:left;background:0 0;border:0;justify-content:space-between;align-items:center;gap:8px;padding:7px 8px;font-size:13px;display:flex}.session-row.active{background:var(--color-accent-soft);color:var(--color-accent);font-weight:var(--font-weight-ui-medium)}.session-row time,.activity-row time{text-overflow:ellipsis;white-space:nowrap;min-width:0;color:var(--color-text-muted);font-size:12px;overflow:hidden}.right-divider{border-top:1px solid var(--color-border-subtle);color:var(--color-text-muted);font-size:11px;font-weight:var(--font-weight-ui-medium);text-transform:uppercase;margin:12px 0 4px;padding-top:12px}.activity-row{color:var(--color-text-muted)}.composer{background:var(--color-bg-surface);grid-area:footer;grid-template-columns:1fr;align-items:end;min-width:0;padding:0 16px 12px;display:grid}.composer-box{width:100%;min-height:var(--composer-min-height);border:1px solid var(--color-border-strong);border-radius:var(--radius-composer);background:var(--color-bg-sidebar);align-items:center;display:flex;position:relative;overflow:visible}.composer-box:focus-within{border-color:var(--color-accent);box-shadow:0 0 0 1px var(--color-accent-soft)}.composer textarea{resize:none;width:100%;max-height:160px;color:var(--color-text-primary);font-weight:var(--font-weight-content-normal);background:0 0;border:0;outline:0;padding:12px 124px 12px 12px}.composer-actions{align-items:center;gap:2px;display:flex;position:absolute;bottom:8px;right:8px}.session-ring,.chip-button,.icon-action,.send-icon,.mobile-toggle{border-radius:var(--radius-control);width:28px;height:28px;color:var(--color-text-muted);background:0 0;border:0;justify-content:center;align-items:center;display:inline-flex}.session-ring:before{content:"";border:2px solid var(--color-accent);border-radius:999px;width:14px;height:14px}.chip-button{width:auto;padding:0 8px;font-size:12px}.icon-action:hover,.chip-button:hover,.mobile-toggle:hover{background:var(--color-hover)}.send-icon{background:var(--color-accent);color:var(--color-text-inverse);font-size:16px;font-weight:700}.send-icon:disabled{opacity:.45}.mobile-toggle,.overlay{display:none}@media (width<=920px){html,body{width:100%;min-height:0;position:fixed;inset:0}.app-shell{height:100dvh;padding-top:env(safe-area-inset-top,0);flex-direction:column;display:flex}.timeline{flex:1;min-height:0}.composer{padding:0 1px max(2px, env(safe-area-inset-bottom));flex:none;grid-template-columns:30px minmax(0,1fr) 30px;gap:1px}.mobile-toggle{align-self:center;height:44px;display:inline-flex}.composer-box{border-radius:10px;min-height:44px}.composer textarea{padding-right:114px}.left-panel,.right-panel{top:env(safe-area-inset-top,0);z-index:30;width:var(--shell-mobile-panel-width);background:var(--color-bg-sidebar);box-shadow:4px 0 24px var(--color-shadow-drawer);transition:transform .22s cubic-bezier(.2,.8,.2,1);position:fixed;bottom:0;transform:translate(-100%)}.left-panel{border-top-right-radius:14px;border-bottom-right-radius:14px;left:0}.right-panel{box-shadow:-4px 0 24px var(--color-shadow-drawer);border-top-left-radius:14px;border-bottom-left-radius:14px;right:0;transform:translate(100%)}.app-shell[data-left-open=true] .left-panel,.app-shell[data-right-open=true] .right-panel{transform:translate(0)}.app-shell[data-left-open=true] .overlay,.app-shell[data-right-open=true] .overlay{z-index:20;background:var(--color-scrim);display:block;position:fixed;inset:0}}}