codexmate 0.0.19 → 0.0.21

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 (102) hide show
  1. package/README.en.md +349 -255
  2. package/README.md +284 -248
  3. package/cli/agents-files.js +162 -0
  4. package/cli/archive-helpers.js +446 -0
  5. package/cli/auth-profiles.js +359 -0
  6. package/cli/builtin-proxy.js +580 -0
  7. package/cli/claude-proxy.js +998 -0
  8. package/cli/config-bootstrap.js +384 -0
  9. package/cli/config-health.js +338 -0
  10. package/cli/openclaw-config.js +629 -0
  11. package/cli/skills.js +1141 -0
  12. package/cli/zip-commands.js +510 -0
  13. package/cli.js +13129 -12973
  14. package/lib/cli-file-utils.js +151 -151
  15. package/lib/cli-models-utils.js +419 -152
  16. package/lib/cli-network-utils.js +164 -148
  17. package/lib/cli-path-utils.js +69 -0
  18. package/lib/cli-session-utils.js +121 -121
  19. package/lib/cli-sessions.js +386 -0
  20. package/lib/cli-utils.js +155 -155
  21. package/lib/download-artifacts.js +77 -0
  22. package/lib/mcp-stdio.js +440 -440
  23. package/lib/task-orchestrator.js +869 -0
  24. package/lib/text-diff.js +303 -303
  25. package/lib/workflow-engine.js +340 -340
  26. package/package.json +74 -63
  27. package/res/json5.min.js +1 -1
  28. package/res/vue.global.prod.js +13 -0
  29. package/web-ui/app.js +530 -5548
  30. package/web-ui/index.html +33 -2246
  31. package/web-ui/logic.agents-diff.mjs +386 -0
  32. package/web-ui/logic.claude.mjs +168 -0
  33. package/web-ui/logic.mjs +5 -793
  34. package/web-ui/logic.runtime.mjs +124 -0
  35. package/web-ui/logic.sessions.mjs +581 -0
  36. package/web-ui/modules/api.mjs +90 -0
  37. package/web-ui/modules/app.computed.dashboard.mjs +113 -0
  38. package/web-ui/modules/app.computed.index.mjs +15 -0
  39. package/web-ui/modules/app.computed.main-tabs.mjs +195 -0
  40. package/web-ui/modules/app.computed.session.mjs +507 -0
  41. package/web-ui/modules/app.constants.mjs +15 -0
  42. package/web-ui/modules/app.methods.agents.mjs +493 -0
  43. package/web-ui/modules/app.methods.claude-config.mjs +174 -0
  44. package/web-ui/modules/app.methods.codex-config.mjs +640 -0
  45. package/web-ui/modules/app.methods.index.mjs +88 -0
  46. package/web-ui/modules/app.methods.install.mjs +149 -0
  47. package/web-ui/modules/app.methods.navigation.mjs +619 -0
  48. package/web-ui/modules/app.methods.openclaw-core.mjs +814 -0
  49. package/web-ui/modules/app.methods.openclaw-editing.mjs +372 -0
  50. package/web-ui/modules/app.methods.openclaw-persist.mjs +369 -0
  51. package/web-ui/modules/app.methods.providers.mjs +363 -0
  52. package/web-ui/modules/app.methods.runtime.mjs +323 -0
  53. package/web-ui/modules/app.methods.session-actions.mjs +520 -0
  54. package/web-ui/modules/app.methods.session-browser.mjs +626 -0
  55. package/web-ui/modules/app.methods.session-timeline.mjs +448 -0
  56. package/web-ui/modules/app.methods.session-trash.mjs +422 -0
  57. package/web-ui/modules/app.methods.startup-claude.mjs +412 -0
  58. package/web-ui/modules/app.methods.task-orchestration.mjs +471 -0
  59. package/web-ui/modules/config-mode.computed.mjs +126 -124
  60. package/web-ui/modules/skills.computed.mjs +107 -107
  61. package/web-ui/modules/skills.methods.mjs +481 -481
  62. package/web-ui/partials/index/layout-footer.html +13 -0
  63. package/web-ui/partials/index/layout-header.html +402 -0
  64. package/web-ui/partials/index/modal-config-template-agents.html +125 -0
  65. package/web-ui/partials/index/modal-confirm-toast.html +32 -0
  66. package/web-ui/partials/index/modal-health-check.html +72 -0
  67. package/web-ui/partials/index/modal-openclaw-config.html +280 -0
  68. package/web-ui/partials/index/modal-skills.html +184 -0
  69. package/web-ui/partials/index/modals-basic.html +156 -0
  70. package/web-ui/partials/index/panel-config-claude.html +126 -0
  71. package/web-ui/partials/index/panel-config-codex.html +237 -0
  72. package/web-ui/partials/index/panel-config-openclaw.html +78 -0
  73. package/web-ui/partials/index/panel-docs.html +130 -0
  74. package/web-ui/partials/index/panel-market.html +174 -0
  75. package/web-ui/partials/index/panel-orchestration.html +397 -0
  76. package/web-ui/partials/index/panel-sessions.html +292 -0
  77. package/web-ui/partials/index/panel-settings.html +190 -0
  78. package/web-ui/partials/index/panel-usage.html +213 -0
  79. package/web-ui/session-helpers.mjs +559 -362
  80. package/web-ui/source-bundle.cjs +233 -0
  81. package/web-ui/styles/base-theme.css +271 -0
  82. package/web-ui/styles/controls-forms.css +360 -0
  83. package/web-ui/styles/docs-panel.css +182 -0
  84. package/web-ui/styles/feedback.css +108 -0
  85. package/web-ui/styles/health-check-dialog.css +144 -0
  86. package/web-ui/styles/layout-shell.css +376 -0
  87. package/web-ui/styles/modals-core.css +464 -0
  88. package/web-ui/styles/navigation-panels.css +348 -0
  89. package/web-ui/styles/openclaw-structured.css +266 -0
  90. package/web-ui/styles/responsive.css +450 -0
  91. package/web-ui/styles/sessions-list.css +400 -0
  92. package/web-ui/styles/sessions-preview.css +411 -0
  93. package/web-ui/styles/sessions-toolbar-trash.css +243 -0
  94. package/web-ui/styles/sessions-usage.css +628 -0
  95. package/web-ui/styles/skills-list.css +296 -0
  96. package/web-ui/styles/skills-market.css +335 -0
  97. package/web-ui/styles/task-orchestration.css +776 -0
  98. package/web-ui/styles/titles-cards.css +408 -0
  99. package/web-ui/styles.css +18 -4668
  100. package/web-ui.html +17 -17
  101. package/res/screenshot.png +0 -0
  102. package/res/vue.global.js +0 -18552
@@ -0,0 +1,233 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ const HTML_INCLUDE_RE = /^[ \t]*<!--\s*@include\s+(.+?)\s*-->\s*$/gm;
5
+ const CSS_IMPORT_RE = /^[ \t]*@import\s+(?:url\(\s*)?(['"]?)([^'")]+)\1\s*\)?\s*;/gm;
6
+ const JS_IMPORT_RE = /(?:^|\n)\s*import\s+(?:[\s\S]*?\s+from\s+)?['"](\.[^'"]+)['"]\s*;?/g;
7
+ const JS_EXPORT_FROM_RE = /(?:^|\n)\s*export\s+\*\s+from\s+['"](\.[^'"]+)['"]\s*;?/g;
8
+ const JS_RELATIVE_IMPORT_STATEMENT_RE = /(^|\n)([ \t]*)import\s+([\s\S]*?)\s+from\s+['"](\.[^'"]+)['"]\s*;?[ \t]*/g;
9
+ const IDENTIFIER_RE = /^[A-Za-z_$][\w$]*$/;
10
+
11
+ function stripBom(content) {
12
+ return content.replace(/^\uFEFF/, '');
13
+ }
14
+
15
+ function readUtf8Text(filePath) {
16
+ return stripBom(fs.readFileSync(filePath, 'utf8').replace(/\r\n?/g, '\n'));
17
+ }
18
+
19
+ function normalizeIncludeTarget(rawTarget) {
20
+ const trimmed = String(rawTarget || '').trim();
21
+ if (!trimmed) return '';
22
+ return trimmed.replace(/^['"]|['"]$/g, '');
23
+ }
24
+
25
+ function assertNoCircularDependency(filePath, stack) {
26
+ if (!stack.includes(filePath)) {
27
+ return;
28
+ }
29
+ const cycle = [...stack, filePath]
30
+ .map(item => path.relative(path.join(__dirname, '..'), item))
31
+ .join(' -> ');
32
+ throw new Error(`Detected circular source include: ${cycle}`);
33
+ }
34
+
35
+ function bundleHtmlFile(filePath, stack = []) {
36
+ assertNoCircularDependency(filePath, stack);
37
+ const source = readUtf8Text(filePath);
38
+ return source.replace(HTML_INCLUDE_RE, (_match, rawTarget) => {
39
+ const target = normalizeIncludeTarget(rawTarget);
40
+ if (!target) {
41
+ return '';
42
+ }
43
+ const targetPath = path.resolve(path.dirname(filePath), target);
44
+ return bundleHtmlFile(targetPath, [...stack, filePath]);
45
+ });
46
+ }
47
+
48
+ function bundleCssFile(filePath, stack = []) {
49
+ assertNoCircularDependency(filePath, stack);
50
+ const source = readUtf8Text(filePath);
51
+ return source.replace(CSS_IMPORT_RE, (match, _quote, rawTarget) => {
52
+ const target = normalizeIncludeTarget(rawTarget);
53
+ if (!target || !target.startsWith('.')) {
54
+ return match;
55
+ }
56
+ const targetPath = path.resolve(path.dirname(filePath), target);
57
+ return bundleCssFile(targetPath, [...stack, filePath]);
58
+ });
59
+ }
60
+
61
+ function resolveJavaScriptDependencies(filePath) {
62
+ const source = readUtf8Text(filePath);
63
+ const dependencies = [];
64
+ for (const pattern of [JS_IMPORT_RE, JS_EXPORT_FROM_RE]) {
65
+ let match = pattern.exec(source);
66
+ while (match) {
67
+ const target = normalizeIncludeTarget(match[1]);
68
+ if (target.startsWith('.')) {
69
+ dependencies.push(path.resolve(path.dirname(filePath), target));
70
+ }
71
+ match = pattern.exec(source);
72
+ }
73
+ pattern.lastIndex = 0;
74
+ }
75
+ return dependencies;
76
+ }
77
+
78
+ function bundleJavaScriptFile(filePath, visited = new Set()) {
79
+ if (visited.has(filePath)) {
80
+ return '';
81
+ }
82
+ visited.add(filePath);
83
+
84
+ const relativePath = path.relative(path.join(__dirname, '..'), filePath).replace(/\\/g, '/');
85
+ const source = readUtf8Text(filePath);
86
+ const chunks = [
87
+ `// ===== FILE: ${relativePath} =====`,
88
+ source.trimEnd(),
89
+ ''
90
+ ];
91
+
92
+ for (const dependencyPath of resolveJavaScriptDependencies(filePath)) {
93
+ chunks.push(bundleJavaScriptFile(dependencyPath, visited).trimEnd());
94
+ chunks.push('');
95
+ }
96
+
97
+ return chunks.join('\n').trimEnd() + '\n';
98
+ }
99
+
100
+ function collectJavaScriptFiles(filePath, ordered = [], visited = new Set(), stack = []) {
101
+ assertNoCircularDependency(filePath, stack);
102
+ if (visited.has(filePath)) {
103
+ return ordered;
104
+ }
105
+ visited.add(filePath);
106
+ for (const dependencyPath of resolveJavaScriptDependencies(filePath)) {
107
+ collectJavaScriptFiles(dependencyPath, ordered, visited, [...stack, filePath]);
108
+ }
109
+ ordered.push(filePath);
110
+ return ordered;
111
+ }
112
+
113
+ function splitCommaSeparatedSpecifiers(source) {
114
+ const items = [];
115
+ let current = '';
116
+ let depth = 0;
117
+ for (let i = 0; i < source.length; i += 1) {
118
+ const ch = source[i];
119
+ if (ch === '{' || ch === '[' || ch === '(') {
120
+ depth += 1;
121
+ } else if (ch === '}' || ch === ']' || ch === ')') {
122
+ depth = Math.max(0, depth - 1);
123
+ }
124
+ if (ch === ',' && depth === 0) {
125
+ items.push(current);
126
+ current = '';
127
+ continue;
128
+ }
129
+ current += ch;
130
+ }
131
+ if (current) {
132
+ items.push(current);
133
+ }
134
+ return items.map(item => item.trim()).filter(Boolean);
135
+ }
136
+
137
+ function buildRelativeImportAliasStatements(importClause, filePath) {
138
+ const clause = String(importClause || '').trim();
139
+ if (!clause) {
140
+ return '';
141
+ }
142
+ if (!clause.startsWith('{') || !clause.endsWith('}')) {
143
+ throw new Error(`Unsupported executable bundle import in ${filePath}: ${clause}`);
144
+ }
145
+
146
+ const innerClause = clause.slice(1, -1).trim();
147
+ if (!innerClause) {
148
+ return '';
149
+ }
150
+
151
+ const statements = [];
152
+ for (const specifier of splitCommaSeparatedSpecifiers(innerClause)) {
153
+ const parts = specifier.split(/\s+as\s+/);
154
+ const imported = String(parts[0] || '').trim();
155
+ const local = String(parts[1] || imported).trim();
156
+ if (!IDENTIFIER_RE.test(imported) || !IDENTIFIER_RE.test(local)) {
157
+ throw new Error(`Unsupported executable bundle import specifier in ${filePath}: ${specifier}`);
158
+ }
159
+ if (local !== imported) {
160
+ statements.push(`const ${local} = ${imported};`);
161
+ }
162
+ }
163
+ return statements.join('\n');
164
+ }
165
+
166
+ function transformJavaScriptModuleSource(source, options = {}) {
167
+ const preserveExports = !!options.preserveExports;
168
+ const sourcePath = typeof source === 'string' ? source : String(source || '');
169
+ let transformed = readUtf8Text(sourcePath);
170
+ transformed = transformed.replace(JS_RELATIVE_IMPORT_STATEMENT_RE, (_match, prefix, indent, importClause) => {
171
+ const aliases = buildRelativeImportAliasStatements(importClause, sourcePath);
172
+ if (!aliases) {
173
+ return prefix || '';
174
+ }
175
+ const indentedAliases = aliases
176
+ .split('\n')
177
+ .map(line => `${indent || ''}${line}`)
178
+ .join('\n');
179
+ return `${prefix || ''}${indentedAliases}\n`;
180
+ });
181
+ transformed = transformed.replace(/^[ \t]*export\s+\*\s+from\s+['"]\.[^'"]+['"]\s*;?\s*$/gm, '');
182
+ if (!preserveExports) {
183
+ transformed = transformed.replace(/(^|\n)([ \t]*)export\s+(?=(?:async\s+function|const|let|class|function)\b)/g, '$1$2');
184
+ }
185
+ return transformed.trimEnd();
186
+ }
187
+
188
+ function bundleExecutableJavaScriptFile(entryPath, options = {}) {
189
+ const orderedFiles = collectJavaScriptFiles(entryPath);
190
+ const preserveExports = !!options.preserveExports;
191
+ const chunks = [];
192
+ for (const filePath of orderedFiles) {
193
+ const transformed = transformJavaScriptModuleSource(filePath, { preserveExports });
194
+ if (!transformed) {
195
+ continue;
196
+ }
197
+ chunks.push(transformed);
198
+ }
199
+ return chunks.join('\n\n').trimEnd() + '\n';
200
+ }
201
+
202
+ function readBundledWebUiHtml(entryPath = path.join(__dirname, 'index.html')) {
203
+ return bundleHtmlFile(entryPath).trimEnd() + '\n';
204
+ }
205
+
206
+ function readBundledWebUiCss(entryPath = path.join(__dirname, 'styles.css')) {
207
+ return bundleCssFile(entryPath).trimEnd() + '\n';
208
+ }
209
+
210
+ function readBundledWebUiScript(entryPath = path.join(__dirname, 'app.js')) {
211
+ return bundleJavaScriptFile(entryPath);
212
+ }
213
+
214
+ function readExecutableBundledWebUiScript(entryPath = path.join(__dirname, 'app.js')) {
215
+ return bundleExecutableJavaScriptFile(entryPath, { preserveExports: false });
216
+ }
217
+
218
+ function readExecutableBundledJavaScriptModule(entryPath) {
219
+ const resolvedEntryPath = path.isAbsolute(entryPath)
220
+ ? entryPath
221
+ : path.resolve(__dirname, '..', entryPath);
222
+ return bundleExecutableJavaScriptFile(resolvedEntryPath, { preserveExports: true });
223
+ }
224
+
225
+ module.exports = {
226
+ collectJavaScriptFiles,
227
+ readUtf8Text,
228
+ readBundledWebUiHtml,
229
+ readBundledWebUiCss,
230
+ readBundledWebUiScript,
231
+ readExecutableBundledWebUiScript,
232
+ readExecutableBundledJavaScriptModule
233
+ };
@@ -0,0 +1,271 @@
1
+ /* Use local font stacks only; avoid third-party font fetches. */
2
+
3
+ /* ============================================
4
+ 设计系统 - Design Tokens
5
+ ============================================ */
6
+ :root {
7
+ /* 色彩系统:温和中性暖灰 + 柔粉强调 */
8
+ --color-brand: #C77462;
9
+ --color-brand-dark: #B45E4E;
10
+ --color-brand-light: rgba(199, 116, 98, 0.14);
11
+ --color-brand-subtle: rgba(199, 116, 98, 0.2);
12
+
13
+ --color-bg: #F7F3EF;
14
+ --color-surface: #FFFDFC;
15
+ --color-surface-alt: #F8F3EE;
16
+ --color-surface-elevated: #FFFFFF;
17
+ --color-surface-tint: rgba(255, 253, 252, 0.9);
18
+ --color-text-primary: #241F1C;
19
+ --color-text-secondary: #5A514B;
20
+ --color-text-tertiary: #7A6E66;
21
+ --color-text-muted: #A39286;
22
+ --color-border: #E7DDD4;
23
+ --color-border-soft: rgba(163, 146, 134, 0.22);
24
+ --color-border-strong: rgba(163, 146, 134, 0.4);
25
+
26
+ --color-success: #4B8B6A;
27
+ --color-error: #C44536;
28
+
29
+ --bg-warm-gradient:
30
+ linear-gradient(180deg, #F7F3EF 0%, #F7F3EF 100%);
31
+
32
+ /* 字体系统 */
33
+ --font-family-body: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans SC', 'PingFang SC', 'Microsoft YaHei', sans-serif;
34
+ --font-family-display: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans SC', 'PingFang SC', 'Microsoft YaHei', sans-serif;
35
+ --font-family-mono: 'JetBrainsMono Nerd Font Mono', 'JetBrains Mono', 'Fira Code', 'SFMono-Regular', Consolas, 'Liberation Mono', monospace;
36
+ --font-family: var(--font-family-body);
37
+
38
+ --font-size-display: 52px;
39
+ --font-size-title: 18px;
40
+ --font-size-large: 20px;
41
+ --font-size-body: 15px;
42
+ --font-size-secondary: 13px;
43
+ --font-size-caption: 11px;
44
+
45
+ --font-weight-display: 600;
46
+ --font-weight-primary: 600;
47
+ --font-weight-title: 600;
48
+ --font-weight-body: 400;
49
+ --font-weight-secondary: 500;
50
+ --font-weight-caption: 500;
51
+
52
+ --line-height-tight: 1.12;
53
+ --line-height-normal: 1.5;
54
+
55
+ /* 间距系统 */
56
+ --spacing-xs: 8px;
57
+ --spacing-sm: 16px;
58
+ --spacing-md: 24px;
59
+ --spacing-lg: 40px;
60
+ --spacing-xl: 64px;
61
+
62
+ /* 圆角系统 */
63
+ --radius-sm: 8px;
64
+ --radius-md: 10px;
65
+ --radius-lg: 12px;
66
+ --radius-xl: 18px;
67
+ --radius-full: 50px;
68
+
69
+ /* 阴影系统 - 温和、轻量 */
70
+ --shadow-subtle: 0 1px 2px rgba(36, 31, 28, 0.03);
71
+ --shadow-card: 0 4px 12px rgba(36, 31, 28, 0.045);
72
+ --shadow-card-hover: 0 8px 22px rgba(36, 31, 28, 0.07);
73
+ --shadow-float: 0 12px 28px rgba(36, 31, 28, 0.1);
74
+ --shadow-raised: 0 8px 18px rgba(36, 31, 28, 0.08);
75
+ --shadow-modal:
76
+ 0 8px 24px rgba(36, 31, 28, 0.08),
77
+ 0 24px 64px rgba(36, 31, 28, 0.05);
78
+ --shadow-input-focus:
79
+ 0 0 0 3px var(--color-brand-light),
80
+ 0 1px 3px rgba(31, 26, 23, 0.04);
81
+
82
+ /* 动画 - 更细腻的曲线 */
83
+ --transition-instant: 100ms;
84
+ --transition-fast: 120ms;
85
+ --transition-normal: 200ms;
86
+ --transition-slow: 300ms;
87
+ --ease-spring: cubic-bezier(0.16, 1, 0.3, 1);
88
+ --ease-spring-soft: cubic-bezier(0.25, 1, 0.5, 1);
89
+ --ease-smooth: cubic-bezier(0.4, 0, 0.2, 1);
90
+ --ease-out-expo: cubic-bezier(0.19, 1, 0.22, 1);
91
+ }
92
+
93
+ /* ============================================
94
+ 手机桌面 UA 兜底:触控设备强制紧凑排版
95
+ ============================================ */
96
+ body.force-compact {
97
+ --font-size-title: 20px;
98
+ --font-size-body: 16px;
99
+ --font-size-secondary: 14px;
100
+ --font-size-caption: 12px;
101
+ }
102
+
103
+ body.force-compact .container {
104
+ max-width: none;
105
+ padding: 0;
106
+ }
107
+
108
+ body.force-compact .provider-fast-switch {
109
+ position: sticky;
110
+ top: 8px;
111
+ z-index: 16;
112
+ }
113
+
114
+ body.force-compact .provider-fast-switch-select {
115
+ min-height: 44px;
116
+ font-size: 16px;
117
+ }
118
+
119
+ body.force-compact .app-shell {
120
+ grid-template-columns: 1fr;
121
+ gap: 12px;
122
+ height: auto;
123
+ min-height: auto;
124
+ overflow: visible;
125
+ }
126
+
127
+ body.force-compact .main-panel {
128
+ position: relative;
129
+ top: auto;
130
+ align-self: stretch;
131
+ width: 100%;
132
+ height: auto;
133
+ overflow-y: visible;
134
+ }
135
+
136
+ body.force-compact .side-rail,
137
+ body.force-compact .status-inspector {
138
+ display: none;
139
+ }
140
+
141
+ body.force-compact .top-tabs {
142
+ display: flex !important;
143
+ flex-wrap: nowrap;
144
+ overflow-x: auto;
145
+ overflow-y: hidden;
146
+ -webkit-overflow-scrolling: touch;
147
+ scrollbar-width: none;
148
+ }
149
+
150
+ body.force-compact .top-tabs::-webkit-scrollbar {
151
+ display: none;
152
+ }
153
+
154
+ body.force-compact .main-panel {
155
+ padding: 14px 12px;
156
+ }
157
+
158
+ body.force-compact .main-panel-topbar {
159
+ position: static;
160
+ margin: 0 0 10px;
161
+ padding: 0;
162
+ background: transparent;
163
+ backdrop-filter: none;
164
+ }
165
+
166
+ body.force-compact .card {
167
+ display: flex;
168
+ flex-direction: column;
169
+ align-items: flex-start;
170
+ justify-content: flex-start;
171
+ padding: 12px;
172
+ gap: 8px;
173
+ }
174
+
175
+ body.force-compact .card-leading {
176
+ align-items: flex-start;
177
+ width: 100%;
178
+ }
179
+
180
+ body.force-compact .card-content {
181
+ width: 100%;
182
+ }
183
+
184
+ body.force-compact .card-title,
185
+ body.force-compact .card-title > span:first-child {
186
+ white-space: normal;
187
+ overflow: visible;
188
+ text-overflow: clip;
189
+ overflow-wrap: anywhere;
190
+ }
191
+
192
+ body.force-compact .card-subtitle {
193
+ white-space: normal;
194
+ overflow: hidden;
195
+ text-overflow: clip;
196
+ overflow-wrap: anywhere;
197
+ display: -webkit-box;
198
+ -webkit-line-clamp: 2;
199
+ -webkit-box-orient: vertical;
200
+ }
201
+
202
+ body.force-compact .card-trailing {
203
+ width: 100%;
204
+ margin-top: 0;
205
+ grid-auto-flow: row;
206
+ grid-auto-columns: 1fr;
207
+ justify-content: stretch;
208
+ justify-items: end;
209
+ }
210
+
211
+ body.force-compact .card-trailing .card-actions {
212
+ width: 100%;
213
+ justify-content: flex-end;
214
+ justify-self: stretch;
215
+ flex-wrap: wrap;
216
+ }
217
+
218
+ body.force-compact .card-actions {
219
+ opacity: 1;
220
+ transform: none;
221
+ }
222
+
223
+ body.force-compact .card-trailing .pill,
224
+ body.force-compact .card-trailing .latency {
225
+ justify-self: end;
226
+ }
227
+
228
+ body.force-compact .btn-add,
229
+ body.force-compact .btn-tool,
230
+ body.force-compact .card-action-btn {
231
+ min-height: 44px;
232
+ }
233
+
234
+ /* ============================================
235
+ 基础重置
236
+ ============================================ */
237
+ * {
238
+ margin: 0;
239
+ padding: 0;
240
+ box-sizing: border-box;
241
+ }
242
+
243
+ /* 仅屏幕阅读器可见 */
244
+ .sr-only {
245
+ position: absolute;
246
+ width: 1px;
247
+ height: 1px;
248
+ padding: 0;
249
+ margin: -1px;
250
+ overflow: hidden;
251
+ clip: rect(0, 0, 0, 0);
252
+ white-space: nowrap;
253
+ border: 0;
254
+ }
255
+
256
+ body {
257
+ font-family: var(--font-family-body);
258
+ background-color: var(--color-bg);
259
+ background: var(--bg-warm-gradient);
260
+ color: var(--color-text-primary);
261
+ display: flex;
262
+ justify-content: stretch;
263
+ align-items: stretch;
264
+ min-height: 100vh;
265
+ padding: 0;
266
+ -webkit-font-smoothing: antialiased;
267
+ -moz-osx-font-smoothing: grayscale;
268
+ position: relative;
269
+ overflow-x: hidden;
270
+ }
271
+