@goodandready/dsh-context-lens 0.1.25 → 0.1.26

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.
package/lib/client.js CHANGED
@@ -4,286 +4,237 @@ window.__ModuleLoader__.load({
4
4
  var module = { exports: {} };
5
5
  const React = require('react');
6
6
  const NS = '@goodandready/dsh-context-lens';
7
- // Plugins page row seat (DSH 0.1.6-alpha.2): key = '<package name>#<row id>',
8
- // row id as cordis.patch.yml declares it.
9
7
  const PKG = '@goodandready/dsh-context-lens';
10
8
  const ROW_ID = 'dsh-context-lens';
11
9
  const ROW_CONFIG_KEY = PKG + '#' + ROW_ID;
12
10
 
13
- const en = {
14
- 'update.available': 'Update available: v{latestVersion} (current: v{currentVersion})',
15
- 'update.btn': 'Update Now',
16
- 'update.updating': 'Updating…',
17
- 'update.done': 'Successfully updated to v{version}! Please restart DSH.',
18
- 'update.failed': 'Update failed: {error}',
19
- 'update.checking': 'Checking for updates…',
20
- 'update.up_to_date': 'Up to date',
21
- title: 'Context Lens & Token Guard',
22
- sub: 'AST compression, log filtering, token budget',
11
+ const en = {
12
+ title: 'Context Lens',
13
+ sub: 'AST code skeletons and test log compression',
23
14
  mode: 'Compression mode',
24
15
  depth: 'AST max depth',
25
- tracking: 'Track token savings',
16
+ threshold: 'Auto-compress threshold (chars)',
26
17
  preview: 'Log preview',
27
18
  compress: 'Compress',
28
- saved: 'Saved',
29
- tokens: 'tokens',
30
- placeholder: 'Paste Jest/Pytest log here…',
19
+ placeholder: 'Paste test or build log here…',
31
20
  saving: 'Saving…',
32
21
  ready: 'Ready',
33
- budget: 'Budget',
34
- history: 'Recent ops',
35
- lowBudget: 'Budget nearly exhausted',
36
- threshold: 'Auto-compress threshold (chars)',
37
- autoCollapse: 'Warn when budget is nearly exhausted',
38
- budgetAlertPercent: 'Budget alert threshold (%)',
39
- previewApprox: 'Server preview',
40
- noData: 'No data yet',
41
- refresh: 'Refresh',
42
- ops: 'ops',
43
- active: 'Active',
44
- readyState: 'Ready',
45
- lowBudgetBadge: 'Low Budget ⚠',
46
- focusPaths: 'Active focus',
47
- noFocus: 'No focus paths set',
48
- clearFocus: 'Clear focus'
22
+ previewApprox: 'Local preview'
49
23
  };
50
24
 
51
25
  const zh = {
52
- 'update.available': '发现新版本 v{latestVersion}(当前版本 v{currentVersion})',
53
- 'update.btn': '立即更新',
54
- 'update.updating': '正在更新…',
55
- 'update.done': '更新成功至 v{version}!请重启 DSH 服务生效。',
56
- 'update.failed': '更新失败:{error}',
57
- 'update.checking': '正在检查更新…',
58
- 'update.up_to_date': '已是最新版本',
59
- title: 'Context Lens 上下文镜头与 Token 守卫',
60
- sub: 'AST 代码精简、日志过滤与 Token 预算监控',
26
+ title: '上下文透镜',
27
+ sub: 'AST 代码骨架生成与测试日志压缩',
61
28
  mode: '压缩模式',
62
29
  depth: 'AST 最大深度',
63
- tracking: '追踪 Token 节省',
30
+ threshold: '自动压缩阈值 (字符)',
64
31
  preview: '日志预览',
65
32
  compress: '压缩',
66
- saved: '已节省',
67
- tokens: 'Token',
68
- placeholder: '在此粘贴 Jest/Pytest 等测试日志…',
33
+ placeholder: '在此粘贴测试或构建日志…',
69
34
  saving: '保存中…',
70
- ready: '已就绪',
71
- budget: '预算',
72
- history: '近期操作',
73
- lowBudget: 'Token 预算即将耗尽',
74
- threshold: '自动压缩阈值 (字符数)',
75
- autoCollapse: '预算偏低时在界面提示警告',
76
- budgetAlertPercent: '预算警告阈值 (%)',
77
- previewApprox: '服务端压缩预览',
78
- noData: '暂无数据',
79
- refresh: '刷新',
80
- ops: '次操作',
81
- active: '运行中',
82
- readyState: '已就绪',
83
- lowBudgetBadge: '预算紧张 ⚠',
84
- focusPaths: '当前聚焦路径',
85
- noFocus: '未设置聚焦路径',
86
- clearFocus: '清除聚焦'
35
+ ready: '就绪',
36
+ previewApprox: '本地预览'
87
37
  };
88
38
 
89
- function ensureCss() {
90
- if (typeof document === 'undefined') return;
91
- if (document.getElementById('dsh-context-lens-css')) return;
92
- const style = document.createElement('style');
93
- style.id = 'dsh-context-lens-css';
94
- style.dataset.dshPlugin = NS;
95
- style.textContent = `
96
- .cl-section-card {
97
- border: 1px solid var(--dsw-alias-border-l2);
98
- background: var(--dsw-alias-bg-layer-3);
99
- border-radius: 12px;
100
- padding: 16px 18px;
101
- margin-bottom: 12px;
102
- display: flex;
103
- flex-direction: column;
104
- gap: 12px;
105
- list-style: none;
106
- }
107
- .cl-section-header {
108
- background: none;
109
- border: none;
110
- cursor: pointer;
111
- display: flex;
112
- align-items: center;
113
- width: 100%;
114
- padding: 0;
115
- text-align: left;
116
- }
117
- .cl-badge {
118
- font-size: 11px;
119
- padding: 2px 8px;
120
- border-radius: 999px;
121
- border: 1px solid var(--dsw-alias-border-l2);
122
- display: inline-flex;
123
- align-items: center;
124
- gap: 4px;
125
- font-weight: 500;
126
- }
127
- .cl-badge-ok {
128
- border-color: var(--dsw-alias-state-success-primary, #10b981);
129
- color: var(--dsw-alias-state-success-primary, #10b981);
130
- background: color-mix(in srgb, var(--dsw-alias-state-success-primary, #10b981) 8%, transparent);
131
- }
132
- .cl-badge-warn {
133
- border-color: var(--dsw-alias-state-error-primary, #ef4444);
134
- color: var(--dsw-alias-state-error-primary, #ef4444);
135
- background: color-mix(in srgb, var(--dsw-alias-state-error-primary, #ef4444) 8%, transparent);
136
- }
137
- .cl-stat-grid {
138
- display: grid;
139
- grid-template-columns: repeat(auto-fit, minmax(110px, 1fr));
140
- gap: 10px;
141
- margin-top: 4px;
142
- }
143
- .cl-stat-box {
144
- padding: 10px 12px;
145
- border: 1px solid var(--dsw-alias-border-l2);
146
- border-radius: 8px;
147
- background: var(--dsw-alias-bg-layer-2);
148
- display: flex;
149
- flex-direction: column;
150
- gap: 3px;
151
- }
152
- .cl-stat-val {
153
- font-size: 16px;
154
- font-weight: 700;
155
- color: var(--dsw-alias-label-primary);
156
- }
157
- .cl-stat-lbl {
158
- font-size: 11px;
159
- color: var(--dsw-alias-label-secondary);
160
- }
161
- .cl-bar-box {
162
- display: flex;
163
- flex-direction: column;
164
- gap: 6px;
165
- padding: 10px 12px;
166
- border: 1px solid var(--dsw-alias-border-l2);
167
- border-radius: 8px;
168
- background: var(--dsw-alias-bg-layer-2);
169
- }
170
- .cl-bar-track {
171
- width: 100%;
172
- height: 8px;
173
- border-radius: 999px;
174
- background: var(--dsw-alias-bg-layer-1);
175
- overflow: hidden;
176
- border: 1px solid var(--dsw-alias-border-l2);
177
- }
178
- .cl-bar-fill {
179
- height: 100%;
180
- border-radius: 999px;
181
- transition: width 0.3s;
182
- }
183
- .cl-input {
184
- height: 34px;
185
- border: 1px solid var(--dsw-alias-border-l2);
186
- background: var(--dsw-alias-bg-layer-2);
187
- color: var(--dsw-alias-label-primary);
188
- border-radius: 8px;
189
- padding: 0 12px;
190
- font-size: 13px;
191
- box-sizing: border-box;
192
- }
193
- .cl-input:focus {
194
- outline: none;
195
- border-color: var(--dsw-alias-state-brand-primary, #3b82f6);
196
- }
197
- .cl-btn {
198
- appearance: none;
199
- font: inherit;
200
- cursor: pointer;
201
- border: 1px solid var(--dsw-alias-border-l2);
202
- border-radius: 8px;
203
- padding: 6px 12px;
204
- font-size: 12px;
205
- background: var(--dsw-alias-bg-layer-2);
206
- color: var(--dsw-alias-label-primary);
207
- font-weight: 500;
208
- display: inline-flex;
209
- align-items: center;
210
- justify-content: center;
211
- gap: 6px;
212
- transition: all 0.15s ease;
213
- }
214
- .cl-btn:hover:not(:disabled) {
215
- background: var(--dsw-alias-bg-layer-4, var(--dsw-alias-bg-layer-2));
216
- border-color: var(--dsw-alias-label-dimmed, var(--dsw-alias-border-l2));
217
- }
218
- .cl-btn-primary {
219
- background: var(--dsw-alias-label-primary);
220
- color: var(--dsw-alias-bg-layer-3);
221
- border-color: transparent;
222
- }
223
- .cl-btn-primary:hover:not(:disabled) {
224
- opacity: 0.88;
225
- }
226
- .cl-btn-disabled {
227
- opacity: 0.5;
228
- cursor: not-allowed;
229
- }
230
- .cl-banner-warn {
231
- padding: 10px 14px;
232
- border-radius: 8px;
233
- background: color-mix(in srgb, var(--dsw-alias-state-error-primary, #ef4444) 10%, transparent);
234
- border: 1px solid var(--dsw-alias-state-error-primary, #ef4444);
235
- color: var(--dsw-alias-state-error-primary, #ef4444);
236
- font-size: 12px;
237
- display: flex;
238
- align-items: center;
239
- gap: 8px;
240
- font-weight: 500;
241
- }
242
- .cl-hist-item {
243
- font-size: 11px;
244
- color: var(--dsw-alias-label-secondary);
245
- padding: 4px 0;
246
- border-bottom: 1px solid var(--dsw-alias-border-l3, var(--dsw-alias-border-l2));
247
- }
248
- .cl-hist-item:last-child {
249
- border-bottom: none;
250
- }
251
- .cl-tag {
252
- display: inline-flex;
253
- align-items: center;
254
- gap: 4px;
255
- padding: 2px 6px;
256
- background: var(--dsw-alias-bg-layer-1);
257
- border: 1px solid var(--dsw-alias-border-l2);
258
- border-radius: 4px;
259
- font-size: 11px;
260
- font-family: monospace;
261
- color: var(--dsw-alias-label-secondary);
262
- }
263
- .cl-error-box {
264
- padding: 12px;
265
- border-radius: 8px;
266
- background: color-mix(in srgb, var(--dsw-alias-state-error-primary, #ef4444) 8%, transparent);
267
- border: 1px solid var(--dsw-alias-state-error-primary, #ef4444);
268
- color: var(--dsw-alias-state-error-primary, #ef4444);
269
- font-size: 12px;
270
- }
271
- `;
272
- document.head.appendChild(style);
39
+ function getActiveLocale(ctx) {
40
+ try {
41
+ if (ctx && ctx.locale && typeof ctx.locale.getSnapshot === 'function') {
42
+ const snap = ctx.locale.getSnapshot();
43
+ if (snap && snap.active && snap.active.startsWith('zh')) return 'zh';
44
+ }
45
+ } catch (err) {
46
+ console.warn('[dsh-context-lens] getActiveLocale failed', err && err.message || err);
47
+ }
48
+ return 'en';
273
49
  }
274
50
 
275
- function makeT(dict, fallback) {
276
- return function t(key, vars) {
277
- let val = (dict && dict[key]) || (fallback && fallback[key]) || key;
278
- if (vars && typeof val === 'string') {
279
- for (const k of Object.keys(vars)) {
280
- val = val.replace(new RegExp('\\{' + k + '\\}', 'g'), String(vars[k]));
51
+ function makeT(locale, fallbackDict) {
52
+ const dict = locale === 'zh' ? zh : fallbackDict;
53
+ return (key, params) => {
54
+ let str = dict[key] || fallbackDict[key] || key;
55
+ if (params && typeof params === 'object') {
56
+ for (const k of Object.keys(params)) {
57
+ str = str.replace(new RegExp('\\{' + k + '\\}', 'g'), String(params[k]));
281
58
  }
282
59
  }
283
- return val;
60
+ return str;
284
61
  };
285
62
  }
286
63
 
64
+ const ANSI_RE = /\u001b\[[0-9;]*[a-zA-Z]/g;
65
+ const KEEP_RE = /(FAIL|FAILED|Error|AssertionError|Exception|Traceback|panic|npm ERR!|ERR!|Expected|Received|missing|×|●|✕|FAIL:|--- FAIL|not ok|at\s+.*:\d+:\d+|stack|Caused by|test result:\s*FAILED|running \d+ test|test .* \.\.\. FAILED|BUILD FAILED|Tests run:|FAILURE:|cargo:.*error|error\[E\d+\]|thread '.*' panicked)/i;
66
+ const PASS_RE = /^(PASS|\s*✓|\s*✔|\s*ok\s|…+\s*$|\s*\.\s*$|test result:\s*ok|running \d+ test.*ok)/i;
67
+ const NOISE_RE = /^\s*(npm (notice|warn|info)|Browserslist|cached|Downloading|Done in|Compiling\s|cargo:.*Finished)/i;
68
+ const STACK_RE = /^\s*(at\s+|File ".*", line \d+|#\d+\s+0x|goroutine \d+ \[|thread '.*' panicked|note: run with)/;
69
+ const SUMMARY_RE = /(tests?\s+(passed|failed|run)|test result:|suites?\s+\d+|\d+\s+passed|BUILD (SUCCESS|FAILED)|===.*passed.*===|passed in \d+|built in|modules transformed|Finished.*release|Finished.*dev)/i;
70
+
71
+ function cleanAnsi(str) {
72
+ if (!str || !str.includes('\u001b')) return str || '';
73
+ return str.replace(ANSI_RE, '');
74
+ }
75
+
76
+ function compressLogClient(text, { mode = 'balanced', maxLines = 200 } = {}) {
77
+ if (!text || typeof text !== 'string') return '';
78
+ const lines = text.split(/\r?\n/);
79
+ if (mode === 'raw') {
80
+ const filtered = lines.filter((l) => {
81
+ const clean = cleanAnsi(l);
82
+ if (KEEP_RE.test(clean) || STACK_RE.test(clean)) return true;
83
+ if (NOISE_RE.test(clean)) return false;
84
+ return true;
85
+ });
86
+ const truncated = filtered.length > maxLines
87
+ ? [...filtered.slice(0, maxLines - 1), `… truncated ${filtered.length - maxLines + 1} lines`]
88
+ : filtered;
89
+ return truncated.join('\n');
90
+ }
91
+
92
+ const keep = new Array(lines.length).fill(false);
93
+ const cleanLines = new Array(lines.length);
94
+ const context = mode === 'aggressive' ? 1 : 2;
95
+ let hasFailure = false;
96
+
97
+ for (let i = 0; i < lines.length; i++) {
98
+ const clean = cleanAnsi(lines[i]);
99
+ cleanLines[i] = clean;
100
+ if (KEEP_RE.test(clean) || STACK_RE.test(clean)) {
101
+ hasFailure = true;
102
+ const s = Math.max(0, i - context);
103
+ const e = Math.min(lines.length - 1, i + context);
104
+ for (let j = s; j <= e; j++) keep[j] = true;
105
+ }
106
+ }
107
+
108
+ if (!hasFailure) {
109
+ const head = Math.min(3, lines.length);
110
+ for (let i = 0; i < head; i++) keep[i] = true;
111
+ for (let i = head; i < lines.length; i++) {
112
+ if (SUMMARY_RE.test(cleanLines[i])) keep[i] = true;
113
+ }
114
+ if (lines.length > 0) keep[lines.length - 1] = true;
115
+ }
116
+
117
+ let out = [];
118
+ for (let i = 0; i < lines.length; i++) {
119
+ if (keep[i]) {
120
+ const l = lines[i];
121
+ const clean = cleanLines[i];
122
+ if (mode === 'aggressive' && (PASS_RE.test(clean) || NOISE_RE.test(clean)) && !KEEP_RE.test(clean) && !SUMMARY_RE.test(clean)) {
123
+ continue;
124
+ }
125
+ out.push(l);
126
+ }
127
+ }
128
+
129
+ if (out.length > maxLines) {
130
+ const half = Math.floor((maxLines - 1) / 2);
131
+ out = [
132
+ ...out.slice(0, half),
133
+ `… truncated ${out.length - maxLines + 1} lines …`,
134
+ ...out.slice(out.length - half)
135
+ ];
136
+ }
137
+
138
+ return out.join('\n');
139
+ }
140
+
141
+ function ensureCss() {
142
+ if (typeof document === 'undefined') return;
143
+ if (document.getElementById('dsh-context-lens-css')) return;
144
+ const el = document.createElement('style');
145
+ el.id = 'dsh-context-lens-css';
146
+ el.dataset.dshPlugin = NS;
147
+ el.textContent = `
148
+ .cl-card {
149
+ font-family: inherit;
150
+ color: var(--dsw-alias-label-primary, inherit);
151
+ }
152
+ .cl-section-card {
153
+ margin-bottom: 12px;
154
+ border: 1px solid var(--dsw-alias-border-l1, var(--dsw-alias-border-primary, var(--dsw-alias-border-l2)));
155
+ border-radius: 12px;
156
+ background: var(--dsw-alias-bg-layer-1, var(--dsw-alias-bg-layer-3));
157
+ overflow: hidden;
158
+ transition: border-color .15s ease, box-shadow .15s ease;
159
+ }
160
+ .cl-page {
161
+ border: none;
162
+ background: transparent;
163
+ }
164
+ .cl-header {
165
+ display: flex;
166
+ align-items: center;
167
+ justify-content: space-between;
168
+ padding: 14px 16px;
169
+ cursor: pointer;
170
+ user-select: none;
171
+ }
172
+ .cl-title {
173
+ font-weight: 600;
174
+ font-size: 14px;
175
+ color: var(--dsw-alias-label-primary, inherit);
176
+ }
177
+ .cl-sub {
178
+ font-size: 12px;
179
+ color: var(--dsw-alias-label-secondary, var(--dsw-alias-label-tertiary));
180
+ margin-top: 2px;
181
+ }
182
+ .cl-body {
183
+ padding: 0 16px 16px;
184
+ }
185
+ .cl-field {
186
+ display: flex;
187
+ flex-direction: column;
188
+ gap: 6px;
189
+ padding: 8px 0;
190
+ }
191
+ .cl-input {
192
+ padding: 8px 12px;
193
+ border-radius: 8px;
194
+ border: 1px solid var(--dsw-alias-border-l2);
195
+ background: var(--dsw-alias-bg-layer-2);
196
+ color: var(--dsw-alias-label-primary, inherit);
197
+ font-size: 13px;
198
+ }
199
+ .cl-btn {
200
+ display: inline-flex;
201
+ align-items: center;
202
+ justify-content: center;
203
+ padding: 6px 14px;
204
+ border-radius: 8px;
205
+ font-size: 13px;
206
+ font-weight: 500;
207
+ cursor: pointer;
208
+ border: 1px solid transparent;
209
+ transition: background .15s ease;
210
+ }
211
+ .cl-btn-primary {
212
+ background: var(--dsw-alias-btn-primary-bg, var(--dsw-alias-accent-primary));
213
+ color: var(--dsw-alias-btn-primary-color, var(--dsw-alias-text-contrast));
214
+ }
215
+ .cl-btn-primary:hover {
216
+ filter: brightness(0.95);
217
+ }
218
+ .cl-btn-disabled {
219
+ opacity: 0.5;
220
+ cursor: not-allowed;
221
+ }
222
+ .cl-error-box {
223
+ padding: 12px;
224
+ border-radius: 8px;
225
+ background: var(--dsw-alias-state-error-bg);
226
+ color: var(--dsw-alias-state-error-primary);
227
+ }
228
+ .cl-chev {
229
+ transition: transform .16s ease;
230
+ }
231
+ .cl-chev-open {
232
+ transform: rotate(180deg);
233
+ }
234
+ `;
235
+ document.head.appendChild(el);
236
+ }
237
+
287
238
  function createErrorBoundary() {
288
239
  if (!React || typeof React.Component !== 'function') {
289
240
  return function NoopBoundary(props) { return (props && props.children) || null; };
@@ -318,304 +269,21 @@ window.__ModuleLoader__.load({
318
269
  }
319
270
  const ErrorBoundary = createErrorBoundary();
320
271
 
321
- function getActiveLocale(ctx) {
322
- try {
323
- if (ctx && ctx.locale && typeof ctx.locale.getSnapshot === 'function') {
324
- const snap = ctx.locale.getSnapshot();
325
- if (snap && snap.active && snap.active.startsWith('zh')) return zh;
326
- }
327
- } catch (err) {
328
- /* intentional fallback: default to english on snapshot error */
329
- }
330
- return en;
331
- }
332
-
333
- function StatusPanel({ stats, history, focus, sessionId, labels, onRefresh, onClearFocus, title, showBadge, compact }) {
334
- ensureCss();
335
- const L = labels || en;
336
- const warn = !!(stats && stats.lowBudget);
337
- const hasSavings = !!(stats && stats.savedTokens > 0);
338
- const barColor = warn ? 'var(--dsw-alias-state-error-primary, #ef4444)' : 'var(--dsw-alias-state-success-primary, #10b981)';
339
- const previewLen = compact ? 28 : 44;
340
- const children = [];
341
-
342
- if (title || showBadge) {
343
- children.push(React.createElement('div', {
344
- key: 'head',
345
- style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 8 }
346
- },
347
- title
348
- ? React.createElement('div', { style: { fontWeight: 600, fontSize: 13, display: 'flex', alignItems: 'center', gap: 6, color: 'var(--dsw-alias-label-primary)' } },
349
- React.createElement('span', { style: { opacity: 0.8 } }, '◐'),
350
- title
351
- )
352
- : React.createElement('span'),
353
- showBadge
354
- ? React.createElement('span', {
355
- className: 'cl-badge ' + (warn ? 'cl-badge-warn' : 'cl-badge-ok')
356
- }, warn ? L.lowBudgetBadge : (stats ? L.active : L.readyState))
357
- : null
358
- ));
359
- }
360
-
361
- if (stats) {
362
- children.push(React.createElement('div', { key: 'stats', className: 'cl-stat-grid' },
363
- React.createElement('div', { className: 'cl-stat-box' },
364
- React.createElement('div', { className: 'cl-stat-val' }, stats.savedTokens ? `${stats.savedTokens}` : '0'),
365
- React.createElement('div', { className: 'cl-stat-lbl' }, `${L.saved} ${L.tokens}`)
366
- ),
367
- React.createElement('div', { className: 'cl-stat-box' },
368
- React.createElement('div', { className: 'cl-stat-val' }, `${stats.savedPercent || 0}%`),
369
- React.createElement('div', { className: 'cl-stat-lbl' }, '% ' + L.saved)
370
- ),
371
- React.createElement('div', { className: 'cl-stat-box' },
372
- React.createElement('div', { className: 'cl-stat-val' }, `${stats.calls || 0}`),
373
- React.createElement('div', { className: 'cl-stat-lbl' }, L.ops)
374
- )
375
- ));
376
- } else {
377
- children.push(React.createElement('div', {
378
- key: 'nodata',
379
- style: { fontSize: 12, color: 'var(--dsw-alias-label-secondary)', padding: '6px 0' }
380
- }, L.noData));
381
- }
382
-
383
- if (stats && stats.lowBudget && !showBadge) {
384
- children.push(React.createElement('div', {
385
- key: 'warn',
386
- className: 'cl-banner-warn',
387
- style: { marginTop: 8 }
388
- }, '⚠ ' + L.lowBudget));
389
- }
390
-
391
- if (stats && stats.budgetLimit) {
392
- children.push(React.createElement('div', { key: 'budget', className: 'cl-bar-box', style: { marginTop: 8 } },
393
- React.createElement('div', {
394
- style: { display: 'flex', justifyContent: 'space-between', fontSize: 11, color: warn ? 'var(--dsw-alias-state-error-primary, #ef4444)' : 'var(--dsw-alias-label-secondary)' }
395
- },
396
- React.createElement('span', { style: { fontWeight: 500 } }, L.budget),
397
- React.createElement('span', null, `${stats.budgetUsed || 0} / ${stats.budgetLimit} (${stats.budgetPercent || 0}%)`)
398
- ),
399
- React.createElement('div', { className: 'cl-bar-track' },
400
- React.createElement('div', {
401
- className: 'cl-bar-fill',
402
- style: { width: Math.min(100, stats.budgetPercent || 0) + '%', background: barColor }
403
- })
404
- )
405
- ));
406
- }
407
-
408
- // Active focus paths section
409
- const focusPaths = (focus && focus.paths) || [];
410
- if (focusPaths.length > 0 || onClearFocus) {
411
- children.push(React.createElement('div', {
412
- key: 'focus',
413
- style: { marginTop: 10, paddingTop: 8, borderTop: '1px solid var(--dsw-alias-border-l2)' }
414
- },
415
- React.createElement('div', {
416
- style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 4 }
417
- },
418
- React.createElement('span', { style: { fontSize: 11, fontWeight: 600, color: 'var(--dsw-alias-label-secondary)' } }, L.focusPaths),
419
- focusPaths.length > 0 && onClearFocus
420
- ? React.createElement('button', {
421
- type: 'button',
422
- onClick: onClearFocus,
423
- style: { background: 'none', border: 'none', padding: 0, fontSize: 10, color: 'var(--dsw-alias-label-tertiary)', cursor: 'pointer', textDecoration: 'underline' }
424
- }, L.clearFocus)
425
- : null
426
- ),
427
- focusPaths.length > 0
428
- ? React.createElement('div', { style: { display: 'flex', flexWrap: 'wrap', gap: 4, marginTop: 4 } },
429
- ...focusPaths.slice(0, 4).map((p, i) => React.createElement('span', { key: i, className: 'cl-tag' }, p))
430
- )
431
- : React.createElement('div', { style: { fontSize: 11, color: 'var(--dsw-alias-label-tertiary)', fontStyle: 'italic' } }, L.noFocus)
432
- ));
433
- }
434
-
435
- if (history && history.length) {
436
- children.push(React.createElement('div', {
437
- key: 'hist',
438
- style: { marginTop: 10, paddingTop: 8, borderTop: '1px solid var(--dsw-alias-border-l2)' }
439
- },
440
- React.createElement('div', { style: { fontSize: 11, fontWeight: 600, color: 'var(--dsw-alias-label-tertiary)', marginBottom: 4 } }, L.history),
441
- ...history.slice(0, 3).map((h) => React.createElement('div', {
442
- key: h.id,
443
- className: 'cl-hist-item',
444
- style: { overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }
445
- }, `−${h.savedTokens} tk (${h.savedPercent}%) · ${(h.preview || '').slice(0, previewLen)}`))
446
- ));
447
- }
448
-
449
- if (onRefresh) {
450
- children.push(React.createElement('div', {
451
- key: 'act',
452
- style: { marginTop: 10, display: 'flex', justifyContent: 'flex-end' }
453
- },
454
- React.createElement('button', {
455
- type: 'button',
456
- onClick: onRefresh,
457
- className: 'cl-btn'
458
- }, L.refresh)
459
- ));
460
- }
461
-
462
- return React.createElement(React.Fragment, null, ...children);
463
- }
464
-
465
- function fetchLensStatus() {
466
- return fetch('/dsh-context-lens/status', { headers: { accept: 'application/json' } })
467
- .then((r) => r.ok ? r.json() : null);
468
- }
469
-
470
- function clearLensFocus(sessionId) {
471
- const q = sessionId ? '?sessionId=' + encodeURIComponent(sessionId) : '';
472
- return fetch('/dsh-context-lens/clear-focus' + q, { method: 'POST', headers: { accept: 'application/json' } })
473
- .then((r) => r.ok ? r.json() : null);
474
- }
475
-
476
- function LensTabInner({ ctx: _ctx, scope }) {
477
- ensureCss();
478
- const [stats, setStats] = React.useState(null);
479
- const [history, setHistory] = React.useState([]);
480
- const [focus, setFocus] = React.useState(null);
481
- const [sessionId, setSessionId] = React.useState(null);
482
- const L = React.useMemo(() => getActiveLocale(_ctx), [_ctx]);
483
- const apply = (j) => {
484
- if (j && j.stats) setStats(j.stats);
485
- if (j && j.history) setHistory(j.history || []);
486
- if (j && j.focus) setFocus(j.focus);
487
- if (j && j.sessionId) setSessionId(j.sessionId);
488
- };
489
- const refresh = () => fetchLensStatus().then(apply).catch(() => {});
490
- const handleClearFocus = () => clearLensFocus(sessionId).then(() => refresh()).catch(() => {});
491
- React.useEffect(() => { refresh(); }, []);
492
- return React.createElement('div', { style: { padding: 12 } },
493
- React.createElement(StatusPanel, {
494
- stats, history, focus, sessionId, labels: L, onRefresh: refresh, onClearFocus: handleClearFocus, title: 'Context Lens', showBadge: false, compact: false
495
- })
496
- );
497
- }
498
-
499
- function LensTab(props) {
500
- return React.createElement(ErrorBoundary, null,
501
- React.createElement(LensTabInner, props)
502
- );
503
- }
504
-
505
- function HeaderChip({ ctx: _ctx }) {
506
- ensureCss();
507
- const [stats, setStats] = React.useState(null);
508
- const [history, setHistory] = React.useState([]);
509
- const [focus, setFocus] = React.useState(null);
510
- const [sessionId, setSessionId] = React.useState(null);
511
- const [open, setOpen] = React.useState(false);
512
- const ref = React.useRef(null);
513
- const L = React.useMemo(() => getActiveLocale(_ctx), [_ctx]);
514
-
515
- const fetchStatus = () => fetchLensStatus().then((j) => {
516
- if (j && j.stats) setStats(j.stats);
517
- if (j && j.history) setHistory(j.history || []);
518
- if (j && j.focus) setFocus(j.focus);
519
- if (j && j.sessionId) setSessionId(j.sessionId);
520
- }).catch(() => {});
521
-
522
- const handleClearFocus = () => clearLensFocus(sessionId).then(() => fetchStatus()).catch(() => {});
523
-
524
- React.useEffect(() => {
525
- let alive = true;
526
- fetchStatus();
527
- const pollInterval = open ? 4000 : 15000;
528
- const id = setInterval(() => {
529
- if (!alive) return;
530
- if (typeof document !== 'undefined' && document.visibilityState === 'hidden') return;
531
- fetchStatus();
532
- }, pollInterval);
533
-
534
- const onVisChange = () => {
535
- if (typeof document !== 'undefined' && document.visibilityState === 'visible' && alive) {
536
- fetchStatus();
537
- }
538
- };
539
- if (typeof document !== 'undefined') {
540
- document.addEventListener('visibilitychange', onVisChange);
541
- }
542
- return () => {
543
- alive = false;
544
- clearInterval(id);
545
- if (typeof document !== 'undefined') {
546
- document.removeEventListener('visibilitychange', onVisChange);
547
- }
548
- };
549
- }, [open]);
550
-
551
- React.useEffect(() => {
552
- if (!open) return;
553
- const onDocClick = (e) => {
554
- if (ref.current && !ref.current.contains(e.target)) setOpen(false);
555
- };
556
- document.addEventListener('click', onDocClick);
557
- return () => document.removeEventListener('click', onDocClick);
558
- }, [open]);
559
-
560
- const warn = !!(stats && stats.lowBudget);
561
- const hasSavings = !!(stats && stats.savedTokens > 0);
562
- const label = hasSavings ? `Lens ${stats.savedPercent}%` : 'Lens';
563
- const color = warn ? 'var(--dsw-alias-state-error-primary, #ef4444)' : 'var(--dsw-alias-label-secondary)';
564
- const borderColor = warn ? 'var(--dsw-alias-state-error-primary, #ef4444)' : open ? 'var(--dsw-alias-border-l1)' : 'var(--dsw-alias-border-l2)';
565
-
566
- const popover = open ? React.createElement('div', {
567
- style: {
568
- position: 'absolute', top: 'calc(100% + 6px)', right: 0, width: 280,
569
- background: 'var(--dsw-alias-bg-layer-3)', border: '1px solid var(--dsw-alias-border-l2)',
570
- borderRadius: 10, boxShadow: 'var(--dsw-alias-modal-shadow, var(--dsw-alias-shadow-large, 0 4px 16px var(--dsw-alias-border-l1)))', padding: '12px 14px',
571
- zIndex: 1000, fontSize: 12, color: 'var(--dsw-alias-label-primary)', cursor: 'default', textAlign: 'left'
572
- },
573
- onClick: (e) => e.stopPropagation()
574
- },
575
- React.createElement(StatusPanel, {
576
- stats, history, focus, sessionId, labels: L, onRefresh: fetchStatus, onClearFocus: handleClearFocus, title: 'Context Lens', showBadge: true, compact: true
577
- })
578
- ) : null;
579
-
580
- return React.createElement('div', { ref, style: { position: 'relative', display: 'inline-flex', alignItems: 'center' } },
581
- React.createElement('button', {
582
- type: 'button',
583
- title: hasSavings ? `Context Lens: ${L.saved} ${stats.savedTokens} ${L.tokens} (${stats.savedPercent}%)` : `Context Lens: ${L.active}`,
584
- onClick: () => setOpen((v) => !v),
585
- style: {
586
- appearance: 'none', font: 'inherit', cursor: 'pointer', display: 'inline-flex', alignItems: 'center', gap: 4,
587
- padding: '2px 8px', borderRadius: 999, fontSize: 11, lineHeight: '16px',
588
- background: open ? 'var(--dsw-alias-bg-layer-3)' : 'var(--dsw-alias-bg-layer-2)',
589
- color, border: '1px solid ' + borderColor, marginLeft: 6
590
- }
591
- },
592
- React.createElement('span', { style: { fontSize: 10, opacity: 0.8 } }, '◐'),
593
- label,
594
- warn ? React.createElement('span', { style: { color: 'var(--dsw-alias-state-error-primary, #ef4444)' } }, ' ⚠') : null
595
- ),
596
- popover
597
- );
598
- }
599
-
600
272
  function PluginCardInner({ ctx: _ctx, t, page }) {
601
273
  ensureCss();
602
274
  const [expanded, setExpanded] = React.useState(!!page);
603
275
  const [draft, setDraft] = React.useState({
604
276
  compressionMode: 'balanced',
605
277
  astSkeletonMaxDepth: 3,
606
- tokenSavingsTracking: true,
607
- budgetLimit: 100000,
608
- budgetAlertPercent: 90,
609
- autoCollapse: true,
610
278
  autoCompressThreshold: 4000
611
279
  });
612
280
  const [status, setStatus] = React.useState('loading');
613
281
  const [saving, setSaving] = React.useState(false);
614
282
  const [saveErr, setSaveErr] = React.useState('');
615
- const [previewIn, setPreviewIn] = React.useState('FAIL src/app.test.js\n ● should handle\n Expected 1 got 2\n at Object.<anonymous> (src/app.test.js:10:5)\nPASS src/ok.test.js\n');
283
+ const [previewIn, setPreviewIn] = React.useState(
284
+ 'FAIL src/app.test.js\n ● should handle\n Expected 1 got 2\n at Object.<anonymous> (src/app.test.js:10:5)\nPASS src/ok.test.js\n'
285
+ );
616
286
  const [previewOut, setPreviewOut] = React.useState('');
617
- const [stats, setStats] = React.useState(null);
618
- const [budgetWarn, setBudgetWarn] = React.useState(false);
619
287
 
620
288
  const scopeRef = React.useRef(null);
621
289
  if (!scopeRef.current && _ctx) {
@@ -624,12 +292,18 @@ window.__ModuleLoader__.load({
624
292
  if (s && typeof s.get === 'function') {
625
293
  scopeRef.current = s.get(NS);
626
294
  }
627
- } catch (e) { scopeRef.current = null; }
295
+ } catch (e) {
296
+ console.warn('[dsh-context-lens] configForms get failed', e && e.message || e);
297
+ scopeRef.current = null;
298
+ }
628
299
  }
629
300
  const scope = scopeRef.current;
630
301
 
631
302
  React.useEffect(() => {
632
- if (!scope) { setStatus('unavailable'); return; }
303
+ if (!scope) {
304
+ setStatus('unavailable');
305
+ return;
306
+ }
633
307
  let cancelled = false;
634
308
 
635
309
  const loadSnap = async () => {
@@ -648,6 +322,7 @@ window.__ModuleLoader__.load({
648
322
  }
649
323
  setStatus('ready');
650
324
  } catch (e) {
325
+ console.warn('[dsh-context-lens] loadSnap failed', e && e.message || e);
651
326
  if (!cancelled) setStatus('unavailable');
652
327
  }
653
328
  };
@@ -671,79 +346,6 @@ window.__ModuleLoader__.load({
671
346
  };
672
347
  }, [scope]);
673
348
 
674
- const [updateState, setUpdateState] = React.useState({
675
- checking: false,
676
- updating: false,
677
- currentVersion: '0.1.18',
678
- latestVersion: '',
679
- updateAvailable: false,
680
- canAutoUpdate: true,
681
- notice: '',
682
- error: ''
683
- });
684
-
685
- const checkUpdate = React.useCallback(async () => {
686
- setUpdateState((s) => ({ ...s, checking: true, error: '' }));
687
- try {
688
- const res = await fetch('/api/dsh-context-lens/update');
689
- if (!res.ok) throw new Error('HTTP ' + res.status);
690
- const data = await res.json().catch(() => ({}));
691
- setUpdateState((s) => ({
692
- ...s,
693
- checking: false,
694
- currentVersion: data.currentVersion || s.currentVersion,
695
- latestVersion: data.latestVersion || '',
696
- updateAvailable: !!data.updateAvailable,
697
- canAutoUpdate: data.canAutoUpdate !== false,
698
- }));
699
- } catch (_) {
700
- setUpdateState((s) => ({ ...s, checking: false }));
701
- }
702
- }, []);
703
-
704
- React.useEffect(() => {
705
- if (expanded) checkUpdate();
706
- }, [expanded, checkUpdate]);
707
-
708
- async function handleTriggerUpdate() {
709
- if (updateState.updating) return;
710
- setUpdateState((s) => ({ ...s, updating: true, error: '', notice: '' }));
711
- try {
712
- const res = await fetch('/api/dsh-context-lens/update', {
713
- method: 'POST',
714
- headers: { 'x-dsh-plugin-update': '1', 'x-dsh-context-lens-update': '1' }
715
- });
716
- const data = await res.json().catch(() => ({}));
717
- if (!res.ok || data.ok === false || data.error) {
718
- throw new Error(data.error || ('HTTP ' + res.status));
719
- }
720
- const newVer = data.updatedVersion || updateState.latestVersion || updateState.currentVersion;
721
- setUpdateState((s) => ({
722
- ...s,
723
- updating: false,
724
- updateAvailable: false,
725
- currentVersion: newVer,
726
- notice: tt('update.done', { version: newVer }),
727
- }));
728
- } catch (e) {
729
- setUpdateState((s) => ({
730
- ...s,
731
- updating: false,
732
- error: tt('update.failed', { error: e && e.message || String(e) }),
733
- }));
734
- }
735
- }
736
-
737
- React.useEffect(() => {
738
- if (!expanded) return;
739
- fetch('/dsh-context-lens/status').then((r) => r.ok ? r.json() : null).then((j) => {
740
- if (j && j.stats) {
741
- setStats(j.stats);
742
- setBudgetWarn(!!(j.stats.lowBudget && draft.autoCollapse !== false));
743
- }
744
- }).catch(() => {});
745
- }, [expanded, draft.autoCollapse]);
746
-
747
349
  const L = React.useMemo(() => getActiveLocale(_ctx), [_ctx]);
748
350
  const tt = React.useMemo(() => (t ? t : makeT(L, en)), [t, L]);
749
351
 
@@ -751,10 +353,20 @@ window.__ModuleLoader__.load({
751
353
  try {
752
354
  const prim = require('@deepseek-ai/dsh-client-ui-primitives');
753
355
  ChevronIcon = prim && prim.IconChevronDownOutline14;
754
- } catch (e) { ChevronIcon = null; }
356
+ } catch (e) {
357
+ ChevronIcon = null;
358
+ }
755
359
 
756
360
  const Chevron = ChevronIcon ? function ChevronNode(p) {
757
- return React.createElement(ChevronIcon, { className: 'cl-chev' + (p.open ? ' cl-chev-open' : ''), style: { marginLeft: 'auto', color: 'var(--dsw-alias-label-tertiary)', transition: 'transform .16s ease', transform: p.open ? 'rotate(180deg)' : 'none' } });
361
+ return React.createElement(ChevronIcon, {
362
+ className: 'cl-chev' + (p.open ? ' cl-chev-open' : ''),
363
+ style: {
364
+ marginLeft: 'auto',
365
+ color: 'var(--dsw-alias-label-tertiary)',
366
+ transition: 'transform .16s ease',
367
+ transform: p.open ? 'rotate(180deg)' : 'none'
368
+ }
369
+ });
758
370
  } : function Fallback(p) {
759
371
  return React.createElement('svg', {
760
372
  className: 'cl-chev' + (p.open ? ' cl-chev-open' : ''),
@@ -769,106 +381,64 @@ window.__ModuleLoader__.load({
769
381
  display: 'inline-block',
770
382
  flexShrink: 0
771
383
  }
772
- }, React.createElement('path', {
773
- d: 'M3.5 5.25L7 8.75L10.5 5.25',
774
- fill: 'none',
775
- stroke: 'currentColor',
776
- strokeWidth: '1.5',
777
- strokeLinecap: 'round',
778
- strokeLinejoin: 'round'
779
- }));
384
+ },
385
+ React.createElement('path', {
386
+ d: 'M3.5 5.25L7 8.75L10.5 5.25',
387
+ fill: 'none',
388
+ stroke: 'currentColor',
389
+ strokeWidth: '1.5',
390
+ strokeLinecap: 'round',
391
+ strokeLinejoin: 'round'
392
+ })
393
+ );
394
+ };
395
+
396
+ const handlePreview = () => {
397
+ const out = compressLogClient(previewIn, { mode: draft.compressionMode || 'balanced' });
398
+ setPreviewOut(out);
780
399
  };
781
400
 
782
- async function onSave() {
783
- if (!scope) { setSaveErr('Settings unavailable'); return; }
784
- setSaving(true); setSaveErr('');
401
+ const handleSave = async () => {
402
+ if (saving || !scope) return;
403
+ setSaving(true);
404
+ setSaveErr('');
785
405
  try {
786
- if (typeof scope.patch === 'function') {
787
- await scope.patch(draft);
788
- } else if (typeof scope.setAll === 'function') {
789
- await scope.setAll(draft);
790
- } else {
791
- const keys = Object.keys(draft);
792
- const errs = [];
793
- for (const k of keys) {
794
- try { await scope.set(k, draft[k]); } catch (e) { errs.push(k + ': ' + (e && e.message || String(e))); }
795
- }
796
- if (errs.length) throw new Error(errs.join('; '));
406
+ if (typeof scope.update === 'function') {
407
+ await scope.update(draft);
408
+ } else if (typeof scope.set === 'function') {
409
+ await scope.set(NS, draft);
797
410
  }
798
411
  } catch (e) {
799
- setSaveErr(e && e.message || String(e));
412
+ console.error('[dsh-context-lens] save failed', e);
413
+ setSaveErr(e && e.message ? e.message : String(e));
800
414
  } finally {
801
415
  setSaving(false);
802
416
  }
803
- }
804
-
805
- async function onPreview() {
806
- try {
807
- const res = await fetch('/dsh-context-lens/compress-preview', {
808
- method: 'POST',
809
- headers: { 'content-type': 'application/json' },
810
- body: JSON.stringify({ text: previewIn, mode: draft.compressionMode || 'balanced' })
811
- });
812
- const j = await res.json();
813
- setPreviewOut((j && j.compressed) || (j && j.error) || '');
814
- } catch (e) {
815
- setPreviewOut(String(e && e.message || e));
816
- }
817
- }
417
+ };
818
418
 
819
- return React.createElement(page ? 'div' : 'li', { className: page ? 'cl-page' : 'cl-section-card' },
820
- React.createElement('button', {
821
- type: 'button',
822
- className: 'cl-section-header',
823
- style: page ? { display: 'none' } : undefined,
824
- onClick: () => setExpanded(!expanded),
825
- 'aria-expanded': expanded
826
- },
827
- React.createElement('span', { style: { display: 'flex', flexDirection: 'column', gap: 2 } },
828
- React.createElement('span', { className: 'cl-title', style: { color: 'var(--dsw-alias-label-primary)', fontSize: 15, fontWeight: 600, lineHeight: 1.4 } }, tt('title')),
829
- React.createElement('span', { className: 'cl-sub', style: { color: 'var(--dsw-alias-label-secondary)', fontSize: 13 } }, tt('sub') + (stats ? ' · ' + tt('saved') + ' ' + (stats.savedTokens || 0) + ' ' + tt('tokens') + ' (' + (stats.savedPercent || 0) + '%)' : ''))
419
+ return React.createElement(
420
+ page ? 'div' : 'li',
421
+ { className: page ? 'cl-page' : 'cl-section-card' },
422
+ React.createElement(
423
+ 'div',
424
+ {
425
+ className: 'cl-header',
426
+ style: page ? { display: 'none' } : undefined,
427
+ onClick: () => setExpanded((v) => !v)
428
+ },
429
+ React.createElement('div', null,
430
+ React.createElement('div', { className: 'cl-title' }, tt('title')),
431
+ React.createElement('div', { className: 'cl-sub' }, tt('sub'))
830
432
  ),
831
433
  React.createElement(Chevron, { open: expanded })
832
434
  ),
833
- expanded ? React.createElement('div', { className: 'cl-body', style: { borderTop: '1px solid var(--dsw-alias-border-l2)', paddingTop: 12 } },
834
- React.createElement('div', {
835
- style: {
836
- display: 'flex', alignItems: 'center', justifyContent: 'space-between',
837
- padding: '6px 10px', marginBottom: 10, borderRadius: 8,
838
- background: 'var(--dsw-alias-bg-layer-2)', border: '1px solid var(--dsw-alias-border-l2)', fontSize: 12
839
- }
840
- },
841
- React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 8 } },
842
- React.createElement('span', { style: { fontWeight: 500, color: 'var(--dsw-alias-label-primary)' } },
843
- 'v' + (updateState.currentVersion || '0.1.18')
844
- ),
845
- updateState.checking
846
- ? React.createElement('span', { style: { color: 'var(--dsw-alias-label-tertiary)', fontSize: 11 } }, tt('update.checking'))
847
- : updateState.updateAvailable
848
- ? React.createElement('span', { className: 'cl-badge cl-badge-warn' },
849
- tt('update.available', { latestVersion: updateState.latestVersion, currentVersion: updateState.currentVersion })
850
- )
851
- : React.createElement('span', { className: 'cl-badge cl-badge-ok' },
852
- '✓ ' + tt('update.up_to_date')
853
- )
854
- ),
855
- updateState.updateAvailable
856
- ? React.createElement('button', {
857
- type: 'button',
858
- className: 'cl-btn cl-btn-primary',
859
- disabled: updateState.updating,
860
- onClick: handleTriggerUpdate,
861
- style: { padding: '3px 8px', fontSize: 11 }
862
- }, updateState.updating ? tt('update.updating') : tt('update.btn'))
863
- : null
864
- ),
865
- updateState.notice ? React.createElement('div', { className: 'cl-banner-ok', style: { marginBottom: 10, padding: '8px 12px', borderRadius: 8, background: 'color-mix(in srgb, var(--dsw-alias-state-success-primary, #10b981) 8%, transparent)', border: '1px solid var(--dsw-alias-state-success-primary, #10b981)', color: 'var(--dsw-alias-state-success-primary, #10b981)', fontSize: 12 } }, '✓ ' + updateState.notice) : null,
866
- updateState.error ? React.createElement('div', { className: 'cl-banner-warn', style: { marginBottom: 10 } }, updateState.error) : null,
867
- budgetWarn ? React.createElement('div', { className: 'cl-banner-warn', style: { marginBottom: 12 } }, '⚠ ' + tt('lowBudget')) : null,
435
+ (expanded || page) ? React.createElement(
436
+ 'div',
437
+ { className: 'cl-body' },
868
438
  status === 'loading' ? React.createElement('div', { style: { padding: 12, color: 'var(--dsw-alias-label-secondary)' } }, 'Loading…') :
869
439
  status === 'unavailable' ? React.createElement('div', { style: { padding: 12, color: 'var(--dsw-alias-label-secondary)' } }, 'Settings unavailable — plugin not registered on host yet') :
870
440
  React.createElement(React.Fragment, null,
871
- React.createElement('div', { className: 'cl-field', style: { display: 'flex', flexDirection: 'column', gap: 6, padding: '8px 0' } },
441
+ React.createElement('div', { className: 'cl-field' },
872
442
  React.createElement('label', { style: { fontSize: 13, color: 'var(--dsw-alias-label-secondary)' } }, tt('mode')),
873
443
  React.createElement('select', {
874
444
  className: 'cl-input',
@@ -880,80 +450,81 @@ window.__ModuleLoader__.load({
880
450
  React.createElement('option', { value: 'aggressive' }, 'aggressive')
881
451
  )
882
452
  ),
883
- React.createElement('div', { className: 'cl-field', style: { display: 'flex', flexDirection: 'column', gap: 6, padding: '8px 0' } },
453
+ React.createElement('div', { className: 'cl-field' },
884
454
  React.createElement('label', { style: { fontSize: 13, color: 'var(--dsw-alias-label-secondary)' } }, tt('depth')),
885
455
  React.createElement('input', {
886
456
  className: 'cl-input',
887
- type: 'number', min: 1, max: 10,
457
+ type: 'number',
458
+ min: 1,
459
+ max: 10,
888
460
  value: draft.astSkeletonMaxDepth,
889
461
  onChange: (e) => setDraft((d) => ({ ...d, astSkeletonMaxDepth: parseInt(e.target.value, 10) || 3 }))
890
462
  })
891
463
  ),
892
- React.createElement('div', { className: 'cl-field', style: { display: 'flex', flexDirection: 'row', alignItems: 'center', gap: 8, padding: '8px 0' } },
893
- React.createElement('input', {
894
- type: 'checkbox',
895
- checked: !!draft.tokenSavingsTracking,
896
- onChange: (e) => setDraft((d) => ({ ...d, tokenSavingsTracking: e.target.checked })),
897
- id: 'cl-tracking'
898
- }),
899
- React.createElement('label', { htmlFor: 'cl-tracking', style: { fontSize: 13, color: 'var(--dsw-alias-label-primary)' } }, tt('tracking'))
900
- ),
901
- React.createElement('div', { className: 'cl-field', style: { display: 'flex', flexDirection: 'row', alignItems: 'center', gap: 8, padding: '8px 0' } },
902
- React.createElement('input', {
903
- type: 'checkbox',
904
- checked: !!draft.autoCollapse,
905
- onChange: (e) => setDraft((d) => ({ ...d, autoCollapse: e.target.checked })),
906
- id: 'cl-autocollapse'
907
- }),
908
- React.createElement('label', { htmlFor: 'cl-autocollapse', style: { fontSize: 13, color: 'var(--dsw-alias-label-primary)' } }, tt('autoCollapse'))
909
- ),
910
- React.createElement('div', { className: 'cl-field', style: { display: 'flex', flexDirection: 'column', gap: 6, padding: '8px 0' } },
911
- React.createElement('label', { style: { fontSize: 13, color: 'var(--dsw-alias-label-secondary)' } }, tt('budget') + ' (tokens)'),
912
- React.createElement('input', {
913
- className: 'cl-input', type: 'number', min: 1000, step: 1000,
914
- value: draft.budgetLimit,
915
- onChange: (e) => setDraft((d) => ({ ...d, budgetLimit: parseInt(e.target.value, 10) || 100000 }))
916
- })
917
- ),
918
- React.createElement('div', { className: 'cl-field', style: { display: 'flex', flexDirection: 'column', gap: 6, padding: '8px 0' } },
919
- React.createElement('label', { style: { fontSize: 13, color: 'var(--dsw-alias-label-secondary)' } }, tt('budgetAlertPercent')),
920
- React.createElement('input', {
921
- className: 'cl-input', type: 'number', min: 50, max: 99, step: 1,
922
- value: draft.budgetAlertPercent,
923
- onChange: (e) => setDraft((d) => ({ ...d, budgetAlertPercent: parseInt(e.target.value, 10) || 90 }))
924
- })
925
- ),
926
- React.createElement('div', { className: 'cl-field', style: { display: 'flex', flexDirection: 'column', gap: 6, padding: '8px 0' } },
464
+ React.createElement('div', { className: 'cl-field' },
927
465
  React.createElement('label', { style: { fontSize: 13, color: 'var(--dsw-alias-label-secondary)' } }, tt('threshold')),
928
466
  React.createElement('input', {
929
- className: 'cl-input', type: 'number', min: 0, step: 500,
467
+ className: 'cl-input',
468
+ type: 'number',
469
+ min: 0,
470
+ step: 500,
930
471
  value: draft.autoCompressThreshold,
931
472
  onChange: (e) => setDraft((d) => ({ ...d, autoCompressThreshold: parseInt(e.target.value, 10) || 0 }))
932
473
  })
933
474
  ),
934
- React.createElement('div', { className: 'cl-field', style: { display: 'flex', flexDirection: 'column', gap: 6, padding: '8px 0' } },
475
+ React.createElement('div', { className: 'cl-field' },
935
476
  React.createElement('label', { style: { fontSize: 13, color: 'var(--dsw-alias-label-secondary)' } }, tt('preview') + ' (' + tt('previewApprox') + ')'),
936
477
  React.createElement('textarea', {
937
478
  value: previewIn,
938
479
  onChange: (e) => setPreviewIn(e.target.value),
939
480
  placeholder: tt('placeholder'),
940
- rows: 5,
941
- style: { border: '1px solid var(--dsw-alias-border-l2)', background: 'var(--dsw-alias-bg-layer-2)', color: 'var(--dsw-alias-label-primary)', borderRadius: 8, padding: 12, fontSize: 12, fontFamily: 'monospace' }
481
+ rows: 4,
482
+ style: {
483
+ border: '1px solid var(--dsw-alias-border-l2)',
484
+ background: 'var(--dsw-alias-bg-layer-2)',
485
+ color: 'var(--dsw-alias-label-primary)',
486
+ borderRadius: 8,
487
+ padding: 10,
488
+ fontSize: 12,
489
+ fontFamily: 'monospace'
490
+ }
942
491
  }),
943
492
  React.createElement('button', {
944
493
  type: 'button',
945
494
  className: 'cl-btn cl-btn-primary',
946
- onClick: onPreview,
495
+ onClick: handlePreview,
947
496
  style: { alignSelf: 'flex-start', marginTop: 4 }
948
497
  }, tt('compress')),
949
- previewOut ? React.createElement('pre', { style: { whiteSpace: 'pre-wrap', fontSize: 12, background: 'var(--dsw-alias-bg-layer-2)', padding: 12, borderRadius: 8, maxHeight: 200, overflow: 'auto', border: '1px solid var(--dsw-alias-border-l2)', marginTop: 8 } }, previewOut) : null
498
+ previewOut ? React.createElement('pre', {
499
+ style: {
500
+ whiteSpace: 'pre-wrap',
501
+ fontSize: 12,
502
+ background: 'var(--dsw-alias-bg-layer-2)',
503
+ padding: 10,
504
+ borderRadius: 8,
505
+ maxHeight: 180,
506
+ overflow: 'auto',
507
+ border: '1px solid var(--dsw-alias-border-l2)',
508
+ marginTop: 6
509
+ }
510
+ }, previewOut) : null
950
511
  ),
951
- saveErr ? React.createElement('div', { style: { color: 'var(--dsw-alias-state-error-primary, #ef4444)', fontSize: 12, padding: '4px 0' } }, saveErr) : null,
952
- React.createElement('div', { className: 'cl-foot', style: { borderTop: '1px solid var(--dsw-alias-border-l2)', display: 'flex', justifyContent: 'flex-end', alignItems: 'center', gap: 8, paddingTop: 12 } },
512
+ saveErr ? React.createElement('div', {
513
+ style: { color: 'var(--dsw-alias-state-error-primary)', fontSize: 12, padding: '4px 0' }
514
+ }, saveErr) : null,
515
+ React.createElement('div', {
516
+ style: {
517
+ borderTop: '1px solid var(--dsw-alias-border-l2)',
518
+ display: 'flex',
519
+ justifyContent: 'flex-end',
520
+ paddingTop: 12,
521
+ marginTop: 8
522
+ }
523
+ },
953
524
  React.createElement('button', {
954
525
  type: 'button',
955
526
  className: 'cl-btn cl-btn-primary' + (saving ? ' cl-btn-disabled' : ''),
956
- onClick: onSave,
527
+ onClick: handleSave,
957
528
  disabled: saving
958
529
  }, saving ? tt('saving') : tt('ready'))
959
530
  )
@@ -963,11 +534,9 @@ window.__ModuleLoader__.load({
963
534
  }
964
535
 
965
536
  function PluginCard(props) {
966
- // Row seat (plugins.row.config): one-liner for the summary, the form without
967
- // our header/card chrome for the page (the host page draws those itself).
968
537
  if (props && props.view === 'summary') {
969
538
  return React.createElement('span', { className: 'cl-sub' },
970
- 'Context lens: token savings, compaction and budget insights.');
539
+ 'Context lens: AST code skeletons and test log compression.');
971
540
  }
972
541
  const page = !!(props && props.view === 'page');
973
542
  return React.createElement(ErrorBoundary, null,
@@ -976,13 +545,17 @@ window.__ModuleLoader__.load({
976
545
  }
977
546
 
978
547
  module.exports.inject = ['slots', 'locale', 'configForms'];
548
+
979
549
  module.exports.apply = function apply(ctx) {
550
+ ensureCss();
551
+
980
552
  let hasEffect = false;
981
553
  try {
982
554
  hasEffect = typeof ctx.effect === 'function';
983
555
  } catch (_) {
984
556
  hasEffect = false;
985
557
  }
558
+
986
559
  if (hasEffect) {
987
560
  ctx.effect(() => {
988
561
  try {
@@ -995,43 +568,41 @@ window.__ModuleLoader__.load({
995
568
  }
996
569
  }, 'dsh-context-lens: locale');
997
570
  } else {
998
- try { ctx.locale.register(NS, { en, zh }); } catch (e) { console.warn('[dsh-context-lens] locale register failed', e && e.message || e); }
571
+ try {
572
+ ctx.locale?.register?.(NS, { en, zh });
573
+ } catch (e) {
574
+ console.warn('[dsh-context-lens] locale register failed', e && e.message || e);
575
+ }
999
576
  }
577
+
1000
578
  if (!ctx.slots) return;
579
+
1001
580
  let registered = false;
1002
- // Primary seat for the current core (0.1.6-alpha.2): the plugin-list seat
1003
- // 'plugins.item', which is the one the Plugins page renders as the plugin's own
1004
- // page with its configuration (the host draws the title, icon, crumb and
1005
- // padding and asks for view 'summary' or view 'page'). The row seat and the
1006
- // legacy settings.plugin.item seat below stay as fallbacks.
1007
581
  if (typeof ctx.slots.inject === 'function') {
1008
582
  try {
1009
583
  ctx.slots.inject('plugins.item', () => ctx.slots.register({
1010
584
  name: 'plugins.item',
1011
- id: ROW_ID,
1012
- order: 60,
1013
- label: () => 'Context Lens',
1014
- locale: NS,
1015
- inject: () => ({ ctx })
585
+ key: PKG,
586
+ title: () => en.title
1016
587
  }, PluginCard));
588
+
1017
589
  ctx.slots.inject('plugins.row.config', () => ctx.slots.register({
1018
590
  name: 'plugins.row.config',
1019
591
  key: ROW_CONFIG_KEY,
1020
- locale: NS,
1021
- inject: () => ({ ctx })
592
+ title: () => en.title
1022
593
  }, PluginCard));
1023
594
  } catch (e) {
1024
- console.error('[dsh-context-lens] plugins.item / plugins.row.config inject failed', e && e.stack || e);
595
+ console.error('[dsh-context-lens] plugins.row.config inject failed', e && e.stack || e);
1025
596
  }
1026
597
  }
598
+
1027
599
  const doRegister = () => {
1028
600
  if (registered) return;
1029
601
  try {
1030
602
  const dispose = ctx.slots.register({
1031
603
  name: 'settings.plugin.item',
1032
604
  key: NS,
1033
- locale: NS,
1034
- inject: () => ({ ctx })
605
+ title: () => en.title
1035
606
  }, PluginCard);
1036
607
  registered = true;
1037
608
  return dispose;
@@ -1040,144 +611,38 @@ window.__ModuleLoader__.load({
1040
611
  throw e;
1041
612
  }
1042
613
  };
614
+
1043
615
  if (typeof ctx.slots.inject === 'function') {
1044
616
  try {
1045
617
  const ok = ctx.slots.inject('settings.plugin.item', doRegister);
1046
- if (!ok) console.warn('[dsh-context-lens] settings.plugin.item inject returned false — slot not declared yet');
618
+ if (!ok) console.warn('[dsh-context-lens] settings.plugin.item inject returned false');
1047
619
  } catch (e) {
1048
620
  console.error('[dsh-context-lens] settings.plugin.item inject failed', e && e.stack || e);
1049
- try { doRegister(); } catch (e2) { console.error('[dsh-context-lens] fallback register failed', e2 && e2.stack || e2); throw e2; }
621
+ try { doRegister(); } catch (e2) {
622
+ console.error('[dsh-context-lens] fallback register failed', e2 && e2.stack || e2);
623
+ throw e2;
624
+ }
1050
625
  throw e;
1051
626
  }
1052
627
  } else {
1053
- try { doRegister(); } catch (e) { console.error('[dsh-context-lens] direct register failed (no inject)', e && e.stack || e); throw e; }
1054
- }
1055
- if (typeof ctx.inject === "function") {
1056
628
  try {
1057
- ctx.inject(["sidebarRightTabs"], (sctx) => {
1058
- const tabs = sctx && sctx.sidebarRightTabs;
1059
- if (!tabs || typeof tabs.register !== "function") return;
1060
- try {
1061
- const def = {
1062
- id: "@goodandready/dsh-context-lens",
1063
- kind: "context-lens",
1064
- priority: "extension",
1065
- title: () => "Lens",
1066
- guide: [{
1067
- order: 50,
1068
- title: () => "Context Lens",
1069
- description: () => "AST compression, log filtering, token budget",
1070
- icon: () => React.createElement("span", null, "◐")
1071
- }]
1072
- };
1073
- if (typeof sctx.effect === "function") {
1074
- sctx.effect(() => tabs.register(def));
1075
- } else {
1076
- tabs.register(def);
1077
- }
1078
-
1079
- if (ctx.slots) {
1080
- const registerPaneTab = () => {
1081
- try {
1082
- return ctx.slots.register({
1083
- name: "sidebar.right.pane.tab",
1084
- key: "@goodandready/dsh-context-lens",
1085
- locale: NS,
1086
- inject: () => ({ ctx })
1087
- }, LensTab);
1088
- } catch (e) {
1089
- console.warn("[dsh-context-lens] native sidebar pane tab register failed", e && e.message || e);
1090
- }
1091
- };
1092
- if (typeof ctx.slots.inject === "function") {
1093
- try {
1094
- const ok = ctx.slots.inject("sidebar.right.pane.tab", registerPaneTab);
1095
- if (!ok) console.warn("[dsh-context-lens] sidebar.right.pane.tab inject returned false");
1096
- } catch (e) {
1097
- console.warn("[dsh-context-lens] sidebar.right.pane.tab inject failed, falling back to direct register", e && e.message || e);
1098
- try {
1099
- registerPaneTab();
1100
- } catch (e2) {
1101
- console.warn("[dsh-context-lens] registerPaneTab direct register fallback failed", e2 && e2.message || e2);
1102
- }
1103
- }
1104
- } else {
1105
- try {
1106
- registerPaneTab();
1107
- } catch (err) {
1108
- console.warn("[dsh-context-lens] registerPaneTab direct register failed", err && err.message || err);
1109
- }
1110
- }
1111
- }
1112
- } catch (e) {
1113
- console.warn("[dsh-context-lens] native sidebar registration failed", e && e.message || e);
1114
- }
1115
- });
1116
- } catch (err) {
1117
- console.warn("[dsh-context-lens] native sidebar inject failed", err && err.message || err);
629
+ ctx.slots.register({
630
+ name: 'plugins.row.config',
631
+ key: ROW_CONFIG_KEY,
632
+ title: () => en.title
633
+ }, PluginCard);
634
+ } catch (e) {
635
+ console.warn('[dsh-context-lens] plugins.row.config register failed', e && e.message || e);
1118
636
  }
1119
- }
1120
-
1121
- if (typeof ctx.inject === 'function') {
1122
637
  try {
1123
- ctx.inject(['betterSidebar'], (sctx) => {
1124
- const svc = sctx && sctx.betterSidebar;
1125
- if (!svc || typeof svc.registerTab !== 'function') return;
1126
- try {
1127
- const tabDef = {
1128
- id: 'dsh-context-lens:tab',
1129
- title: () => 'Lens',
1130
- icon: () => React.createElement('span', null, '◐'),
1131
- order: 50,
1132
- component: ({ scope }) => React.createElement(LensTab, { ctx, scope })
1133
- };
1134
- if (typeof sctx.effect === 'function') {
1135
- sctx.effect(() => svc.registerTab(tabDef));
1136
- } else {
1137
- svc.registerTab(tabDef);
1138
- }
1139
- } catch (e) {
1140
- console.warn('[dsh-context-lens] betterSidebar registerTab failed', e && e.message || e);
1141
- }
1142
- });
1143
- } catch (err) {
1144
- console.warn('[dsh-context-lens] betterSidebar inject failed', err && err.message || err);
1145
- }
1146
- }
1147
-
1148
- if (ctx.slots) {
1149
- const headerChipRegister = () => {
1150
- try {
1151
- return ctx.slots.register({
1152
- name: 'conversation.session.header.utilities',
1153
- id: 'dsh-context-lens-header-chip',
1154
- order: 7,
1155
- inject: () => ({ ctx })
1156
- }, HeaderChip);
1157
- } catch (e) {
1158
- console.warn('[dsh-context-lens] header chip register failed', e && e.message || e);
1159
- }
1160
- };
1161
- if (typeof ctx.slots.inject === 'function') {
1162
- try {
1163
- ctx.slots.inject('conversation.session.header.utilities', headerChipRegister);
1164
- } catch (e) {
1165
- console.warn('[dsh-context-lens] utilities inject failed, falling back to direct register', e && e.message || e);
1166
- try {
1167
- headerChipRegister();
1168
- } catch (e2) {
1169
- console.warn('[dsh-context-lens] headerChipRegister fallback failed', e2 && e2.message || e2);
1170
- }
1171
- }
1172
- } else {
1173
- try {
1174
- headerChipRegister();
1175
- } catch (err) {
1176
- console.warn('[dsh-context-lens] headerChipRegister direct register failed', err && err.message || err);
1177
- }
638
+ doRegister();
639
+ } catch (e) {
640
+ console.error('[dsh-context-lens] direct register failed (no inject)', e && e.stack || e);
641
+ throw e;
1178
642
  }
1179
643
  }
1180
644
  };
645
+
1181
646
  return module.exports;
1182
647
  }
1183
- });
648
+ });