march-cli 0.1.35 → 0.1.36

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 (42) hide show
  1. package/package.json +1 -1
  2. package/src/agent/code-search/cache.mjs +133 -0
  3. package/src/agent/code-search/chunk-rules.mjs +107 -0
  4. package/src/agent/code-search/chunker.mjs +125 -0
  5. package/src/agent/code-search/engine.mjs +109 -0
  6. package/src/agent/code-search/languages.mjs +25 -0
  7. package/src/agent/code-search/parser-pool.mjs +29 -0
  8. package/src/agent/code-search/rerank.mjs +43 -0
  9. package/src/agent/code-search/retrieval/bm25.mjs +47 -0
  10. package/src/agent/code-search/retrieval/fusion.mjs +18 -0
  11. package/src/agent/code-search/retrieval/model2vec.mjs +96 -0
  12. package/src/agent/code-search/retrieval/safetensors.mjs +49 -0
  13. package/src/agent/code-search/retrieval/vector.mjs +107 -0
  14. package/src/agent/code-search/retrieval/wordpiece.mjs +82 -0
  15. package/src/agent/code-search/scanner.mjs +84 -0
  16. package/src/agent/code-search/tokenize.mjs +16 -0
  17. package/src/agent/code-search/tool.mjs +75 -0
  18. package/src/agent/runner/provider-quota-runtime.mjs +38 -0
  19. package/src/agent/runner.mjs +5 -0
  20. package/src/agent/runtime/remote-runner-client.mjs +2 -0
  21. package/src/agent/runtime/runner-ipc-target.mjs +7 -0
  22. package/src/agent/runtime/state/runner-state.mjs +1 -0
  23. package/src/agent/runtime/ui-event-bridge.mjs +2 -0
  24. package/src/agent/tools.mjs +3 -0
  25. package/src/cli/commands/registry/slash-command-registry.mjs +10 -7
  26. package/src/cli/commands/status-command.mjs +61 -35
  27. package/src/context/system-core/base.md +5 -0
  28. package/src/provider/quota/codex.mjs +278 -0
  29. package/src/provider/quota/index.mjs +46 -0
  30. package/src/provider/quota/transport-observer.mjs +99 -0
  31. package/src/web-ui/runtime-host.mjs +3 -0
  32. package/src/web-ui/server.mjs +1 -0
  33. package/src/web-ui/session-manager.mjs +2 -0
  34. package/src/web-ui/src/components/AppShell.tsx +1 -0
  35. package/src/web-ui/src/components/RightSidebar.tsx +47 -2
  36. package/src/web-ui/src/model.ts +20 -0
  37. package/src/web-ui/src/runtime/client.ts +8 -1
  38. package/src/web-ui/src/runtime/useWebRuntime.ts +13 -1
  39. package/src/web-ui/src/styles/shell.css +10 -0
  40. package/src/web-ui/dist/assets/index-BUmhnID4.css +0 -1
  41. package/src/web-ui/dist/assets/index-CtuqTjcB.js +0 -1845
  42. package/src/web-ui/dist/index.html +0 -13
@@ -1,5 +1,5 @@
1
1
  import { useState } from "react";
2
- import type { ActivityEvent, SessionSummary } from "../model";
2
+ import type { ActivityEvent, ProviderQuotaSnapshot, SessionSummary } from "../model";
3
3
  import type { FsEntry } from "../runtime/client";
4
4
 
5
5
  type RightSidebarProps = {
@@ -7,6 +7,7 @@ type RightSidebarProps = {
7
7
  activity: ActivityEvent[];
8
8
  fsEntries: FsEntry[];
9
9
  fsPath: string | null;
10
+ providerQuota?: ProviderQuotaSnapshot | null;
10
11
  running: boolean;
11
12
  onOpenSession: (sessionId: string) => Promise<void>;
12
13
  onCreateSession: (workspacePath: string) => Promise<void>;
@@ -14,7 +15,9 @@ type RightSidebarProps = {
14
15
  onBrowsePath: (path: string) => Promise<void>;
15
16
  };
16
17
 
17
- export function RightSidebar({ sessions, activity, fsEntries, fsPath, running, onOpenSession, onCreateSession, onBrowseRoots, onBrowsePath }: RightSidebarProps) {
18
+ export function RightSidebar(props: RightSidebarProps) {
19
+ const { sessions, activity, fsEntries, fsPath, providerQuota, running } = props;
20
+ const { onOpenSession, onCreateSession, onBrowseRoots, onBrowsePath } = props;
18
21
  const [workspacePath, setWorkspacePath] = useState("");
19
22
  const canCreate = workspacePath.trim().length > 0 && !running;
20
23
 
@@ -50,6 +53,8 @@ export function RightSidebar({ sessions, activity, fsEntries, fsPath, running, o
50
53
  ))}
51
54
  </div>
52
55
 
56
+ {providerQuota ? <ProviderQuotaCard quota={providerQuota} /> : null}
57
+
53
58
  <div className="right-divider">Sessions</div>
54
59
  {sessions.map((session) => (
55
60
  <button key={session.id} className={session.active ? "session-row active" : "session-row"} type="button" onClick={() => onOpenSession(session.id)}>
@@ -68,3 +73,43 @@ export function RightSidebar({ sessions, activity, fsEntries, fsPath, running, o
68
73
  </aside>
69
74
  );
70
75
  }
76
+
77
+ function ProviderQuotaCard({ quota }: { quota: ProviderQuotaSnapshot }) {
78
+ return (
79
+ <div className="provider-quota" aria-label="Provider quota">
80
+ <div className="provider-quota-header">
81
+ <span>{quota.label}</span>
82
+ <time>{quota.providerId}</time>
83
+ </div>
84
+ {quota.limits.flatMap((limit) => limit.windows.map((window) => {
85
+ const left = Math.round(window.remainingPercent);
86
+ return (
87
+ <div key={`${limit.id}:${window.id}`} className="quota-row">
88
+ <div className="quota-row-main">
89
+ <span>{formatQuotaLabel(window.label)}</span>
90
+ <strong>{left}% left</strong>
91
+ </div>
92
+ <div className="quota-bar" aria-label={`${left}% quota left`}>
93
+ <span style={{ width: `${Math.max(0, Math.min(100, left))}%` }} />
94
+ </div>
95
+ <em>{formatReset(window.resetsAt)}</em>
96
+ </div>
97
+ );
98
+ }))}
99
+ </div>
100
+ );
101
+ }
102
+
103
+ function formatQuotaLabel(label: string) {
104
+ return label === "weekly" ? "Weekly limit:" : `${label} limit:`;
105
+ }
106
+
107
+ function formatReset(resetsAt?: string | null) {
108
+ if (!resetsAt) return "reset unknown";
109
+ const date = new Date(resetsAt);
110
+ if (Number.isNaN(date.getTime())) return "reset unknown";
111
+ const hours = String(date.getHours()).padStart(2, "0");
112
+ const minutes = String(date.getMinutes()).padStart(2, "0");
113
+ const month = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"][date.getMonth()];
114
+ return `resets ${hours}:${minutes} on ${date.getDate()} ${month}`;
115
+ }
@@ -43,6 +43,25 @@ export type ActivityEvent = {
43
43
  time: string;
44
44
  };
45
45
 
46
+ export type ProviderQuotaSnapshot = {
47
+ providerId: string;
48
+ modelId?: string | null;
49
+ label: string;
50
+ planType?: string | null;
51
+ capturedAt: string;
52
+ limits: Array<{
53
+ id: string;
54
+ name: string;
55
+ windows: Array<{
56
+ id: string;
57
+ label: string;
58
+ usedPercent: number;
59
+ remainingPercent: number;
60
+ resetsAt?: string | null;
61
+ }>;
62
+ }>;
63
+ };
64
+
46
65
  export type ComposerState = {
47
66
  mode: string;
48
67
  placeholder: string;
@@ -57,6 +76,7 @@ export type WebUiModel = {
57
76
  events: MarchTimelineEvent[];
58
77
  };
59
78
  sessions: SessionSummary[];
79
+ providerQuota?: ProviderQuotaSnapshot | null;
60
80
  activity: ActivityEvent[];
61
81
  composer: ComposerState;
62
82
  };
@@ -1,4 +1,4 @@
1
- import type { SessionSummary, WebUiModel } from "../model";
1
+ import type { ProviderQuotaSnapshot, SessionSummary, WebUiModel } from "../model";
2
2
 
3
3
  export type RuntimeUiEvent =
4
4
  | { type: "web_user_message"; text: string }
@@ -13,6 +13,7 @@ export type RuntimeUiEvent =
13
13
  | { type: "tool_end"; name: string; isError?: boolean; result?: unknown }
14
14
  | { type: "edit_diff"; path: string; diffLines?: Array<{ type?: string; text?: string }> }
15
15
  | { type: "permission_request"; toolName: string; category?: string; params?: unknown }
16
+ | { type: "provider_quota_snapshot"; snapshot: ProviderQuotaSnapshot | null }
16
17
  | { type: "status"; text: string }
17
18
  | { type: "retry_start"; errorMessage?: string }
18
19
  | { type: "retry_end"; success?: boolean; finalError?: string };
@@ -47,6 +48,12 @@ export async function fetchFsList(path: string): Promise<FsEntry[]> {
47
48
  return (await response.json()).entries;
48
49
  }
49
50
 
51
+ export async function fetchProviderQuota(sessionId: string): Promise<ProviderQuotaSnapshot | null> {
52
+ const response = await fetch(apiPath("/api/provider-quota", { sessionId }));
53
+ if (!response.ok) throw new Error(await response.text());
54
+ return (await response.json()).snapshot ?? null;
55
+ }
56
+
50
57
  export async function submitRuntimeTurn(sessionId: string, prompt: string) {
51
58
  const response = await fetch("/api/turn", {
52
59
  method: "POST",
@@ -2,7 +2,7 @@ import { useEffect, useRef, useState } from "react";
2
2
  import { mockWebUiModel } from "../mockData";
3
3
  import type { MarchTimelineEvent, WebUiModel } from "../model";
4
4
  import { applyRuntimeEvent } from "./runtimeTimeline";
5
- import { connectRuntimeEvents, createRuntimeSession, fetchFsList, fetchFsRoots, fetchRuntimeSnapshot, submitRuntimeTurn } from "./client";
5
+ import { connectRuntimeEvents, createRuntimeSession, fetchFsList, fetchFsRoots, fetchProviderQuota, fetchRuntimeSnapshot, submitRuntimeTurn } from "./client";
6
6
  import type { FsEntry } from "./client";
7
7
 
8
8
  export type WebRuntimeState = {
@@ -48,6 +48,12 @@ export function useWebRuntime(): WebRuntimeState {
48
48
  activeSessionId,
49
49
  (event) => {
50
50
  setConnected(true);
51
+ if (event.type === "provider_quota_snapshot") {
52
+ setModel((current) => current.activeSessionId === activeSessionId
53
+ ? { ...current, providerQuota: event.snapshot }
54
+ : current);
55
+ return;
56
+ }
51
57
  setModel((current) => {
52
58
  const next = updateTimelineEvents(current, (events) => applyRuntimeEvent(events, event));
53
59
  rememberTimeline(next, timelineCache.current);
@@ -56,6 +62,7 @@ export function useWebRuntime(): WebRuntimeState {
56
62
  },
57
63
  () => setConnected(false),
58
64
  );
65
+ refreshProviderQuota(activeSessionId).catch(() => undefined);
59
66
  return disconnect;
60
67
  }, [activeSessionId]);
61
68
 
@@ -79,6 +86,11 @@ export function useWebRuntime(): WebRuntimeState {
79
86
  }
80
87
  }
81
88
 
89
+ async function refreshProviderQuota(sessionId: string) {
90
+ const providerQuota = await fetchProviderQuota(sessionId);
91
+ setModel((current) => current.activeSessionId === sessionId ? { ...current, providerQuota } : current);
92
+ }
93
+
82
94
  async function browseRoots() {
83
95
  setFsPath(null);
84
96
  setFsEntries(await fetchFsRoots());
@@ -110,6 +110,16 @@
110
110
  .fs-entry-row { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 4px; }
111
111
  .fs-entry-row button, .fs-row { min-height: 28px; padding: 0 7px; text-align: left; }
112
112
  .fs-entry-row button:first-child { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--color-text-primary); }
113
+ .provider-quota { margin-top: 12px; border-top: 1px solid var(--color-border-subtle); padding-top: 12px; display: grid; gap: 9px; }
114
+ .provider-quota-header { display: flex; align-items: center; justify-content: space-between; gap: 8px; font-size: 12px; }
115
+ .provider-quota-header span { color: var(--color-text-primary); font-weight: var(--font-weight-ui-medium); }
116
+ .provider-quota-header time, .quota-row em { color: var(--color-text-muted); font-style: normal; }
117
+ .quota-row { display: grid; gap: 5px; font-size: 12px; }
118
+ .quota-row-main { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
119
+ .quota-row-main span { color: var(--color-text-primary); }
120
+ .quota-row-main strong { color: var(--color-accent); font-size: 12px; }
121
+ .quota-bar { height: 6px; overflow: hidden; border-radius: 999px; background: var(--color-bg-surface); box-shadow: inset 0 0 0 1px var(--color-border-subtle); }
122
+ .quota-bar span { display: block; height: 100%; border-radius: inherit; background: linear-gradient(90deg, var(--color-accent), color-mix(in srgb, var(--color-accent) 72%, white)); }
113
123
  .session-row, .activity-row { width: 100%; min-height: 34px; border: 0; border-radius: var(--radius-control); padding: 7px 8px; background: transparent; color: var(--color-text-primary); display: flex; align-items: center; justify-content: space-between; gap: 8px; text-align: left; font-size: 13px; }
114
124
  .session-row.active { background: var(--color-accent-soft); color: var(--color-accent); font-weight: var(--font-weight-ui-medium); }
115
125
  .session-row time, .activity-row time { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--color-text-muted); font-size: 12px; }
@@ -1 +0,0 @@
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}.permission-block.pending strong{color:var(--color-warning)}.permission-block.approved strong{color:var(--color-success)}.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}.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}}}