@tea-agent/loop-agent 0.33.0 → 0.33.1

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.
@@ -4,9 +4,10 @@
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
7
+ <link rel="stylesheet" href="/inspect/operator-chrome.css" />
7
8
  <title>Loop 操作台 · Operator Console</title>
8
- <script type="module" crossorigin src="/assets/index-3R-GT3a_.js"></script>
9
- <link rel="stylesheet" crossorigin href="/assets/index-B0EQt_yq.css">
9
+ <script type="module" crossorigin src="/assets/index-PzYzcuFG.js"></script>
10
+ <link rel="stylesheet" crossorigin href="/assets/index-CnUXAqxG.css">
10
11
  </head>
11
12
  <body>
12
13
  <div id="root"></div>
@@ -30,7 +30,8 @@ export function useRecoveryConsole(params) {
30
30
  const [inspectSplitOpen, setInspectSplitOpen] = useState(false);
31
31
  const inspectPath = useCallback(() => {
32
32
  if (dagRunId.trim()) {
33
- return `/inspect/#/dag/${encodeURIComponent(dagRunId.trim())}`;
33
+ // from=recovery 激活统一壳的对象上下文 chip 与显式返回路径
34
+ return `/inspect/#/dag/${encodeURIComponent(dagRunId.trim())}?from=recovery`;
34
35
  }
35
36
  return "/inspect/#/";
36
37
  }, [dagRunId]);
@@ -91,7 +92,7 @@ export function useRecoveryConsole(params) {
91
92
  const resolved = await resolveDagRunForInspect();
92
93
  if (!resolved)
93
94
  return;
94
- window.location.assign(`/inspect/#/dag/${encodeURIComponent(resolved)}`);
95
+ window.location.assign(`/inspect/#/dag/${encodeURIComponent(resolved)}?from=recovery`);
95
96
  }, [resolveDagRunForInspect]);
96
97
  const loadRecoveryRuns = useCallback(async () => {
97
98
  setRecoveryRunsLoading(true);
@@ -201,9 +201,9 @@ export async function serveObserveStatic(req, res, staticDir, options = {}) {
201
201
  const url = new URL(req.url ?? "/", "http://localhost");
202
202
  let pathname = decodeURIComponent(url.pathname);
203
203
  const urlPrefix = options.urlPrefix?.replace(/\/$/, "");
204
- const embeddedShell = Boolean(urlPrefix) &&
204
+ const withinPrefix = Boolean(urlPrefix) &&
205
205
  (pathname === urlPrefix || pathname.startsWith(`${urlPrefix}/`));
206
- if (urlPrefix && embeddedShell) {
206
+ if (urlPrefix && withinPrefix) {
207
207
  pathname = pathname.slice(urlPrefix.length) || "/";
208
208
  }
209
209
  if (pathname === "/") {
@@ -221,13 +221,8 @@ export async function serveObserveStatic(req, res, staticDir, options = {}) {
221
221
  sendJson(res, 404, { error: "Not found" });
222
222
  return;
223
223
  }
224
- let content = await readFile(resolvedFile);
224
+ const content = await readFile(resolvedFile);
225
225
  const contentType = contentTypeFor(pathname);
226
- if (embeddedShell && contentType.startsWith("text/html")) {
227
- content = Buffer.from(content
228
- .toString("utf8")
229
- .replace(/<body\b/i, '<body data-embedded-shell="true"'));
230
- }
231
226
  res.writeHead(200, {
232
227
  "Content-Type": contentType,
233
228
  "Content-Length": content.byteLength,
@@ -67,6 +67,7 @@ import {
67
67
  mergeSessionEvents,
68
68
  renderSessionTimeline,
69
69
  } from "./views/session-timeline.js";
70
+ import { mountOperatorChrome } from "./operator-chrome.js";
70
71
 
71
72
  // Compatibility re-exports for poll-stable UI helpers (owned by state.js / views):
72
73
  // reuse-dag-detail-shell, reuse-dag-graph-wrap, existingGraphWrap?.parentNode === ranksEl,
@@ -142,6 +143,16 @@ function route() {
142
143
  }
143
144
 
144
145
  if (typeof window !== "undefined") {
146
+ // 先同步挂载共享壳,再启动路由;首个 snapshot 的新鲜度不会因异步挂载丢失。
147
+ const chromeHost = document.getElementById("operator-chrome");
148
+ if (chromeHost) {
149
+ mountOperatorChrome(chromeHost, {
150
+ mode: "inspect",
151
+ liveLocation: true,
152
+ healthUrl: "/api/health",
153
+ readinessTitle: "Pi 状态(只读展示)。模型与认证管理在操作侧,点击进入。",
154
+ });
155
+ }
145
156
  window.addEventListener("hashchange", route);
146
157
  window.addEventListener("load", route);
147
158
  document.addEventListener("pointerdown", closeDagInspectorOnOutsideClick);
@@ -11,47 +11,14 @@
11
11
  href="https://cdn.jsdelivr.net/npm/remixicon@4.6.0/fonts/remixicon.css"
12
12
  />
13
13
  <link rel="stylesheet" href="styles.css" />
14
+ <link rel="stylesheet" href="operator-chrome.css" />
14
15
  </head>
15
16
  <body data-shell="observe-console">
16
17
  <a class="skip-link" href="#main-content">跳到主内容</a>
17
- <nav class="embedded-operator-nav" aria-label="Operator 导航">
18
- <a href="/#/tasks">返回操作台</a>
19
- <span aria-current="page">观测</span>
20
- </nav>
18
+ <!-- 统一 Operator Chrome(操作/观测同壳):由 operator-chrome.js 挂载 -->
19
+ <header id="operator-chrome"></header>
21
20
  <div id="app">
22
- <header class="site-header">
23
- <a href="#/" class="logo" aria-label="Loop Agent 运行看板首页">
24
- <i class="ri-radar-line" aria-hidden="true"></i>
25
- <span>Loop Agent</span>
26
- </a>
27
- <nav class="top-nav" aria-label="主导航">
28
- <a href="#/" class="nav-link"
29
- ><i class="ri-dashboard-3-line" aria-hidden="true"></i
30
- ><span>总览</span></a
31
- >
32
- <a href="#/pool" class="nav-link"
33
- ><i class="ri-database-2-line" aria-hidden="true"></i
34
- ><span>资源池</span></a
35
- >
36
- <a href="#/failures" class="nav-link"
37
- ><i class="ri-alarm-warning-line" aria-hidden="true"></i
38
- ><span>异常 Task</span></a
39
- >
40
- <a href="#/night" class="nav-link"
41
- ><i class="ri-moon-clear-line" aria-hidden="true"></i
42
- ><span>夜间任务</span></a
43
- >
44
- </nav>
45
- <div class="header-meta">
46
- <i class="ri-live-line" aria-hidden="true"></i>
47
- <span
48
- id="header-refresh"
49
- class="header-refresh"
50
- aria-live="polite"
51
- ></span>
52
- </div>
53
- <nav id="breadcrumb" class="breadcrumb" aria-label="面包屑"></nav>
54
- </header>
21
+ <nav id="breadcrumb" class="breadcrumb" aria-label="面包屑"></nav>
55
22
 
56
23
  <main id="main-content" class="dashboard-main" tabindex="-1">
57
24
  <section
@@ -63,12 +30,13 @@
63
30
  <div class="view-heading page-header-heading">
64
31
  <h2>运行总览</h2>
65
32
  </div>
66
- <div
67
- id="dashboard-repo"
68
- class="repo-banner repo-banner-inline"
69
- aria-label="当前仓库"
70
- ></div>
71
33
  </header>
34
+ <section
35
+ id="dashboard-night"
36
+ class="panel night-strip"
37
+ aria-label="昨夜班次"
38
+ hidden
39
+ ></section>
72
40
  <section
73
41
  id="dashboard-kpi"
74
42
  class="panel panel-kpi"
@@ -242,7 +210,7 @@
242
210
  >
243
211
  刷新
244
212
  </button>
245
- <a class="page-action-btn" href="/#/night">打开操作台 · 夜间</a>
213
+ <a class="page-action-btn" href="/#/night">在操作台管理 →</a>
246
214
  </div>
247
215
  </header>
248
216
  <section
@@ -1,7 +1,6 @@
1
- /** KPI cards, repo banner, projection fault, artifact/inbox links. */
1
+ /** KPI cards, projection fault, artifact/inbox links. */
2
2
  import { UI_TEXT } from "./constants.js";
3
- import { clearNode, el } from "./dom.js";
4
- import { formatRepoPathDisplay, repoBaseName } from "./format.js";
3
+ import { el } from "./dom.js";
5
4
  import { navigate } from "./router.js";
6
5
 
7
6
  export function failuresInboxLink(count) {
@@ -55,27 +54,6 @@ export function kpiCard(label, value, extraClass, helpText) {
55
54
  return card;
56
55
  }
57
56
 
58
- export function renderRepoBanner(repoEl, snapshot) {
59
- if (!repoEl) return;
60
- clearNode(repoEl);
61
- const repoRoot = snapshot.repoRoot ?? "";
62
- const iconWrap = el("span", "repo-banner-icon");
63
- const icon = el("i", "ri-git-repository-line");
64
- icon.setAttribute("aria-hidden", "true");
65
- iconWrap.appendChild(icon);
66
- repoEl.appendChild(iconWrap);
67
- const text = el("div", "repo-banner-text");
68
- text.appendChild(el("span", "repo-banner-name", repoBaseName(repoRoot)));
69
- const pathLine = el(
70
- "span",
71
- "repo-banner-path",
72
- formatRepoPathDisplay(repoRoot),
73
- );
74
- pathLine.title = repoRoot || "路径未知";
75
- text.appendChild(pathLine);
76
- repoEl.appendChild(text);
77
- }
78
-
79
57
  export function renderProjectionFault(error) {
80
58
  const banner = el("div", "projection-fault");
81
59
  banner.setAttribute("role", "alert");
@@ -0,0 +1,480 @@
1
+ /**
2
+ * Operator Chrome — 统一壳样式(Operate 与 Inspect 共用唯一来源)。
3
+ *
4
+ * 由统一 Console server 同时服务两侧:
5
+ * - Inspect(/inspect/)以相对路径引入本文件;
6
+ * - Operate(/)经 /inspect/operator-chrome.css 引入。
7
+ * 变量默认回退值与 observe/static/styles.css 及 console/static-src/styles.css
8
+ * 的暖白 token 保持一致;宿主样式表已定义同名变量时以宿主为准。
9
+ */
10
+
11
+ .och {
12
+ --och-canvas: var(--canvas, #f7f7f4);
13
+ --och-surface: var(--surface, #ffffff);
14
+ --och-surface-muted: var(--surface-muted, #f0efe9);
15
+ --och-ink: var(--ink, #26251e);
16
+ --och-body: var(--body, #5a5852);
17
+ --och-muted: var(--muted, #807d72);
18
+ --och-muted-soft: var(--muted-soft, #a09c92);
19
+ --och-hairline: var(--hairline, #e6e5e0);
20
+ --och-hairline-strong: var(--hairline-strong, #cfcdc4);
21
+ --och-orange: var(--orange, #f54e00);
22
+ --och-orange-active: var(--orange-active, #d04200);
23
+ --och-green: var(--green, #1f8a65);
24
+ --och-amber: var(--amber, #a66a00);
25
+ --och-red: var(--red, #c72d53);
26
+ --och-mint: var(--mint, #d8ead5);
27
+ --och-peach: var(--peach, #f1d8cb);
28
+ --och-mono: var(
29
+ --font-mono,
30
+ "JetBrains Mono",
31
+ ui-monospace,
32
+ SFMono-Regular,
33
+ Menlo,
34
+ Consolas,
35
+ monospace
36
+ );
37
+ --och-shell-max: var(--shell-max, 1680px);
38
+
39
+ position: sticky;
40
+ top: 0;
41
+ z-index: 50;
42
+ border-bottom: 1px solid var(--och-hairline);
43
+ background: color-mix(in srgb, var(--och-canvas) 88%, transparent);
44
+ backdrop-filter: blur(12px);
45
+ -webkit-backdrop-filter: blur(12px);
46
+ font-family: inherit;
47
+ }
48
+
49
+ .och-inner {
50
+ width: min(100% - 48px, var(--och-shell-max));
51
+ margin: 0 auto;
52
+ }
53
+
54
+ /* Band 1 — 全局带:品牌 / 仓库身份 / 模式 / 对象上下文 / 状态格 */
55
+ .och-global {
56
+ position: relative;
57
+ display: flex;
58
+ align-items: center;
59
+ gap: 12px;
60
+ height: 54px;
61
+ }
62
+
63
+ .och-brand {
64
+ display: inline-flex;
65
+ align-items: center;
66
+ gap: 8px;
67
+ font-size: 14px;
68
+ font-weight: 700;
69
+ letter-spacing: 0.01em;
70
+ color: var(--och-ink);
71
+ text-decoration: none;
72
+ white-space: nowrap;
73
+ }
74
+ .och-brand svg {
75
+ color: var(--och-orange);
76
+ flex-shrink: 0;
77
+ }
78
+ .och-brand:hover {
79
+ color: var(--och-orange-active);
80
+ }
81
+
82
+ .och-repo {
83
+ appearance: none;
84
+ display: inline-flex;
85
+ align-items: center;
86
+ gap: 7px;
87
+ min-height: 30px;
88
+ padding: 0 11px;
89
+ border: 1px solid var(--och-hairline);
90
+ border-radius: 999px;
91
+ background: var(--och-surface);
92
+ color: var(--och-body);
93
+ font-size: 12px;
94
+ font-family: inherit;
95
+ white-space: nowrap;
96
+ cursor: pointer;
97
+ transition:
98
+ border-color 0.15s ease,
99
+ color 0.15s ease;
100
+ }
101
+ .och-repo:hover {
102
+ border-color: var(--och-hairline-strong);
103
+ color: var(--och-ink);
104
+ }
105
+ .och-repo svg {
106
+ color: var(--och-muted);
107
+ }
108
+ .och-repo-fp {
109
+ font-family: var(--och-mono);
110
+ font-size: 11px;
111
+ color: var(--och-muted);
112
+ }
113
+
114
+ .och-pop {
115
+ position: absolute;
116
+ top: 50px;
117
+ left: 150px;
118
+ z-index: 60;
119
+ min-width: 300px;
120
+ padding: 14px 16px;
121
+ border: 1px solid var(--och-hairline);
122
+ border-radius: 14px;
123
+ background: var(--och-surface);
124
+ box-shadow: 0 8px 32px rgba(38, 37, 30, 0.14);
125
+ font-size: 12px;
126
+ color: var(--och-body);
127
+ }
128
+ .och-pop[hidden] {
129
+ display: none;
130
+ }
131
+ .och-pop-title {
132
+ font-size: 13px;
133
+ font-weight: 700;
134
+ color: var(--och-ink);
135
+ margin: 0 0 8px;
136
+ }
137
+ .och-pop-kv {
138
+ display: flex;
139
+ justify-content: space-between;
140
+ gap: 12px;
141
+ padding: 4px 0;
142
+ }
143
+ .och-pop-kv b {
144
+ font-weight: 500;
145
+ color: var(--och-muted);
146
+ }
147
+ .och-pop-kv span {
148
+ font-family: var(--och-mono);
149
+ font-size: 11.5px;
150
+ color: var(--och-ink);
151
+ text-align: right;
152
+ word-break: break-all;
153
+ }
154
+
155
+ /* 模式分段控件:全局唯一模式入口(重语法:ink 实底) */
156
+ .och-mode {
157
+ display: inline-flex;
158
+ padding: 3px;
159
+ border: 1px solid var(--och-hairline);
160
+ border-radius: 999px;
161
+ background: var(--och-surface-muted);
162
+ flex-shrink: 0;
163
+ }
164
+ .och-mode-btn {
165
+ display: inline-flex;
166
+ align-items: center;
167
+ justify-content: center;
168
+ gap: 6px;
169
+ min-width: 92px;
170
+ height: 30px;
171
+ padding: 0 14px;
172
+ border-radius: 999px;
173
+ font-size: 13px;
174
+ font-weight: 600;
175
+ color: var(--och-body);
176
+ text-decoration: none;
177
+ transition:
178
+ color 0.18s ease,
179
+ background-color 0.18s ease,
180
+ box-shadow 0.18s ease;
181
+ }
182
+ .och-mode-btn svg {
183
+ opacity: 0.85;
184
+ }
185
+ .och-mode-btn:hover {
186
+ color: var(--och-ink);
187
+ }
188
+ .och-mode-btn.och-on,
189
+ .och-mode-btn.och-on:hover {
190
+ background: var(--och-ink);
191
+ color: #fff;
192
+ box-shadow: 0 2px 8px rgba(38, 37, 30, 0.25);
193
+ }
194
+
195
+ /* 对象上下文 chip:跨模式选中连续性 + 显式返回路径 */
196
+ .och-context {
197
+ display: inline-flex;
198
+ align-items: center;
199
+ gap: 8px;
200
+ min-height: 30px;
201
+ padding: 0 6px 0 12px;
202
+ border: 1px solid
203
+ color-mix(in srgb, var(--och-orange) 38%, var(--och-hairline));
204
+ border-radius: 999px;
205
+ background: color-mix(in srgb, var(--och-peach) 42%, var(--och-surface));
206
+ font-size: 12px;
207
+ color: var(--och-ink);
208
+ white-space: nowrap;
209
+ }
210
+ .och-context[hidden] {
211
+ display: none;
212
+ }
213
+ .och-context-kind {
214
+ color: var(--och-orange-active);
215
+ font-weight: 700;
216
+ }
217
+ .och-context-id {
218
+ font-family: var(--och-mono);
219
+ font-size: 11px;
220
+ color: var(--och-body);
221
+ max-width: 260px;
222
+ overflow: hidden;
223
+ text-overflow: ellipsis;
224
+ }
225
+ .och-context-return {
226
+ display: inline-flex;
227
+ align-items: center;
228
+ gap: 4px;
229
+ height: 22px;
230
+ padding: 0 9px;
231
+ border-radius: 999px;
232
+ background: var(--och-ink);
233
+ color: #fff;
234
+ font-size: 11px;
235
+ font-weight: 600;
236
+ text-decoration: none;
237
+ }
238
+ .och-context-return:hover {
239
+ background: var(--och-orange-active);
240
+ }
241
+ .och-context-close {
242
+ appearance: none;
243
+ display: inline-flex;
244
+ align-items: center;
245
+ justify-content: center;
246
+ width: 20px;
247
+ height: 20px;
248
+ padding: 0;
249
+ border: none;
250
+ border-radius: 50%;
251
+ background: transparent;
252
+ color: var(--och-muted);
253
+ cursor: pointer;
254
+ }
255
+ .och-context-close:hover {
256
+ background: rgba(38, 37, 30, 0.08);
257
+ color: var(--och-ink);
258
+ }
259
+
260
+ .och-spacer {
261
+ flex: 1 1 auto;
262
+ }
263
+
264
+ /* 新鲜度格(Inspect 模式渲染;元素 id 保持 header-refresh 供轮询回写) */
265
+ .och-fresh {
266
+ display: inline-flex;
267
+ align-items: center;
268
+ gap: 7px;
269
+ min-height: 30px;
270
+ padding: 0 12px;
271
+ border: 1px solid var(--och-hairline);
272
+ border-radius: 999px;
273
+ background: var(--och-surface);
274
+ color: var(--och-muted);
275
+ font-size: 12px;
276
+ font-variant-numeric: tabular-nums;
277
+ white-space: nowrap;
278
+ }
279
+ .och-fresh-dot {
280
+ width: 7px;
281
+ height: 7px;
282
+ border-radius: 50%;
283
+ background: var(--och-green);
284
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--och-green) 18%, transparent);
285
+ flex-shrink: 0;
286
+ }
287
+
288
+ /* Pi readiness:三态语义色与现行 Console 一致 */
289
+ .och-pi {
290
+ display: inline-flex;
291
+ align-items: center;
292
+ gap: 7px;
293
+ min-height: 30px;
294
+ padding: 0 12px;
295
+ border-radius: 999px;
296
+ border: 1px solid var(--och-hairline);
297
+ background: var(--och-surface);
298
+ font-size: 12px;
299
+ color: var(--och-body);
300
+ text-decoration: none;
301
+ white-space: nowrap;
302
+ transition: border-color 0.15s ease;
303
+ }
304
+ .och-pi:hover {
305
+ border-color: var(--och-hairline-strong);
306
+ }
307
+ .och-pi-label {
308
+ font-family: var(--och-mono);
309
+ font-size: 11px;
310
+ font-weight: 700;
311
+ color: var(--och-ink);
312
+ }
313
+ .och-pi strong {
314
+ font-weight: 600;
315
+ }
316
+ .och-pi[data-state="ready"] {
317
+ border-color: color-mix(in srgb, var(--och-green) 34%, var(--och-hairline));
318
+ background: color-mix(in srgb, var(--och-mint) 46%, var(--och-surface));
319
+ }
320
+ .och-pi[data-state="ready"] strong {
321
+ color: var(--och-green);
322
+ }
323
+ .och-pi[data-state="degraded"] {
324
+ border-color: color-mix(in srgb, var(--och-amber) 38%, var(--och-hairline));
325
+ background: color-mix(in srgb, var(--och-peach) 40%, var(--och-surface));
326
+ }
327
+ .och-pi[data-state="degraded"] strong {
328
+ color: var(--och-amber);
329
+ }
330
+ .och-pi[data-state="setup-required"],
331
+ .och-pi[data-state="unknown"] {
332
+ border-color: color-mix(in srgb, var(--och-red) 30%, var(--och-hairline));
333
+ background: color-mix(in srgb, var(--och-red) 5%, var(--och-surface));
334
+ }
335
+ .och-pi[data-state="setup-required"] strong,
336
+ .och-pi[data-state="unknown"] strong {
337
+ color: var(--och-red);
338
+ }
339
+
340
+ /* Band 2 — 工作区带:下划线 tab(轻语法,从属于模式控件) */
341
+ .och-workspace {
342
+ display: flex;
343
+ align-items: center;
344
+ gap: 16px;
345
+ height: 44px;
346
+ }
347
+ .och-tabs {
348
+ display: flex;
349
+ align-items: stretch;
350
+ gap: 2px;
351
+ height: 100%;
352
+ }
353
+ .och-tab {
354
+ position: relative;
355
+ display: inline-flex;
356
+ align-items: center;
357
+ padding: 0 13px;
358
+ color: var(--och-muted);
359
+ font-size: 13.5px;
360
+ font-weight: 500;
361
+ text-decoration: none;
362
+ white-space: nowrap;
363
+ transition: color 0.15s ease;
364
+ }
365
+ .och-tab:hover {
366
+ color: var(--och-ink);
367
+ }
368
+ .och-tab.och-tab-active {
369
+ color: var(--och-ink);
370
+ font-weight: 600;
371
+ }
372
+ .och-tab.och-tab-active::after {
373
+ content: "";
374
+ position: absolute;
375
+ left: 10px;
376
+ right: 10px;
377
+ bottom: -1px;
378
+ height: 2px;
379
+ border-radius: 2px;
380
+ background: var(--och-orange);
381
+ }
382
+
383
+ .och a:focus-visible,
384
+ .och button:focus-visible,
385
+ .operator-chrome-fallback button:focus-visible {
386
+ outline: 2px solid color-mix(in srgb, var(--och-orange) 55%, transparent);
387
+ outline-offset: 2px;
388
+ }
389
+
390
+ .operator-chrome-fallback {
391
+ display: flex;
392
+ align-items: center;
393
+ gap: 10px;
394
+ min-height: 54px;
395
+ padding: 0 24px;
396
+ border-bottom: 1px solid var(--hairline, #e6e5e0);
397
+ background: var(--canvas, #f7f7f4);
398
+ }
399
+ .operator-chrome-fallback button {
400
+ appearance: none;
401
+ padding: 6px 10px;
402
+ border: 1px solid var(--hairline, #e6e5e0);
403
+ border-radius: 999px;
404
+ background: var(--surface, #fff);
405
+ color: var(--ink, #26251e);
406
+ font: inherit;
407
+ cursor: pointer;
408
+ }
409
+ .operator-chrome-fallback [role="status"] {
410
+ color: var(--red, #c72d53);
411
+ font-size: 12px;
412
+ }
413
+
414
+ @media (max-width: 1100px) {
415
+ .och-repo-fp {
416
+ display: none;
417
+ }
418
+ .och-mode-btn {
419
+ min-width: 0;
420
+ }
421
+ .och-workspace {
422
+ overflow-x: auto;
423
+ }
424
+ .och-global {
425
+ gap: 8px;
426
+ }
427
+ .och-fresh,
428
+ .och-pi {
429
+ padding-inline: 9px;
430
+ }
431
+ .och-fresh-text,
432
+ .och-pi strong {
433
+ position: absolute;
434
+ width: 1px;
435
+ height: 1px;
436
+ padding: 0;
437
+ margin: -1px;
438
+ overflow: hidden;
439
+ clip: rect(0, 0, 0, 0);
440
+ white-space: nowrap;
441
+ border: 0;
442
+ }
443
+ }
444
+
445
+ @media (max-width: 760px) {
446
+ .och-inner {
447
+ width: min(100% - 24px, var(--och-shell-max));
448
+ }
449
+ .och-global {
450
+ gap: 6px;
451
+ overflow-x: auto;
452
+ scrollbar-width: none;
453
+ }
454
+ .och-global::-webkit-scrollbar {
455
+ display: none;
456
+ }
457
+ .och-brand > span:last-child {
458
+ display: none;
459
+ }
460
+ .och-repo-name {
461
+ max-width: 120px;
462
+ overflow: hidden;
463
+ text-overflow: ellipsis;
464
+ }
465
+ .och-mode-btn {
466
+ padding-inline: 10px;
467
+ }
468
+ .och-tab {
469
+ padding-inline: 10px;
470
+ }
471
+ }
472
+
473
+ @media (prefers-reduced-motion: reduce) {
474
+ .och-mode-btn,
475
+ .och-tab,
476
+ .och-repo,
477
+ .och-pi {
478
+ transition: none;
479
+ }
480
+ }