@goodandready/dsh-clinebot 0.3.3 → 0.3.5
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 +12 -0
- package/lib/client.js +105 -12
- package/lib/index.js +3 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,18 @@ 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.5] - 2026-09-09
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
- **Settings Card React Crash & ErrorBoundary Isolation**: Wrapped `PluginCard` and `SettingsPage` with a defensive `ErrorBoundary` preventing unhandled render exceptions from unmounting the card.
|
|
12
|
+
- **useSyncExternalStore Stability**: Eliminated object allocations inside `getSnapshot` callback when `settingsScope` is detached or resolving, resolving infinite render loops (`Maximum update depth exceeded`).
|
|
13
|
+
- **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.
|
|
14
|
+
|
|
15
|
+
## [0.3.4] - 2026-09-08
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
- **Cache Path Resolution**: Corrected POSIX home directory expansion (`~/`) in `resolvePathWithHome` so `~/.dsh/clinebot-models-cache.json` resolves cleanly to `/home/vadim/.dsh/...` instead of root-level paths.
|
|
19
|
+
|
|
8
20
|
## [0.3.3] - 2026-09-08
|
|
9
21
|
|
|
10
22
|
### Added
|
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
|
|
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
|
|
|
@@ -1094,7 +1175,17 @@ window.__ModuleLoader__.load({
|
|
|
1094
1175
|
React.createElement(Chevron)
|
|
1095
1176
|
)
|
|
1096
1177
|
),
|
|
1097
|
-
open
|
|
1178
|
+
open
|
|
1179
|
+
? React.createElement(
|
|
1180
|
+
'div',
|
|
1181
|
+
{ style: { marginTop: '16px' } },
|
|
1182
|
+
React.createElement(
|
|
1183
|
+
ErrorBoundary,
|
|
1184
|
+
null,
|
|
1185
|
+
React.createElement(SettingsPage, { ...props, ctx: (props && props.ctx) || ctx })
|
|
1186
|
+
)
|
|
1187
|
+
)
|
|
1188
|
+
: null
|
|
1098
1189
|
)
|
|
1099
1190
|
}
|
|
1100
1191
|
|
|
@@ -1106,7 +1197,8 @@ window.__ModuleLoader__.load({
|
|
|
1106
1197
|
|
|
1107
1198
|
let placed = false
|
|
1108
1199
|
try {
|
|
1109
|
-
|
|
1200
|
+
const res = ctx.slots.inject('settings.plugin.item', () => {
|
|
1201
|
+
placed = true
|
|
1110
1202
|
return ctx.slots.register(
|
|
1111
1203
|
{
|
|
1112
1204
|
name: 'settings.plugin.item',
|
|
@@ -1114,9 +1206,10 @@ window.__ModuleLoader__.load({
|
|
|
1114
1206
|
locale: NS,
|
|
1115
1207
|
inject: () => ({ ctx }),
|
|
1116
1208
|
},
|
|
1117
|
-
PluginCard
|
|
1209
|
+
(props) => React.createElement(ErrorBoundary, null, React.createElement(PluginCard, { ...props, ctx: (props && props.ctx) || ctx }))
|
|
1118
1210
|
)
|
|
1119
1211
|
})
|
|
1212
|
+
if (res === null) placed = false
|
|
1120
1213
|
} catch (_) {
|
|
1121
1214
|
placed = false
|
|
1122
1215
|
}
|
|
@@ -1134,7 +1227,7 @@ window.__ModuleLoader__.load({
|
|
|
1134
1227
|
label: () => t('title'),
|
|
1135
1228
|
inject: () => ({ ctx }),
|
|
1136
1229
|
},
|
|
1137
|
-
SettingsPage
|
|
1230
|
+
(props) => React.createElement(ErrorBoundary, null, React.createElement(SettingsPage, { ...props, ctx: (props && props.ctx) || ctx }))
|
|
1138
1231
|
)
|
|
1139
1232
|
})
|
|
1140
1233
|
} catch (_) {}
|
package/lib/index.js
CHANGED
|
@@ -113,8 +113,9 @@ import path from 'node:path'
|
|
|
113
113
|
|
|
114
114
|
function resolvePathWithHome(p) {
|
|
115
115
|
if (!p || typeof p !== 'string') return ''
|
|
116
|
-
if (p
|
|
117
|
-
|
|
116
|
+
if (p === '~') return os.homedir()
|
|
117
|
+
if (p.startsWith('~/') || p.startsWith('~\\')) {
|
|
118
|
+
return path.join(os.homedir(), p.slice(2))
|
|
118
119
|
}
|
|
119
120
|
return p
|
|
120
121
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goodandready/dsh-clinebot",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.5",
|
|
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",
|