@goodandready/dsh-clinebot 0.3.4 → 0.3.6
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/CHANGELOG.md +14 -0
- package/lib/client.js +128 -12
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,20 @@ All notable changes to `@goodandready/dsh-clinebot` will be documented in this f
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.3.6] - 2026-09-09
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
- **Restored `handleSmoke` diagnostics handler**: Re-introduced missing `handleSmoke` click callback in `SettingsPage`, fixing `handleSmoke is not defined` runtime error during Diagnostics card interaction.
|
|
12
|
+
- **Defensive ErrorBoundary child rendering**: Hardened `ErrorBoundary.render()` with `this.props?.children || null` to prevent unhandled exceptions if props are omitted.
|
|
13
|
+
- **Comprehensive UI render integrity test**: Added recursive VDOM component tree evaluation test in `test/client-modules.test.js` covering all 190+ elements and verifying definition of all 18 event handlers.
|
|
14
|
+
|
|
15
|
+
## [0.3.5] - 2026-09-09
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
- **Settings Card React Crash & ErrorBoundary Isolation**: Wrapped `PluginCard` and `SettingsPage` with a defensive `ErrorBoundary` preventing unhandled render exceptions from unmounting the card.
|
|
19
|
+
- **useSyncExternalStore Stability**: Eliminated object allocations inside `getSnapshot` callback when `settingsScope` is detached or resolving, resolving infinite render loops (`Maximum update depth exceeded`).
|
|
20
|
+
- **Resilient Context & Reset Time Formatting**: Guaranteed fallback to module `ctx` inside `PluginCard` and added safe parsing for high-precision ISO timestamps in rolling quota progress bars.
|
|
21
|
+
|
|
8
22
|
## [0.3.4] - 2026-09-08
|
|
9
23
|
|
|
10
24
|
### Fixed
|
package/lib/client.js
CHANGED
|
@@ -261,13 +261,70 @@ window.__ModuleLoader__.load({
|
|
|
261
261
|
document.head.appendChild(style)
|
|
262
262
|
}
|
|
263
263
|
|
|
264
|
+
function createErrorBoundary() {
|
|
265
|
+
if (!React || typeof React.Component !== 'function') {
|
|
266
|
+
return function NoopBoundary(props) { return props?.children || null }
|
|
267
|
+
}
|
|
268
|
+
return class ErrorBoundary extends React.Component {
|
|
269
|
+
constructor(props) {
|
|
270
|
+
super(props)
|
|
271
|
+
this.state = { hasError: false, error: null }
|
|
272
|
+
}
|
|
273
|
+
static getDerivedStateFromError(error) {
|
|
274
|
+
return { hasError: true, error }
|
|
275
|
+
}
|
|
276
|
+
componentDidCatch(error, errorInfo) {
|
|
277
|
+
console.error('[dsh-clinebot] React Error:', error, errorInfo)
|
|
278
|
+
}
|
|
279
|
+
render() {
|
|
280
|
+
if (this.state.hasError) {
|
|
281
|
+
return React.createElement(
|
|
282
|
+
'div',
|
|
283
|
+
{
|
|
284
|
+
className: 'cb-alert cb-alert-err',
|
|
285
|
+
style: { margin: '12px 0', padding: '14px', borderRadius: '8px' },
|
|
286
|
+
},
|
|
287
|
+
React.createElement('div', { style: { fontWeight: 600, marginBottom: '6px' } }, '⚠️ ClineBot UI Error:'),
|
|
288
|
+
React.createElement('div', { style: { fontSize: '12px', wordBreak: 'break-all' } }, String(this.state.error?.message || this.state.error)),
|
|
289
|
+
React.createElement(
|
|
290
|
+
'button',
|
|
291
|
+
{
|
|
292
|
+
type: 'button',
|
|
293
|
+
className: 'cb-btn',
|
|
294
|
+
style: { marginTop: '10px', fontSize: '12px', padding: '4px 10px' },
|
|
295
|
+
onClick: () => this.setState({ hasError: false, error: null }),
|
|
296
|
+
},
|
|
297
|
+
'Retry'
|
|
298
|
+
)
|
|
299
|
+
)
|
|
300
|
+
}
|
|
301
|
+
return this.props?.children || null
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
const ErrorBoundary = createErrorBoundary()
|
|
306
|
+
|
|
307
|
+
const SNAPSHOT_READY = Object.freeze({ status: 'ready', value: {} })
|
|
308
|
+
const SNAPSHOT_LOADING = Object.freeze({ status: 'loading', value: {} })
|
|
309
|
+
|
|
310
|
+
function formatResetTime(isoString) {
|
|
311
|
+
if (!isoString) return '—'
|
|
312
|
+
try {
|
|
313
|
+
const d = new Date(isoString)
|
|
314
|
+
if (isNaN(d.getTime())) return '—'
|
|
315
|
+
return d.toLocaleTimeString()
|
|
316
|
+
} catch {
|
|
317
|
+
return '—'
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
264
321
|
function ProgressBar({ label, percentUsed, remainingPercent, resetsAt, t }) {
|
|
265
322
|
const used = Math.max(0, Math.min(100, percentUsed || 0))
|
|
266
323
|
let fillColor = 'var(--dsw-alias-state-success-primary)'
|
|
267
324
|
if (used > 75) fillColor = 'var(--dsw-alias-state-warning-primary)'
|
|
268
325
|
if (used > 90) fillColor = 'var(--dsw-alias-state-error-primary)'
|
|
269
326
|
|
|
270
|
-
const resetStr =
|
|
327
|
+
const resetStr = formatResetTime(resetsAt)
|
|
271
328
|
|
|
272
329
|
return React.createElement(
|
|
273
330
|
'div',
|
|
@@ -299,15 +356,39 @@ window.__ModuleLoader__.load({
|
|
|
299
356
|
const ctx = props?.ctx
|
|
300
357
|
const t = props?.t || makeT(ru, en)
|
|
301
358
|
|
|
302
|
-
const scope = React.useMemo(
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
359
|
+
const scope = React.useMemo(() => {
|
|
360
|
+
if (!ctx?.settingsScope?.bind) return undefined
|
|
361
|
+
try {
|
|
362
|
+
return ctx.settingsScope.bind({ namespace: NS })
|
|
363
|
+
} catch (_) {
|
|
364
|
+
return undefined
|
|
365
|
+
}
|
|
366
|
+
}, [ctx])
|
|
367
|
+
|
|
368
|
+
const subscribe = React.useMemo(() => {
|
|
369
|
+
return (cb) => {
|
|
370
|
+
if (!scope?.subscribe) return () => {}
|
|
371
|
+
try {
|
|
372
|
+
return scope.subscribe(cb) || (() => {})
|
|
373
|
+
} catch (_) {
|
|
374
|
+
return () => {}
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
}, [scope])
|
|
378
|
+
|
|
379
|
+
const getSnapshot = React.useCallback(() => {
|
|
380
|
+
if (!scope?.getSnapshot) return SNAPSHOT_READY
|
|
381
|
+
try {
|
|
382
|
+
return scope.getSnapshot() || SNAPSHOT_READY
|
|
383
|
+
} catch (_) {
|
|
384
|
+
return SNAPSHOT_READY
|
|
385
|
+
}
|
|
386
|
+
}, [scope])
|
|
306
387
|
|
|
307
388
|
const snapshot = React.useSyncExternalStore(
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
React.useCallback(() =>
|
|
389
|
+
subscribe,
|
|
390
|
+
getSnapshot,
|
|
391
|
+
React.useCallback(() => SNAPSHOT_LOADING, [])
|
|
311
392
|
)
|
|
312
393
|
const snapshotStatus = snapshot?.status || 'ready'
|
|
313
394
|
|
|
@@ -445,6 +526,29 @@ window.__ModuleLoader__.load({
|
|
|
445
526
|
}
|
|
446
527
|
}
|
|
447
528
|
|
|
529
|
+
// Smoke chat test
|
|
530
|
+
async function handleSmoke() {
|
|
531
|
+
setBusy('smoke')
|
|
532
|
+
setErr('')
|
|
533
|
+
setMsg('')
|
|
534
|
+
setSmokeResult(null)
|
|
535
|
+
try {
|
|
536
|
+
const res = await fetch(`${ROUTE_PREFIX}/smoke`, {
|
|
537
|
+
method: 'POST',
|
|
538
|
+
headers: { 'Content-Type': 'application/json' },
|
|
539
|
+
body: JSON.stringify({ model: draft?.defaultModel }),
|
|
540
|
+
})
|
|
541
|
+
const data = await res.json().catch(() => ({}))
|
|
542
|
+
if (!data.ok) throw new Error(data.error || `HTTP ${res.status}`)
|
|
543
|
+
setSmokeResult(data)
|
|
544
|
+
setMsg(t('diag.smoke_ok', { latency: data.latencyMs }))
|
|
545
|
+
} catch (e) {
|
|
546
|
+
setErr(String(e.message || e))
|
|
547
|
+
} finally {
|
|
548
|
+
setBusy('')
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
|
|
448
552
|
// Fast Browser Login
|
|
449
553
|
async function handleFastLogin() {
|
|
450
554
|
setBusy('fast-login')
|
|
@@ -1094,7 +1198,17 @@ window.__ModuleLoader__.load({
|
|
|
1094
1198
|
React.createElement(Chevron)
|
|
1095
1199
|
)
|
|
1096
1200
|
),
|
|
1097
|
-
open
|
|
1201
|
+
open
|
|
1202
|
+
? React.createElement(
|
|
1203
|
+
'div',
|
|
1204
|
+
{ style: { marginTop: '16px' } },
|
|
1205
|
+
React.createElement(
|
|
1206
|
+
ErrorBoundary,
|
|
1207
|
+
null,
|
|
1208
|
+
React.createElement(SettingsPage, { ...props, ctx: (props && props.ctx) || ctx })
|
|
1209
|
+
)
|
|
1210
|
+
)
|
|
1211
|
+
: null
|
|
1098
1212
|
)
|
|
1099
1213
|
}
|
|
1100
1214
|
|
|
@@ -1106,7 +1220,8 @@ window.__ModuleLoader__.load({
|
|
|
1106
1220
|
|
|
1107
1221
|
let placed = false
|
|
1108
1222
|
try {
|
|
1109
|
-
|
|
1223
|
+
const res = ctx.slots.inject('settings.plugin.item', () => {
|
|
1224
|
+
placed = true
|
|
1110
1225
|
return ctx.slots.register(
|
|
1111
1226
|
{
|
|
1112
1227
|
name: 'settings.plugin.item',
|
|
@@ -1114,9 +1229,10 @@ window.__ModuleLoader__.load({
|
|
|
1114
1229
|
locale: NS,
|
|
1115
1230
|
inject: () => ({ ctx }),
|
|
1116
1231
|
},
|
|
1117
|
-
PluginCard
|
|
1232
|
+
(props) => React.createElement(ErrorBoundary, null, React.createElement(PluginCard, { ...props, ctx: (props && props.ctx) || ctx }))
|
|
1118
1233
|
)
|
|
1119
1234
|
})
|
|
1235
|
+
if (res === null) placed = false
|
|
1120
1236
|
} catch (_) {
|
|
1121
1237
|
placed = false
|
|
1122
1238
|
}
|
|
@@ -1134,7 +1250,7 @@ window.__ModuleLoader__.load({
|
|
|
1134
1250
|
label: () => t('title'),
|
|
1135
1251
|
inject: () => ({ ctx }),
|
|
1136
1252
|
},
|
|
1137
|
-
SettingsPage
|
|
1253
|
+
(props) => React.createElement(ErrorBoundary, null, React.createElement(SettingsPage, { ...props, ctx: (props && props.ctx) || ctx }))
|
|
1138
1254
|
)
|
|
1139
1255
|
})
|
|
1140
1256
|
} catch (_) {}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goodandready/dsh-clinebot",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.6",
|
|
4
4
|
"description": "DeepSeek Harness companion for ClineBot / ClinePass: dynamic subscription models sync, quota exhaustion warnings, session metrics, dedicated settings page, live usage limits, and /cline slash-command.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|