@goodandready/dsh-context-lens 0.1.14 → 0.1.16
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/README.md +15 -0
- package/README.ru.md +15 -0
- package/lib/client.js +401 -105
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -174,6 +174,13 @@ Audit fixes (#33–#47, #18):
|
|
|
174
174
|
- **Test**: Added regression test suite simulating strict Cordis Context Proxy behavior.
|
|
175
175
|
|
|
176
176
|
|
|
177
|
+
## Changed in v0.1.15
|
|
178
|
+
|
|
179
|
+
- **Feature (#54)**: Support native DSH right sidebar (`ctx.sidebarRightTabs` + `sidebar.right.pane.tab` slot) introduced in DSH 0.1.5-alpha.1.
|
|
180
|
+
- **Compatibility (#54)**: Preserve legacy `dsh-better-sidebar` integration with deterministic, non-conflicting IDs (`@goodandready/dsh-context-lens` vs `dsh-context-lens:tab`).
|
|
181
|
+
- **Resilience (#54)**: Clean boot and graceful fallback across all 4 layouts (Native only, Legacy only, Both active, Neither active).
|
|
182
|
+
- **Tests (#54)**: Added comprehensive test matrix in `test/sidebar-matrix-54.test.mjs`.
|
|
183
|
+
|
|
177
184
|
## Changed in v0.1.14
|
|
178
185
|
|
|
179
186
|
- **Fix (#52)**: Multiline AST signature parsing in `skeletonizer.js` for TypeScript, JavaScript, Rust, and Go with complex parameter types and return annotations.
|
|
@@ -181,3 +188,11 @@ Audit fixes (#33–#47, #18):
|
|
|
181
188
|
- **Quality & UX (#52)**: Dynamic RU/EN locale detection in `HeaderChip`, `LensTab`, and `StatusPanel` via `ctx.locale` for seamless UI integration.
|
|
182
189
|
- **Perf & Stability (#52)**: Adaptive polling in `HeaderChip` pausing on hidden tabs (`visibilitychange`) and polling actively (4s) only when popover is open.
|
|
183
190
|
- **Reliability (#52)**: Atomic settings save (`scope.patch` / `scope.setAll`) in `PluginCard` with graceful fallback.
|
|
191
|
+
|
|
192
|
+
### UI & Styling System (v0.1.16+)
|
|
193
|
+
|
|
194
|
+
Context Lens shares its visual language and component architecture with `dsh-clinebot`:
|
|
195
|
+
- **Native Design System**: 100% theme integration using `--dsw-alias-*` tokens.
|
|
196
|
+
- **Resilient UI Surfaces**: Every component (`PluginCard`, `LensTab`, `StatusPanel`) is protected by an `ErrorBoundary` with instant retry capability.
|
|
197
|
+
- **Reactive Settings**: Live updates via `scope.subscribe()` and zero-delay reads via `scope.getSnapshot()`.
|
|
198
|
+
- **Adaptive Telemetry**: Popover and tab display metric cards for tokens saved, percentage reduced, and live budget utilization.
|
package/README.ru.md
CHANGED
|
@@ -163,6 +163,13 @@ MIT © [GooDAnDReaDY](https://github.com/GooDAnDReaDY)
|
|
|
163
163
|
- **Тесты**: Добавлены регрессионные тесты со строгим Cordis Context Proxy.
|
|
164
164
|
|
|
165
165
|
|
|
166
|
+
## Changed in v0.1.15
|
|
167
|
+
|
|
168
|
+
- **Функциональность (#54)**: Поддержка встроенной правой боковой панели DSH (`ctx.sidebarRightTabs` + слот `sidebar.right.pane.tab`) из DSH 0.1.5-alpha.1.
|
|
169
|
+
- **Совместимость (#54)**: Сохранена интеграция с легаси `dsh-better-sidebar` с детерминированными неконфликтующими идентификаторами (`@goodandready/dsh-context-lens` и `dsh-context-lens:tab`).
|
|
170
|
+
- **Отказоустойчивость (#54)**: Чистая инициализация во всех 4 конфигурациях (только Native, только Legacy, обе активны, обе отсутствуют).
|
|
171
|
+
- **Тесты (#54)**: Добавлена матрица тестов в `test/sidebar-matrix-54.test.mjs`.
|
|
172
|
+
|
|
166
173
|
## Changed in v0.1.14
|
|
167
174
|
|
|
168
175
|
- **Исправление (#52)**: Поддержка многострочных сигнатур функций и методов в `skeletonizer.js` (TypeScript, JavaScript, Rust, Go) со сложными типами параметров и аннотациями возвращаемых значений.
|
|
@@ -170,3 +177,11 @@ MIT © [GooDAnDReaDY](https://github.com/GooDAnDReaDY)
|
|
|
170
177
|
- **Качество и локализация (#52)**: Динамическое переключение языка интерфейса (RU/EN) в чипе шапки (`HeaderChip`), боковой вкладке (`LensTab`) и панели статуса (`StatusPanel`) через `ctx.locale`.
|
|
171
178
|
- **Производительность (#52)**: Адаптивный поллинг в `HeaderChip` с паузой при неактивной вкладке браузера (`document.visibilityState`) и учащённым опросом (4 сек) только при открытом поповере.
|
|
172
179
|
- **Стабильность (#52)**: Атомарное сохранение конфигурации в карточке настроек (`scope.patch` / `scope.setAll`) без риска частичной записи.
|
|
180
|
+
|
|
181
|
+
### Визуальный стиль и отказоустойчивость UI (v0.1.16+)
|
|
182
|
+
|
|
183
|
+
Context Lens приведен к единому стандарту оформления `dsh-clinebot`:
|
|
184
|
+
- **Нативные токены**: 100% совместимость со светлой и тёмной темами DSH через токены `--dsw-alias-*`.
|
|
185
|
+
- **Изоляция сбоев интерфейса**: Компоненты (`PluginCard`, `LensTab`, `StatusPanel`) обёрнуты в `ErrorBoundary` с кнопкой быстрого повтора («Retry»).
|
|
186
|
+
- **Реактивные настройки**: Автоматическое обновление через `scope.subscribe()` и мгновенное чтение через `scope.getSnapshot()`.
|
|
187
|
+
- **Наглядная телеметрия**: Компактные карточки метрик сэкономленных токенов, процента оптимизации и шкалы сессионного бюджета.
|
package/lib/client.js
CHANGED
|
@@ -58,10 +58,215 @@ window.__ModuleLoader__.load({
|
|
|
58
58
|
lowBudgetBadge: 'Мало бюджета ⚠'
|
|
59
59
|
};
|
|
60
60
|
|
|
61
|
-
|
|
61
|
+
function ensureCss() {
|
|
62
|
+
if (typeof document === 'undefined') return;
|
|
63
|
+
if (document.getElementById('dsh-context-lens-css')) return;
|
|
64
|
+
const style = document.createElement('style');
|
|
65
|
+
style.id = 'dsh-context-lens-css';
|
|
66
|
+
style.dataset.dshPlugin = NS;
|
|
67
|
+
style.textContent = `
|
|
68
|
+
.cl-section-card {
|
|
69
|
+
border: 1px solid var(--dsw-alias-border-l2);
|
|
70
|
+
background: var(--dsw-alias-bg-layer-3);
|
|
71
|
+
border-radius: 12px;
|
|
72
|
+
padding: 16px 18px;
|
|
73
|
+
margin-bottom: 12px;
|
|
74
|
+
display: flex;
|
|
75
|
+
flex-direction: column;
|
|
76
|
+
gap: 12px;
|
|
77
|
+
list-style: none;
|
|
78
|
+
}
|
|
79
|
+
.cl-section-header {
|
|
80
|
+
background: none;
|
|
81
|
+
border: none;
|
|
82
|
+
cursor: pointer;
|
|
83
|
+
display: flex;
|
|
84
|
+
align-items: center;
|
|
85
|
+
width: 100%;
|
|
86
|
+
padding: 0;
|
|
87
|
+
text-align: left;
|
|
88
|
+
}
|
|
89
|
+
.cl-badge {
|
|
90
|
+
font-size: 11px;
|
|
91
|
+
padding: 2px 8px;
|
|
92
|
+
border-radius: 999px;
|
|
93
|
+
border: 1px solid var(--dsw-alias-border-l2);
|
|
94
|
+
display: inline-flex;
|
|
95
|
+
align-items: center;
|
|
96
|
+
gap: 4px;
|
|
97
|
+
font-weight: 500;
|
|
98
|
+
}
|
|
99
|
+
.cl-badge-ok {
|
|
100
|
+
border-color: var(--dsw-alias-state-success-primary, #10b981);
|
|
101
|
+
color: var(--dsw-alias-state-success-primary, #10b981);
|
|
102
|
+
background: rgba(16, 185, 129, 0.08);
|
|
103
|
+
}
|
|
104
|
+
.cl-badge-warn {
|
|
105
|
+
border-color: var(--dsw-alias-state-error-primary, #ef4444);
|
|
106
|
+
color: var(--dsw-alias-state-error-primary, #ef4444);
|
|
107
|
+
background: rgba(239, 68, 68, 0.08);
|
|
108
|
+
}
|
|
109
|
+
.cl-stat-grid {
|
|
110
|
+
display: grid;
|
|
111
|
+
grid-template-columns: repeat(auto-fit, minmax(110px, 1fr));
|
|
112
|
+
gap: 10px;
|
|
113
|
+
margin-top: 4px;
|
|
114
|
+
}
|
|
115
|
+
.cl-stat-box {
|
|
116
|
+
padding: 10px 12px;
|
|
117
|
+
border: 1px solid var(--dsw-alias-border-l2);
|
|
118
|
+
border-radius: 8px;
|
|
119
|
+
background: var(--dsw-alias-bg-layer-2);
|
|
120
|
+
display: flex;
|
|
121
|
+
flex-direction: column;
|
|
122
|
+
gap: 3px;
|
|
123
|
+
}
|
|
124
|
+
.cl-stat-val {
|
|
125
|
+
font-size: 16px;
|
|
126
|
+
font-weight: 700;
|
|
127
|
+
color: var(--dsw-alias-label-primary);
|
|
128
|
+
}
|
|
129
|
+
.cl-stat-lbl {
|
|
130
|
+
font-size: 11px;
|
|
131
|
+
color: var(--dsw-alias-label-secondary);
|
|
132
|
+
}
|
|
133
|
+
.cl-bar-box {
|
|
134
|
+
display: flex;
|
|
135
|
+
flex-direction: column;
|
|
136
|
+
gap: 6px;
|
|
137
|
+
padding: 10px 12px;
|
|
138
|
+
border: 1px solid var(--dsw-alias-border-l2);
|
|
139
|
+
border-radius: 8px;
|
|
140
|
+
background: var(--dsw-alias-bg-layer-2);
|
|
141
|
+
}
|
|
142
|
+
.cl-bar-track {
|
|
143
|
+
width: 100%;
|
|
144
|
+
height: 8px;
|
|
145
|
+
border-radius: 999px;
|
|
146
|
+
background: var(--dsw-alias-bg-layer-1);
|
|
147
|
+
overflow: hidden;
|
|
148
|
+
border: 1px solid var(--dsw-alias-border-l2);
|
|
149
|
+
}
|
|
150
|
+
.cl-bar-fill {
|
|
151
|
+
height: 100%;
|
|
152
|
+
border-radius: 999px;
|
|
153
|
+
transition: width 0.3s;
|
|
154
|
+
}
|
|
155
|
+
.cl-input {
|
|
156
|
+
height: 34px;
|
|
157
|
+
border: 1px solid var(--dsw-alias-border-l2);
|
|
158
|
+
background: var(--dsw-alias-bg-layer-2);
|
|
159
|
+
color: var(--dsw-alias-label-primary);
|
|
160
|
+
border-radius: 8px;
|
|
161
|
+
padding: 0 12px;
|
|
162
|
+
font-size: 13px;
|
|
163
|
+
box-sizing: border-box;
|
|
164
|
+
}
|
|
165
|
+
.cl-input:focus {
|
|
166
|
+
outline: none;
|
|
167
|
+
border-color: var(--dsw-alias-state-brand-primary, #3b82f6);
|
|
168
|
+
}
|
|
169
|
+
.cl-btn {
|
|
170
|
+
appearance: none;
|
|
171
|
+
font: inherit;
|
|
172
|
+
cursor: pointer;
|
|
173
|
+
border: 1px solid var(--dsw-alias-border-l2);
|
|
174
|
+
border-radius: 8px;
|
|
175
|
+
padding: 6px 12px;
|
|
176
|
+
font-size: 12px;
|
|
177
|
+
background: var(--dsw-alias-bg-layer-2);
|
|
178
|
+
color: var(--dsw-alias-label-primary);
|
|
179
|
+
font-weight: 500;
|
|
180
|
+
display: inline-flex;
|
|
181
|
+
align-items: center;
|
|
182
|
+
justify-content: center;
|
|
183
|
+
gap: 6px;
|
|
184
|
+
transition: all 0.15s ease;
|
|
185
|
+
}
|
|
186
|
+
.cl-btn:hover:not(:disabled) {
|
|
187
|
+
background: var(--dsw-alias-bg-layer-4, var(--dsw-alias-bg-layer-2));
|
|
188
|
+
border-color: var(--dsw-alias-label-dimmed, var(--dsw-alias-border-l2));
|
|
189
|
+
}
|
|
190
|
+
.cl-btn-primary {
|
|
191
|
+
background: var(--dsw-alias-label-primary);
|
|
192
|
+
color: var(--dsw-alias-bg-layer-3);
|
|
193
|
+
border-color: transparent;
|
|
194
|
+
}
|
|
195
|
+
.cl-btn-primary:hover:not(:disabled) {
|
|
196
|
+
opacity: 0.88;
|
|
197
|
+
}
|
|
198
|
+
.cl-btn-disabled {
|
|
199
|
+
opacity: 0.5;
|
|
200
|
+
cursor: not-allowed;
|
|
201
|
+
}
|
|
202
|
+
.cl-banner-warn {
|
|
203
|
+
padding: 10px 14px;
|
|
204
|
+
border-radius: 8px;
|
|
205
|
+
background: rgba(239, 68, 68, 0.1);
|
|
206
|
+
border: 1px solid var(--dsw-alias-state-error-primary, #ef4444);
|
|
207
|
+
color: var(--dsw-alias-state-error-primary, #ef4444);
|
|
208
|
+
font-size: 12px;
|
|
209
|
+
display: flex;
|
|
210
|
+
align-items: center;
|
|
211
|
+
gap: 8px;
|
|
212
|
+
font-weight: 500;
|
|
213
|
+
}
|
|
214
|
+
.cl-hist-item {
|
|
215
|
+
font-size: 11px;
|
|
216
|
+
color: var(--dsw-alias-label-secondary);
|
|
217
|
+
padding: 4px 0;
|
|
218
|
+
border-bottom: 1px solid var(--dsw-alias-border-l3, var(--dsw-alias-border-l2));
|
|
219
|
+
}
|
|
220
|
+
.cl-hist-item:last-child {
|
|
221
|
+
border-bottom: none;
|
|
222
|
+
}
|
|
223
|
+
.cl-error-box {
|
|
224
|
+
padding: 12px;
|
|
225
|
+
border-radius: 8px;
|
|
226
|
+
background: rgba(239, 68, 68, 0.08);
|
|
227
|
+
border: 1px solid var(--dsw-alias-state-error-primary, #ef4444);
|
|
228
|
+
color: var(--dsw-alias-state-error-primary, #ef4444);
|
|
229
|
+
font-size: 12px;
|
|
230
|
+
}
|
|
231
|
+
`;
|
|
232
|
+
document.head.appendChild(style);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function createErrorBoundary() {
|
|
236
|
+
if (!React || typeof React.Component !== 'function') {
|
|
237
|
+
return function NoopBoundary(props) { return (props && props.children) || null; };
|
|
238
|
+
}
|
|
239
|
+
return class ErrorBoundary extends React.Component {
|
|
240
|
+
constructor(props) {
|
|
241
|
+
super(props);
|
|
242
|
+
this.state = { hasError: false, error: null };
|
|
243
|
+
}
|
|
244
|
+
static getDerivedStateFromError(error) {
|
|
245
|
+
return { hasError: true, error };
|
|
246
|
+
}
|
|
247
|
+
componentDidCatch(error, errorInfo) {
|
|
248
|
+
console.error('[dsh-context-lens] UI Error:', error, errorInfo);
|
|
249
|
+
}
|
|
250
|
+
render() {
|
|
251
|
+
if (this.state.hasError) {
|
|
252
|
+
return React.createElement('div', { className: 'cl-error-box' },
|
|
253
|
+
React.createElement('div', { style: { fontWeight: 600, marginBottom: 4 } }, '⚠ Context Lens UI Error'),
|
|
254
|
+
React.createElement('div', { style: { fontSize: 11, wordBreak: 'break-all' } }, String(this.state.error?.message || this.state.error)),
|
|
255
|
+
React.createElement('button', {
|
|
256
|
+
type: 'button',
|
|
257
|
+
className: 'cl-btn',
|
|
258
|
+
style: { marginTop: 8, fontSize: 11, padding: '3px 8px' },
|
|
259
|
+
onClick: () => this.setState({ hasError: false, error: null })
|
|
260
|
+
}, 'Retry')
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
return (this.props && this.props.children) || null;
|
|
264
|
+
}
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
const ErrorBoundary = createErrorBoundary();
|
|
62
268
|
|
|
63
|
-
|
|
64
|
-
function getActiveLocale(ctx) {
|
|
269
|
+
function getActiveLocale(ctx) {
|
|
65
270
|
try {
|
|
66
271
|
if (ctx && ctx.locale && typeof ctx.locale.getSnapshot === 'function') {
|
|
67
272
|
const snap = ctx.locale.getSnapshot();
|
|
@@ -72,13 +277,14 @@ window.__ModuleLoader__.load({
|
|
|
72
277
|
}
|
|
73
278
|
|
|
74
279
|
function StatusPanel({ stats, history, labels, onRefresh, title, showBadge, compact }) {
|
|
280
|
+
ensureCss();
|
|
75
281
|
const L = labels || en;
|
|
76
282
|
const warn = !!(stats && stats.lowBudget);
|
|
77
283
|
const hasSavings = !!(stats && stats.savedTokens > 0);
|
|
78
|
-
const barColor = warn ? '#
|
|
79
|
-
const previewLen = compact ? 28 :
|
|
80
|
-
const barH = compact ? 5 : 6;
|
|
284
|
+
const barColor = warn ? 'var(--dsw-alias-state-error-primary, #ef4444)' : 'var(--dsw-alias-state-success-primary, #10b981)';
|
|
285
|
+
const previewLen = compact ? 28 : 44;
|
|
81
286
|
const children = [];
|
|
287
|
+
|
|
82
288
|
if (title || showBadge) {
|
|
83
289
|
children.push(React.createElement('div', {
|
|
84
290
|
key: 'head',
|
|
@@ -92,78 +298,86 @@ window.__ModuleLoader__.load({
|
|
|
92
298
|
: React.createElement('span'),
|
|
93
299
|
showBadge
|
|
94
300
|
? React.createElement('span', {
|
|
95
|
-
|
|
96
|
-
fontSize: 11, padding: '1px 6px', borderRadius: 4,
|
|
97
|
-
background: warn ? 'rgba(215, 58, 74, 0.15)' : 'var(--dsw-alias-bg-layer-2)',
|
|
98
|
-
color: warn ? '#d73a4a' : 'var(--dsw-alias-label-tertiary)',
|
|
99
|
-
border: '1px solid ' + (warn ? 'rgba(215, 58, 74, 0.3)' : 'var(--dsw-alias-border-l2)')
|
|
100
|
-
}
|
|
301
|
+
className: 'cl-badge ' + (warn ? 'cl-badge-warn' : 'cl-badge-ok')
|
|
101
302
|
}, warn ? L.lowBudgetBadge : (stats ? L.active : L.readyState))
|
|
102
303
|
: null
|
|
103
304
|
));
|
|
104
305
|
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
306
|
+
|
|
307
|
+
if (stats) {
|
|
308
|
+
children.push(React.createElement('div', { key: 'stats', className: 'cl-stat-grid' },
|
|
309
|
+
React.createElement('div', { className: 'cl-stat-box' },
|
|
310
|
+
React.createElement('div', { className: 'cl-stat-val' }, stats.savedTokens ? `${stats.savedTokens}` : '0'),
|
|
311
|
+
React.createElement('div', { className: 'cl-stat-lbl' }, `${L.saved} ${L.tokens}`)
|
|
312
|
+
),
|
|
313
|
+
React.createElement('div', { className: 'cl-stat-box' },
|
|
314
|
+
React.createElement('div', { className: 'cl-stat-val' }, `${stats.savedPercent || 0}%`),
|
|
315
|
+
React.createElement('div', { className: 'cl-stat-lbl' }, '% ' + L.saved)
|
|
316
|
+
),
|
|
317
|
+
React.createElement('div', { className: 'cl-stat-box' },
|
|
318
|
+
React.createElement('div', { className: 'cl-stat-val' }, `${stats.calls || 0}`),
|
|
319
|
+
React.createElement('div', { className: 'cl-stat-lbl' }, L.ops)
|
|
320
|
+
)
|
|
321
|
+
));
|
|
322
|
+
} else {
|
|
323
|
+
children.push(React.createElement('div', {
|
|
324
|
+
key: 'nodata',
|
|
325
|
+
style: { fontSize: 12, color: 'var(--dsw-alias-label-secondary)', padding: '6px 0' }
|
|
326
|
+
}, L.noData));
|
|
327
|
+
}
|
|
328
|
+
|
|
111
329
|
if (stats && stats.lowBudget && !showBadge) {
|
|
112
|
-
children.push(React.createElement('div', {
|
|
330
|
+
children.push(React.createElement('div', {
|
|
331
|
+
key: 'warn',
|
|
332
|
+
className: 'cl-banner-warn',
|
|
333
|
+
style: { marginTop: 8 }
|
|
334
|
+
}, '⚠ ' + L.lowBudget));
|
|
113
335
|
}
|
|
336
|
+
|
|
114
337
|
if (stats && stats.budgetLimit) {
|
|
115
|
-
children.push(React.createElement('div', { key: 'budget', style: { marginTop:
|
|
338
|
+
children.push(React.createElement('div', { key: 'budget', className: 'cl-bar-box', style: { marginTop: 8 } },
|
|
116
339
|
React.createElement('div', {
|
|
117
|
-
style: {
|
|
118
|
-
display: compact ? 'flex' : 'block',
|
|
119
|
-
justifyContent: 'space-between',
|
|
120
|
-
fontSize: 11,
|
|
121
|
-
color: warn ? '#d73a4a' : 'var(--dsw-alias-label-secondary)',
|
|
122
|
-
marginBottom: 4
|
|
123
|
-
}
|
|
340
|
+
style: { display: 'flex', justifyContent: 'space-between', fontSize: 11, color: warn ? 'var(--dsw-alias-state-error-primary, #ef4444)' : 'var(--dsw-alias-label-secondary)' }
|
|
124
341
|
},
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
React.createElement('span', null, L.budget),
|
|
128
|
-
React.createElement('span', null, `${stats.budgetUsed || 0} / ${stats.budgetLimit} (${stats.budgetPercent || 0}%)`)
|
|
129
|
-
)
|
|
130
|
-
: `${L.budget} ${stats.budgetUsed}/${stats.budgetLimit} ${L.tokens} (${stats.budgetPercent}%)${warn ? ' ⚠' : ''}`
|
|
342
|
+
React.createElement('span', { style: { fontWeight: 500 } }, L.budget),
|
|
343
|
+
React.createElement('span', null, `${stats.budgetUsed || 0} / ${stats.budgetLimit} (${stats.budgetPercent || 0}%)`)
|
|
131
344
|
),
|
|
132
|
-
React.createElement('div', {
|
|
133
|
-
React.createElement('div', {
|
|
345
|
+
React.createElement('div', { className: 'cl-bar-track' },
|
|
346
|
+
React.createElement('div', {
|
|
347
|
+
className: 'cl-bar-fill',
|
|
348
|
+
style: { width: Math.min(100, stats.budgetPercent || 0) + '%', background: barColor }
|
|
349
|
+
})
|
|
134
350
|
)
|
|
135
351
|
));
|
|
136
352
|
}
|
|
353
|
+
|
|
137
354
|
if (history && history.length) {
|
|
138
355
|
children.push(React.createElement('div', {
|
|
139
356
|
key: 'hist',
|
|
140
|
-
style: { marginTop:
|
|
357
|
+
style: { marginTop: 10, paddingTop: 8, borderTop: '1px solid var(--dsw-alias-border-l2)' }
|
|
141
358
|
},
|
|
142
|
-
React.createElement('div', { style: { fontSize: 11, color: 'var(--dsw-alias-label-tertiary)', marginBottom: 4 } }, L.history),
|
|
359
|
+
React.createElement('div', { style: { fontSize: 11, fontWeight: 600, color: 'var(--dsw-alias-label-tertiary)', marginBottom: 4 } }, L.history),
|
|
143
360
|
...history.slice(0, 3).map((h) => React.createElement('div', {
|
|
144
361
|
key: h.id,
|
|
145
|
-
|
|
362
|
+
className: 'cl-hist-item',
|
|
363
|
+
style: { overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }
|
|
146
364
|
}, `−${h.savedTokens} tk (${h.savedPercent}%) · ${(h.preview || '').slice(0, previewLen)}`))
|
|
147
365
|
));
|
|
148
366
|
}
|
|
367
|
+
|
|
149
368
|
if (onRefresh) {
|
|
150
369
|
children.push(React.createElement('div', {
|
|
151
370
|
key: 'act',
|
|
152
|
-
style: { marginTop:
|
|
371
|
+
style: { marginTop: 10, display: 'flex', justifyContent: 'flex-end' }
|
|
153
372
|
},
|
|
154
373
|
React.createElement('button', {
|
|
155
374
|
type: 'button',
|
|
156
375
|
onClick: onRefresh,
|
|
157
|
-
|
|
158
|
-
appearance: 'none', cursor: 'pointer', fontSize: compact ? 11 : 12,
|
|
159
|
-
padding: compact ? '3px 8px' : '4px 10px', borderRadius: 6,
|
|
160
|
-
border: '1px solid var(--dsw-alias-border-l2)',
|
|
161
|
-
background: compact ? 'var(--dsw-alias-bg-layer-2)' : 'var(--dsw-alias-bg-layer-3)',
|
|
162
|
-
color: compact ? 'var(--dsw-alias-label-secondary)' : 'var(--dsw-alias-label-primary)'
|
|
163
|
-
}
|
|
376
|
+
className: 'cl-btn'
|
|
164
377
|
}, L.refresh)
|
|
165
378
|
));
|
|
166
379
|
}
|
|
380
|
+
|
|
167
381
|
return React.createElement(React.Fragment, null, ...children);
|
|
168
382
|
}
|
|
169
383
|
|
|
@@ -172,7 +386,8 @@ window.__ModuleLoader__.load({
|
|
|
172
386
|
.then((r) => r.ok ? r.json() : null);
|
|
173
387
|
}
|
|
174
388
|
|
|
175
|
-
function
|
|
389
|
+
function LensTabInner({ ctx: _ctx, scope }) {
|
|
390
|
+
ensureCss();
|
|
176
391
|
const [stats, setStats] = React.useState(null);
|
|
177
392
|
const [history, setHistory] = React.useState([]);
|
|
178
393
|
const L = React.useMemo(() => getActiveLocale(_ctx), [_ctx]);
|
|
@@ -189,7 +404,14 @@ window.__ModuleLoader__.load({
|
|
|
189
404
|
);
|
|
190
405
|
}
|
|
191
406
|
|
|
407
|
+
function LensTab(props) {
|
|
408
|
+
return React.createElement(ErrorBoundary, null,
|
|
409
|
+
React.createElement(LensTabInner, props)
|
|
410
|
+
);
|
|
411
|
+
}
|
|
412
|
+
|
|
192
413
|
function HeaderChip({ ctx: _ctx }) {
|
|
414
|
+
ensureCss();
|
|
193
415
|
const [stats, setStats] = React.useState(null);
|
|
194
416
|
const [history, setHistory] = React.useState([]);
|
|
195
417
|
const [open, setOpen] = React.useState(false);
|
|
@@ -204,7 +426,6 @@ window.__ModuleLoader__.load({
|
|
|
204
426
|
React.useEffect(() => {
|
|
205
427
|
let alive = true;
|
|
206
428
|
fetchStatus();
|
|
207
|
-
// Adaptive polling: poll every 4s when popover is open, 15s when closed, pause when tab hidden
|
|
208
429
|
const pollInterval = open ? 4000 : 15000;
|
|
209
430
|
const id = setInterval(() => {
|
|
210
431
|
if (!alive) return;
|
|
@@ -241,12 +462,12 @@ window.__ModuleLoader__.load({
|
|
|
241
462
|
const warn = !!(stats && stats.lowBudget);
|
|
242
463
|
const hasSavings = !!(stats && stats.savedTokens > 0);
|
|
243
464
|
const label = hasSavings ? `Lens ${stats.savedPercent}%` : 'Lens';
|
|
244
|
-
const color = warn ? '#
|
|
245
|
-
const borderColor = warn ? '#
|
|
465
|
+
const color = warn ? 'var(--dsw-alias-state-error-primary, #ef4444)' : 'var(--dsw-alias-label-secondary)';
|
|
466
|
+
const borderColor = warn ? 'var(--dsw-alias-state-error-primary, #ef4444)' : open ? 'var(--dsw-alias-border-l1)' : 'var(--dsw-alias-border-l2)';
|
|
246
467
|
|
|
247
468
|
const popover = open ? React.createElement('div', {
|
|
248
469
|
style: {
|
|
249
|
-
position: 'absolute', top: 'calc(100% + 6px)', right: 0, width:
|
|
470
|
+
position: 'absolute', top: 'calc(100% + 6px)', right: 0, width: 280,
|
|
250
471
|
background: 'var(--dsw-alias-bg-layer-3)', border: '1px solid var(--dsw-alias-border-l2)',
|
|
251
472
|
borderRadius: 10, boxShadow: '0 8px 24px rgba(0, 0, 0, 0.28)', padding: '12px 14px',
|
|
252
473
|
zIndex: 1000, fontSize: 12, color: 'var(--dsw-alias-label-primary)', cursor: 'default', textAlign: 'left'
|
|
@@ -272,16 +493,23 @@ window.__ModuleLoader__.load({
|
|
|
272
493
|
},
|
|
273
494
|
React.createElement('span', { style: { fontSize: 10, opacity: 0.8 } }, '◐'),
|
|
274
495
|
label,
|
|
275
|
-
warn ? React.createElement('span', { style: { color: '#
|
|
496
|
+
warn ? React.createElement('span', { style: { color: 'var(--dsw-alias-state-error-primary, #ef4444)' } }, ' ⚠') : null
|
|
276
497
|
),
|
|
277
498
|
popover
|
|
278
499
|
);
|
|
279
500
|
}
|
|
280
501
|
|
|
281
|
-
function
|
|
502
|
+
function PluginCardInner({ ctx: _ctx, t }) {
|
|
503
|
+
ensureCss();
|
|
282
504
|
const [expanded, setExpanded] = React.useState(false);
|
|
283
|
-
|
|
284
|
-
|
|
505
|
+
const [draft, setDraft] = React.useState({
|
|
506
|
+
compressionMode: 'balanced',
|
|
507
|
+
astSkeletonMaxDepth: 3,
|
|
508
|
+
tokenSavingsTracking: true,
|
|
509
|
+
budgetLimit: 100000,
|
|
510
|
+
autoCollapse: true,
|
|
511
|
+
autoCompressThreshold: 4000
|
|
512
|
+
});
|
|
285
513
|
const [status, setStatus] = React.useState('loading');
|
|
286
514
|
const [saving, setSaving] = React.useState(false);
|
|
287
515
|
const [saveErr, setSaveErr] = React.useState('');
|
|
@@ -293,8 +521,9 @@ window.__ModuleLoader__.load({
|
|
|
293
521
|
const scopeRef = React.useRef(null);
|
|
294
522
|
if (!scopeRef.current && _ctx) {
|
|
295
523
|
try {
|
|
296
|
-
|
|
297
|
-
|
|
524
|
+
const s = (_ctx.get && _ctx.get('lanSettings')) || _ctx.settingsScope;
|
|
525
|
+
if (s && typeof s.bind === 'function') {
|
|
526
|
+
scopeRef.current = s.bind({ namespace: NS });
|
|
298
527
|
}
|
|
299
528
|
} catch (e) { scopeRef.current = null; }
|
|
300
529
|
}
|
|
@@ -303,16 +532,17 @@ window.__ModuleLoader__.load({
|
|
|
303
532
|
React.useEffect(() => {
|
|
304
533
|
if (!scope) { setStatus('unavailable'); return; }
|
|
305
534
|
let cancelled = false;
|
|
306
|
-
|
|
535
|
+
|
|
536
|
+
const loadSnap = async () => {
|
|
307
537
|
try {
|
|
308
|
-
const snap =
|
|
538
|
+
const snap = (typeof scope.getSnapshot === 'function')
|
|
539
|
+
? scope.getSnapshot()
|
|
540
|
+
: (typeof scope.get === 'function' ? await scope.get() : null);
|
|
309
541
|
if (cancelled) return;
|
|
310
|
-
// check snapshot status if available
|
|
311
542
|
if (snap && typeof snap === 'object' && 'status' in snap) {
|
|
312
543
|
if (snap.status === 'loading') { setStatus('loading'); return; }
|
|
313
544
|
if (snap.status === 'unavailable') { setStatus('unavailable'); return; }
|
|
314
545
|
}
|
|
315
|
-
// snap is either values or wrapper with values
|
|
316
546
|
const vals = snap && snap.values ? snap.values : snap;
|
|
317
547
|
if (vals && typeof vals === 'object') {
|
|
318
548
|
setDraft((d) => ({ ...d, ...vals }));
|
|
@@ -321,8 +551,23 @@ window.__ModuleLoader__.load({
|
|
|
321
551
|
} catch (e) {
|
|
322
552
|
if (!cancelled) setStatus('unavailable');
|
|
323
553
|
}
|
|
324
|
-
}
|
|
325
|
-
|
|
554
|
+
};
|
|
555
|
+
|
|
556
|
+
loadSnap();
|
|
557
|
+
|
|
558
|
+
let unsub = null;
|
|
559
|
+
if (typeof scope.subscribe === 'function') {
|
|
560
|
+
try {
|
|
561
|
+
unsub = scope.subscribe(() => {
|
|
562
|
+
if (!cancelled) loadSnap();
|
|
563
|
+
});
|
|
564
|
+
} catch (_) {}
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
return () => {
|
|
568
|
+
cancelled = true;
|
|
569
|
+
if (typeof unsub === 'function') unsub();
|
|
570
|
+
};
|
|
326
571
|
}, [scope]);
|
|
327
572
|
|
|
328
573
|
React.useEffect(() => {
|
|
@@ -330,7 +575,6 @@ window.__ModuleLoader__.load({
|
|
|
330
575
|
fetch('/dsh-context-lens/status').then((r) => r.ok ? r.json() : null).then((j) => {
|
|
331
576
|
if (j && j.stats) {
|
|
332
577
|
setStats(j.stats);
|
|
333
|
-
// #41: warn instead of force-closing the card
|
|
334
578
|
setBudgetWarn(!!(j.stats.lowBudget && draft.autoCollapse !== false));
|
|
335
579
|
}
|
|
336
580
|
}).catch(() => {});
|
|
@@ -387,106 +631,103 @@ window.__ModuleLoader__.load({
|
|
|
387
631
|
}
|
|
388
632
|
}
|
|
389
633
|
|
|
390
|
-
|
|
391
|
-
return React.createElement('li', { className: 'cl-card', style: { border: '1px solid var(--dsw-alias-border-l2)', background: 'var(--dsw-alias-bg-layer-3)', borderRadius: 12, listStyle: 'none' } },
|
|
634
|
+
return React.createElement('li', { className: 'cl-section-card' },
|
|
392
635
|
React.createElement('button', {
|
|
393
|
-
|
|
636
|
+
type: 'button',
|
|
637
|
+
className: 'cl-section-header',
|
|
394
638
|
onClick: () => setExpanded(!expanded),
|
|
395
|
-
'aria-expanded': expanded
|
|
396
|
-
style: { appearance: 'none', width: '100%', font: 'inherit', color: 'inherit', textAlign: 'left', cursor: 'pointer', background: '0 0', border: 0, borderRadius: 12, display: 'flex', alignItems: 'center', gap: 12, padding: '14px 16px' }
|
|
639
|
+
'aria-expanded': expanded
|
|
397
640
|
},
|
|
398
|
-
React.createElement('span', { style: { display: 'flex', flexDirection: 'column' } },
|
|
641
|
+
React.createElement('span', { style: { display: 'flex', flexDirection: 'column', gap: 2 } },
|
|
399
642
|
React.createElement('span', { className: 'cl-title', style: { color: 'var(--dsw-alias-label-primary)', fontSize: 15, fontWeight: 600, lineHeight: 1.4 } }, tt('title')),
|
|
400
643
|
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) + '%)' : ''))
|
|
401
644
|
),
|
|
402
645
|
React.createElement(Chevron, { open: expanded })
|
|
403
646
|
),
|
|
404
|
-
expanded ? React.createElement('div', { className: 'cl-body', style: { borderTop: '1px solid var(--dsw-alias-border-l2)',
|
|
405
|
-
budgetWarn ? React.createElement('div', {
|
|
647
|
+
expanded ? React.createElement('div', { className: 'cl-body', style: { borderTop: '1px solid var(--dsw-alias-border-l2)', paddingTop: 12 } },
|
|
648
|
+
budgetWarn ? React.createElement('div', { className: 'cl-banner-warn', style: { marginBottom: 12 } }, '⚠ ' + tt('lowBudget')) : null,
|
|
406
649
|
status === 'loading' ? React.createElement('div', { style: { padding: 12, color: 'var(--dsw-alias-label-secondary)' } }, 'Loading…') :
|
|
407
650
|
status === 'unavailable' ? React.createElement('div', { style: { padding: 12, color: 'var(--dsw-alias-label-secondary)' } }, 'Settings unavailable — plugin not registered on host yet') :
|
|
408
651
|
React.createElement(React.Fragment, null,
|
|
409
|
-
React.createElement('div', { className: 'cl-field', style: { display: 'flex', flexDirection: 'column', gap: 6, padding: '
|
|
652
|
+
React.createElement('div', { className: 'cl-field', style: { display: 'flex', flexDirection: 'column', gap: 6, padding: '8px 0' } },
|
|
410
653
|
React.createElement('label', { style: { fontSize: 13, color: 'var(--dsw-alias-label-secondary)' } }, tt('mode')),
|
|
411
654
|
React.createElement('select', {
|
|
412
655
|
className: 'cl-input',
|
|
413
656
|
value: draft.compressionMode,
|
|
414
|
-
onChange: (e) => setDraft((d) => ({ ...d, compressionMode: e.target.value }))
|
|
415
|
-
style: { height: 34, border: '1px solid var(--dsw-alias-border-l2)', background: 'var(--dsw-alias-bg-layer-3)', color: 'var(--dsw-alias-label-primary)', borderRadius: 8, padding: '0 12px', fontSize: 13 }
|
|
657
|
+
onChange: (e) => setDraft((d) => ({ ...d, compressionMode: e.target.value }))
|
|
416
658
|
},
|
|
417
659
|
React.createElement('option', { value: 'raw' }, 'raw'),
|
|
418
660
|
React.createElement('option', { value: 'balanced' }, 'balanced'),
|
|
419
661
|
React.createElement('option', { value: 'aggressive' }, 'aggressive')
|
|
420
662
|
)
|
|
421
663
|
),
|
|
422
|
-
React.createElement('div', { className: 'cl-field', style: { display: 'flex', flexDirection: 'column', gap: 6, padding: '
|
|
664
|
+
React.createElement('div', { className: 'cl-field', style: { display: 'flex', flexDirection: 'column', gap: 6, padding: '8px 0' } },
|
|
423
665
|
React.createElement('label', { style: { fontSize: 13, color: 'var(--dsw-alias-label-secondary)' } }, tt('depth')),
|
|
424
666
|
React.createElement('input', {
|
|
425
667
|
className: 'cl-input',
|
|
426
668
|
type: 'number', min: 1, max: 10,
|
|
427
669
|
value: draft.astSkeletonMaxDepth,
|
|
428
|
-
onChange: (e) => setDraft((d) => ({ ...d, astSkeletonMaxDepth: parseInt(e.target.value, 10) || 3 }))
|
|
429
|
-
style: { height: 34, border: '1px solid var(--dsw-alias-border-l2)', background: 'var(--dsw-alias-bg-layer-3)', color: 'var(--dsw-alias-label-primary)', borderRadius: 8, padding: '0 12px', fontSize: 13 }
|
|
670
|
+
onChange: (e) => setDraft((d) => ({ ...d, astSkeletonMaxDepth: parseInt(e.target.value, 10) || 3 }))
|
|
430
671
|
})
|
|
431
672
|
),
|
|
432
|
-
React.createElement('div', { className: 'cl-field', style: { display: 'flex', flexDirection: 'row', alignItems: 'center', gap: 8, padding: '
|
|
673
|
+
React.createElement('div', { className: 'cl-field', style: { display: 'flex', flexDirection: 'row', alignItems: 'center', gap: 8, padding: '8px 0' } },
|
|
433
674
|
React.createElement('input', {
|
|
434
675
|
type: 'checkbox',
|
|
435
676
|
checked: !!draft.tokenSavingsTracking,
|
|
436
677
|
onChange: (e) => setDraft((d) => ({ ...d, tokenSavingsTracking: e.target.checked })),
|
|
437
678
|
id: 'cl-tracking'
|
|
438
679
|
}),
|
|
439
|
-
React.createElement('label', { htmlFor: 'cl-tracking', style: { fontSize: 13 } }, tt('tracking'))
|
|
680
|
+
React.createElement('label', { htmlFor: 'cl-tracking', style: { fontSize: 13, color: 'var(--dsw-alias-label-primary)' } }, tt('tracking'))
|
|
440
681
|
),
|
|
441
|
-
React.createElement('div', { className: 'cl-field', style: { display: 'flex', flexDirection: 'row', alignItems: 'center', gap: 8, padding: '
|
|
682
|
+
React.createElement('div', { className: 'cl-field', style: { display: 'flex', flexDirection: 'row', alignItems: 'center', gap: 8, padding: '8px 0' } },
|
|
442
683
|
React.createElement('input', {
|
|
443
684
|
type: 'checkbox',
|
|
444
685
|
checked: !!draft.autoCollapse,
|
|
445
686
|
onChange: (e) => setDraft((d) => ({ ...d, autoCollapse: e.target.checked })),
|
|
446
687
|
id: 'cl-autocollapse'
|
|
447
688
|
}),
|
|
448
|
-
React.createElement('label', { htmlFor: 'cl-autocollapse', style: { fontSize: 13 } }, tt('autoCollapse'))
|
|
689
|
+
React.createElement('label', { htmlFor: 'cl-autocollapse', style: { fontSize: 13, color: 'var(--dsw-alias-label-primary)' } }, tt('autoCollapse'))
|
|
449
690
|
),
|
|
450
|
-
React.createElement('div', { className: 'cl-field', style: { display: 'flex', flexDirection: 'column', gap: 6, padding: '
|
|
691
|
+
React.createElement('div', { className: 'cl-field', style: { display: 'flex', flexDirection: 'column', gap: 6, padding: '8px 0' } },
|
|
451
692
|
React.createElement('label', { style: { fontSize: 13, color: 'var(--dsw-alias-label-secondary)' } }, tt('budget') + ' (tokens)'),
|
|
452
693
|
React.createElement('input', {
|
|
453
694
|
className: 'cl-input', type: 'number', min: 1000, step: 1000,
|
|
454
695
|
value: draft.budgetLimit,
|
|
455
|
-
onChange: (e) => setDraft((d) => ({ ...d, budgetLimit: parseInt(e.target.value, 10) || 100000 }))
|
|
456
|
-
style: { height: 34, border: '1px solid var(--dsw-alias-border-l2)', background: 'var(--dsw-alias-bg-layer-3)', color: 'var(--dsw-alias-label-primary)', borderRadius: 8, padding: '0 12px', fontSize: 13 }
|
|
696
|
+
onChange: (e) => setDraft((d) => ({ ...d, budgetLimit: parseInt(e.target.value, 10) || 100000 }))
|
|
457
697
|
})
|
|
458
698
|
),
|
|
459
|
-
React.createElement('div', { className: 'cl-field', style: { display: 'flex', flexDirection: 'column', gap: 6, padding: '
|
|
699
|
+
React.createElement('div', { className: 'cl-field', style: { display: 'flex', flexDirection: 'column', gap: 6, padding: '8px 0' } },
|
|
460
700
|
React.createElement('label', { style: { fontSize: 13, color: 'var(--dsw-alias-label-secondary)' } }, tt('threshold')),
|
|
461
701
|
React.createElement('input', {
|
|
462
702
|
className: 'cl-input', type: 'number', min: 0, step: 500,
|
|
463
703
|
value: draft.autoCompressThreshold,
|
|
464
|
-
onChange: (e) => setDraft((d) => ({ ...d, autoCompressThreshold: parseInt(e.target.value, 10) || 0 }))
|
|
465
|
-
style: { height: 34, border: '1px solid var(--dsw-alias-border-l2)', background: 'var(--dsw-alias-bg-layer-3)', color: 'var(--dsw-alias-label-primary)', borderRadius: 8, padding: '0 12px', fontSize: 13 }
|
|
704
|
+
onChange: (e) => setDraft((d) => ({ ...d, autoCompressThreshold: parseInt(e.target.value, 10) || 0 }))
|
|
466
705
|
})
|
|
467
706
|
),
|
|
468
|
-
React.createElement('div', { className: 'cl-field', style: { display: 'flex', flexDirection: 'column', gap: 6, padding: '
|
|
707
|
+
React.createElement('div', { className: 'cl-field', style: { display: 'flex', flexDirection: 'column', gap: 6, padding: '8px 0' } },
|
|
469
708
|
React.createElement('label', { style: { fontSize: 13, color: 'var(--dsw-alias-label-secondary)' } }, tt('preview') + ' (' + tt('previewApprox') + ')'),
|
|
470
709
|
React.createElement('textarea', {
|
|
471
710
|
value: previewIn,
|
|
472
711
|
onChange: (e) => setPreviewIn(e.target.value),
|
|
473
712
|
placeholder: tt('placeholder'),
|
|
474
713
|
rows: 5,
|
|
475
|
-
style: { border: '1px solid var(--dsw-alias-border-l2)', background: 'var(--dsw-alias-bg-layer-
|
|
714
|
+
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' }
|
|
476
715
|
}),
|
|
477
716
|
React.createElement('button', {
|
|
717
|
+
type: 'button',
|
|
718
|
+
className: 'cl-btn cl-btn-primary',
|
|
478
719
|
onClick: onPreview,
|
|
479
|
-
style: {
|
|
720
|
+
style: { alignSelf: 'flex-start', marginTop: 4 }
|
|
480
721
|
}, tt('compress')),
|
|
481
|
-
previewOut ? React.createElement('pre', { style: { whiteSpace: 'pre-wrap', fontSize: 12, background: 'var(--dsw-alias-bg-layer-2
|
|
722
|
+
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
|
|
482
723
|
),
|
|
483
|
-
saveErr ? React.createElement('div', { style: { color: '#
|
|
484
|
-
React.createElement('div', { className: 'cl-foot', style: { borderTop: '1px solid var(--dsw-alias-border-l2)', display: 'flex', justifyContent: 'flex-end', alignItems: 'center', gap: 8,
|
|
724
|
+
saveErr ? React.createElement('div', { style: { color: 'var(--dsw-alias-state-error-primary, #ef4444)', fontSize: 12, padding: '4px 0' } }, saveErr) : null,
|
|
725
|
+
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 } },
|
|
485
726
|
React.createElement('button', {
|
|
486
|
-
|
|
727
|
+
type: 'button',
|
|
728
|
+
className: 'cl-btn cl-btn-primary' + (saving ? ' cl-btn-disabled' : ''),
|
|
487
729
|
onClick: onSave,
|
|
488
|
-
disabled: saving
|
|
489
|
-
style: { appearance: 'none', font: 'inherit', cursor: 'pointer', border: '1px solid transparent', borderRadius: 8, padding: '5px 14px', fontSize: 13, background: 'var(--dsw-alias-label-primary)', color: 'var(--dsw-alias-bg-layer-3)', opacity: saving ? 0.6 : 1 }
|
|
730
|
+
disabled: saving
|
|
490
731
|
}, saving ? tt('saving') : tt('ready'))
|
|
491
732
|
)
|
|
492
733
|
)
|
|
@@ -494,6 +735,12 @@ window.__ModuleLoader__.load({
|
|
|
494
735
|
);
|
|
495
736
|
}
|
|
496
737
|
|
|
738
|
+
function PluginCard(props) {
|
|
739
|
+
return React.createElement(ErrorBoundary, null,
|
|
740
|
+
React.createElement(PluginCardInner, props)
|
|
741
|
+
);
|
|
742
|
+
}
|
|
743
|
+
|
|
497
744
|
module.exports.inject = ['slots', 'locale'];
|
|
498
745
|
module.exports.apply = function apply(ctx) {
|
|
499
746
|
try { ctx.locale.register(NS, { en, ru }); } catch (e) { console.warn('[dsh-context-lens] locale register failed', e && e.message || e); }
|
|
@@ -511,26 +758,77 @@ window.__ModuleLoader__.load({
|
|
|
511
758
|
registered = true;
|
|
512
759
|
return dispose;
|
|
513
760
|
} catch (e) {
|
|
514
|
-
// Do not swallow real registration errors — surface diagnostics
|
|
515
761
|
console.error('[dsh-context-lens] settings.plugin.item register failed', e && e.stack || e);
|
|
516
762
|
throw e;
|
|
517
763
|
}
|
|
518
764
|
};
|
|
519
|
-
// Declaration-aware: wait for host to declare the slot (alpha2 SlotCore throws if not declared)
|
|
520
765
|
if (typeof ctx.slots.inject === 'function') {
|
|
521
766
|
try {
|
|
522
767
|
const ok = ctx.slots.inject('settings.plugin.item', doRegister);
|
|
523
768
|
if (!ok) console.warn('[dsh-context-lens] settings.plugin.item inject returned false — slot not declared yet');
|
|
524
769
|
} catch (e) {
|
|
525
770
|
console.error('[dsh-context-lens] settings.plugin.item inject failed', e && e.stack || e);
|
|
526
|
-
// Fallback for older DSH without inject or if inject itself fails — surface error
|
|
527
771
|
try { doRegister(); } catch (e2) { console.error('[dsh-context-lens] fallback register failed', e2 && e2.stack || e2); throw e2; }
|
|
528
772
|
throw e;
|
|
529
773
|
}
|
|
530
774
|
} else {
|
|
531
775
|
try { doRegister(); } catch (e) { console.error('[dsh-context-lens] direct register failed (no inject)', e && e.stack || e); throw e; }
|
|
532
776
|
}
|
|
533
|
-
|
|
777
|
+
if (typeof ctx.inject === "function") {
|
|
778
|
+
try {
|
|
779
|
+
ctx.inject(["sidebarRightTabs"], (sctx) => {
|
|
780
|
+
const tabs = sctx && sctx.sidebarRightTabs;
|
|
781
|
+
if (!tabs || typeof tabs.register !== "function") return;
|
|
782
|
+
try {
|
|
783
|
+
const def = {
|
|
784
|
+
id: "@goodandready/dsh-context-lens",
|
|
785
|
+
kind: "context-lens",
|
|
786
|
+
priority: "extension",
|
|
787
|
+
title: () => "Lens",
|
|
788
|
+
guide: [{
|
|
789
|
+
order: 50,
|
|
790
|
+
title: () => "Context Lens",
|
|
791
|
+
description: () => "AST compression, log filtering, token budget",
|
|
792
|
+
icon: () => React.createElement("span", null, "◐")
|
|
793
|
+
}]
|
|
794
|
+
};
|
|
795
|
+
if (typeof sctx.effect === "function") {
|
|
796
|
+
sctx.effect(() => tabs.register(def));
|
|
797
|
+
} else {
|
|
798
|
+
tabs.register(def);
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
if (ctx.slots) {
|
|
802
|
+
const registerPaneTab = () => {
|
|
803
|
+
try {
|
|
804
|
+
return ctx.slots.register({
|
|
805
|
+
name: "sidebar.right.pane.tab",
|
|
806
|
+
key: "@goodandready/dsh-context-lens",
|
|
807
|
+
locale: NS,
|
|
808
|
+
inject: () => ({ ctx })
|
|
809
|
+
}, LensTab);
|
|
810
|
+
} catch (e) {
|
|
811
|
+
console.warn("[dsh-context-lens] native sidebar pane tab register failed", e && e.message || e);
|
|
812
|
+
}
|
|
813
|
+
};
|
|
814
|
+
if (typeof ctx.slots.inject === "function") {
|
|
815
|
+
try {
|
|
816
|
+
const ok = ctx.slots.inject("sidebar.right.pane.tab", registerPaneTab);
|
|
817
|
+
if (!ok) console.warn("[dsh-context-lens] sidebar.right.pane.tab inject returned false");
|
|
818
|
+
} catch (e) {
|
|
819
|
+
try { registerPaneTab(); } catch (e2) {}
|
|
820
|
+
}
|
|
821
|
+
} else {
|
|
822
|
+
try { registerPaneTab(); } catch (e) {}
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
} catch (e) {
|
|
826
|
+
console.warn("[dsh-context-lens] native sidebar registration failed", e && e.message || e);
|
|
827
|
+
}
|
|
828
|
+
});
|
|
829
|
+
} catch (e) {}
|
|
830
|
+
}
|
|
831
|
+
|
|
534
832
|
if (typeof ctx.inject === 'function') {
|
|
535
833
|
try {
|
|
536
834
|
ctx.inject(['betterSidebar'], (sctx) => {
|
|
@@ -553,11 +851,9 @@ window.__ModuleLoader__.load({
|
|
|
553
851
|
console.warn('[dsh-context-lens] betterSidebar registerTab failed', e && e.message || e);
|
|
554
852
|
}
|
|
555
853
|
});
|
|
556
|
-
} catch (e) {
|
|
557
|
-
// host without betterSidebar declared or inject failure
|
|
558
|
-
}
|
|
854
|
+
} catch (e) {}
|
|
559
855
|
}
|
|
560
|
-
|
|
856
|
+
|
|
561
857
|
if (ctx.slots) {
|
|
562
858
|
const headerChipRegister = () => {
|
|
563
859
|
try {
|
package/package.json
CHANGED