@goodandready/dsh-goal 0.2.5 → 0.2.7

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
@@ -282,6 +282,46 @@ window.__ModuleLoader__.load({
282
282
 
283
283
  }
284
284
 
285
+ function IconMinimize({ size = 14, className = '' }) {
286
+
287
+ return React.createElement(
288
+
289
+ 'svg',
290
+
291
+ {
292
+
293
+ width: size,
294
+
295
+ height: size,
296
+
297
+ viewBox: '0 0 24 24',
298
+
299
+ fill: 'none',
300
+
301
+ stroke: 'currentColor',
302
+
303
+ strokeWidth: 1.8,
304
+
305
+ strokeLinecap: 'round',
306
+
307
+ strokeLinejoin: 'round',
308
+
309
+ className,
310
+
311
+ },
312
+
313
+ React.createElement('polyline', { points: '4 14 10 14 10 20' }),
314
+
315
+ React.createElement('polyline', { points: '20 10 14 10 14 4' }),
316
+
317
+ React.createElement('line', { x1: 14, y1: 10, x2: 21, y2: 3 }),
318
+
319
+ React.createElement('line', { x1: 3, y1: 21, x2: 10, y2: 14 }),
320
+
321
+ );
322
+
323
+ }
324
+
285
325
  function IconRotateCcw({ size = 13, className = '' }) {
286
326
 
287
327
  return React.createElement(
@@ -372,6 +412,54 @@ window.__ModuleLoader__.load({
372
412
 
373
413
  }
374
414
 
415
+ .dsh-goal-dock-pill {
416
+ display: inline-flex;
417
+ align-items: center;
418
+ gap: 8px;
419
+ background: var(--dsw-alias-surface-raised);
420
+ border: 1px solid var(--dsw-alias-border-subtle);
421
+ border-radius: 9999px;
422
+ padding: 4px 12px;
423
+ box-shadow: var(--dsw-alias-shadow-md);
424
+ backdrop-filter: blur(8px);
425
+ font-size: 13px;
426
+ max-width: 100%;
427
+ box-sizing: border-box;
428
+ }
429
+ .dsh-goal-dock-pill .dsh-goal-title {
430
+ max-width: 260px;
431
+ white-space: nowrap;
432
+ overflow: hidden;
433
+ text-overflow: ellipsis;
434
+ font-weight: 500;
435
+ }
436
+ .dsh-goal-checklist {
437
+ margin-top: 6px;
438
+ display: flex;
439
+ flex-direction: column;
440
+ gap: 4px;
441
+ }
442
+ .dsh-goal-checklist-item {
443
+ display: flex;
444
+ align-items: center;
445
+ gap: 6px;
446
+ font-size: 12px;
447
+ color: var(--dsw-alias-label-secondary);
448
+ cursor: pointer;
449
+ user-select: none;
450
+ }
451
+ .dsh-goal-checklist-item input[type="checkbox"] {
452
+ cursor: pointer;
453
+ margin: 0;
454
+ }
455
+ .dsh-goal-budget-warn {
456
+ color: var(--dsw-alias-status-warning);
457
+ font-weight: 600;
458
+ display: inline-flex;
459
+ align-items: center;
460
+ gap: 4px;
461
+ font-size: 12px;
462
+ }
375
463
  .dsh-goal-dock.dsh-goal-dock-quick {
376
464
 
377
465
  justify-content: flex-start;
@@ -1646,6 +1734,10 @@ window.__ModuleLoader__.load({
1646
1734
 
1647
1735
  const [gitCopied, setGitCopied] = useState(false);
1648
1736
 
1737
+ const [artifactSaved, setArtifactSaved] = useState(false);
1738
+
1739
+ const [savingArtifact, setSavingArtifact] = useState(false);
1740
+
1649
1741
  const [nudgeText, setNudgeText] = useState('');
1650
1742
 
1651
1743
  const [nudgeSent, setNudgeSent] = useState(false);
@@ -2010,6 +2102,51 @@ window.__ModuleLoader__.load({
2010
2102
 
2011
2103
  m.notes ? React.createElement('div', { style: { fontSize: 12, color: 'var(--dsw-alias-label-tertiary)', marginTop: 2 } }, m.notes) : null,
2012
2104
 
2105
+ Array.isArray(m.checklist) && m.checklist.length > 0
2106
+ ? React.createElement(
2107
+ 'div',
2108
+ { className: 'dsh-goal-checklist' },
2109
+ m.checklist.map((item, itemIdx) =>
2110
+ React.createElement(
2111
+ 'label',
2112
+ { key: itemIdx, className: 'dsh-goal-checklist-item' },
2113
+ React.createElement('input', {
2114
+ type: 'checkbox',
2115
+ checked: Boolean(item.done),
2116
+ onChange: () => onAction?.('toggle_checklist_item', { milestoneId: m.id, itemIndex: itemIdx, done: !item.done }),
2117
+ }),
2118
+ React.createElement(
2119
+ 'span',
2120
+ { style: { textDecoration: item.done ? 'line-through' : 'none', color: item.done ? 'var(--dsw-alias-label-tertiary)' : 'inherit' } },
2121
+ item.text
2122
+ )
2123
+ )
2124
+ )
2125
+ )
2126
+ : null,
2127
+
2128
+ m.checkpointCommit
2129
+ ? React.createElement(
2130
+ 'div',
2131
+ { style: { marginTop: 4 } },
2132
+ React.createElement(
2133
+ 'button',
2134
+ {
2135
+ type: 'button',
2136
+ className: 'dsh-goal-btn-inline-reset',
2137
+ style: { fontSize: 11, padding: '1px 6px' },
2138
+ title: t('rollbackBtn') || 'Rollback to Checkpoint',
2139
+ onClick: () => {
2140
+ if (confirm(t('rollbackConfirm') || `Rollback working tree to checkpoint ${m.checkpointCommit}?`)) {
2141
+ onAction?.('rollback_milestone', { commit: m.checkpointCommit });
2142
+ }
2143
+ },
2144
+ },
2145
+ `↩ ${m.checkpointCommit}`
2146
+ )
2147
+ )
2148
+ : null,
2149
+
2013
2150
  ),
2014
2151
 
2015
2152
  ),
@@ -2086,6 +2223,28 @@ window.__ModuleLoader__.load({
2086
2223
 
2087
2224
  ),
2088
2225
 
2226
+ React.createElement(
2227
+ 'button',
2228
+ {
2229
+ className: `dsh-goal-btn dsh-goal-btn-copy${artifactSaved ? ' copied' : ''}`,
2230
+ disabled: savingArtifact,
2231
+ onClick: async () => {
2232
+ setSavingArtifact(true);
2233
+ try {
2234
+ await onAction?.('save_artifact');
2235
+ setArtifactSaved(true);
2236
+ setTimeout(() => setArtifactSaved(false), 3000);
2237
+ } catch (err) {
2238
+ console.warn('[dsh-goal] Save artifact error:', err);
2239
+ } finally {
2240
+ setSavingArtifact(false);
2241
+ }
2242
+ },
2243
+ title: t('saveArtifact') || 'Save Run Artifact to .dsh/goals/',
2244
+ },
2245
+ artifactSaved ? (t('artifactSaved') || '✅ Saved to .dsh/goals/') : (t('saveArtifact') || '💾 Save Run Artifact')
2246
+ ),
2247
+
2089
2248
  )
2090
2249
 
2091
2250
  : null,
@@ -2408,6 +2567,26 @@ window.__ModuleLoader__.load({
2408
2567
 
2409
2568
  const [isQuickLaunchOpen, setIsQuickLaunchOpen] = useState(false);
2410
2569
 
2570
+ const [isDockCollapsed, setIsDockCollapsed] = useState(() => {
2571
+ try {
2572
+ return localStorage.getItem('dsh_goal_dock_collapsed') === 'true';
2573
+ } catch (e) {
2574
+ return false;
2575
+ }
2576
+ });
2577
+
2578
+ const toggleDockCollapse = () => {
2579
+ setIsDockCollapsed((prev) => {
2580
+ const next = !prev;
2581
+ try {
2582
+ localStorage.setItem('dsh_goal_dock_collapsed', String(next));
2583
+ } catch (e) {
2584
+ // Handled defensively: localStorage might be unavailable or quota exceeded
2585
+ }
2586
+ return next;
2587
+ });
2588
+ };
2589
+
2411
2590
  const [now, setNow] = useState(() => Date.now());
2412
2591
 
2413
2592
  const stateRef = useRef(null);
@@ -2460,7 +2639,7 @@ window.__ModuleLoader__.load({
2460
2639
 
2461
2640
  const active = resolveLocale();
2462
2641
 
2463
- return LOCALES[active]?.[key] || LOCALES.en?.[key] || LOCALES.ru?.[key] || key;
2642
+ return LOCALES[active]?.[key] || LOCALES.en?.[key] || key;
2464
2643
 
2465
2644
  };
2466
2645
 
@@ -2926,6 +3105,8 @@ window.__ModuleLoader__.load({
2926
3105
 
2927
3106
  const etaStr = state?.formattedETA ? ` (ETA ${state.formattedETA})` : '';
2928
3107
 
3108
+ const isBudgetExceeded = state?.state === 'PAUSED' && state?.logs?.some((l) => l.message?.includes('Token budget reached'));
3109
+
2929
3110
  return React.createElement(
2930
3111
 
2931
3112
  React.Fragment,
@@ -2938,7 +3119,54 @@ window.__ModuleLoader__.load({
2938
3119
 
2939
3120
  { className: 'dsh-goal-dock', 'data-goal-dock': 'true' },
2940
3121
 
2941
- React.createElement(
3122
+ isDockCollapsed
3123
+ ? React.createElement(
3124
+ 'div',
3125
+ { className: 'dsh-goal-dock-pill' },
3126
+ React.createElement(
3127
+ 'span',
3128
+ { className: 'dsh-goal-icon' },
3129
+ isCompleted ? React.createElement(IconCheck, { size: 14 }) : React.createElement(IconTarget, { size: 13 })
3130
+ ),
3131
+ React.createElement(
3132
+ 'span',
3133
+ { className: `dsh-goal-badge ${isCompleted ? 'dsh-goal-badge-ok' : isPaused ? 'dsh-goal-badge-warn' : ''}` },
3134
+ isCompleted ? (t('goalCompleted') || 'Done') : isPaused ? (t('paused') || 'Paused') : (t('goalLabel') || 'Goal')
3135
+ ),
3136
+ React.createElement('span', { className: 'dsh-goal-title', title: state.title }, state.title),
3137
+ React.createElement('span', { className: 'dsh-goal-time' }, `⏱ ${liveElapsedStr}`),
3138
+ !isCompleted ? React.createElement(
3139
+ 'button',
3140
+ {
3141
+ className: 'dsh-goal-btn icon-only',
3142
+ style: { padding: '2px 4px', height: 22 },
3143
+ title: isPaused ? (t('resume') || 'Resume') : (t('pause') || 'Pause'),
3144
+ onClick: () => handleAction(isPaused ? 'resume' : 'pause'),
3145
+ },
3146
+ isPaused ? React.createElement(IconPlay, { size: 12 }) : React.createElement(IconPause, { size: 12 })
3147
+ ) : null,
3148
+ React.createElement(
3149
+ 'button',
3150
+ {
3151
+ className: 'dsh-goal-btn icon-only',
3152
+ style: { padding: '2px 4px', height: 22 },
3153
+ title: t('details') || 'Expand goal details',
3154
+ onClick: () => setIsModalOpen(true),
3155
+ },
3156
+ React.createElement(IconExpand, { size: 12 })
3157
+ ),
3158
+ React.createElement(
3159
+ 'button',
3160
+ {
3161
+ className: 'dsh-goal-btn icon-only',
3162
+ style: { padding: '2px 4px', height: 22 },
3163
+ title: t('expandDock') || 'Expand goal banner',
3164
+ onClick: toggleDockCollapse,
3165
+ },
3166
+ '⤢'
3167
+ )
3168
+ )
3169
+ : React.createElement(
2942
3170
 
2943
3171
  'div',
2944
3172
 
@@ -3016,6 +3244,37 @@ window.__ModuleLoader__.load({
3016
3244
 
3017
3245
  : null,
3018
3246
 
3247
+ isBudgetExceeded
3248
+ ? React.createElement(
3249
+ 'button',
3250
+ {
3251
+ className: 'dsh-goal-btn',
3252
+ style: { background: 'var(--dsw-alias-status-warning)', color: 'var(--dsw-alias-surface-raised)', fontWeight: 600, fontSize: 12, padding: '2px 8px' },
3253
+ title: t('extendBudgetBtn') || '+50k Tokens & Resume',
3254
+ onClick: () => handleAction('extend_budget', { addTokens: 50000 }),
3255
+ },
3256
+ t('extendBudgetBtn') || '+50k & Resume'
3257
+ )
3258
+ : null,
3259
+
3260
+ React.createElement(
3261
+
3262
+ 'button',
3263
+
3264
+ {
3265
+
3266
+ className: 'dsh-goal-btn icon-only',
3267
+
3268
+ title: t('compactDock') || 'Collapse to compact pill',
3269
+
3270
+ onClick: toggleDockCollapse,
3271
+
3272
+ },
3273
+
3274
+ React.createElement(IconMinimize, { size: 14 }),
3275
+
3276
+ ),
3277
+
3019
3278
  React.createElement(
3020
3279
 
3021
3280
  'button',
@@ -3386,9 +3645,15 @@ window.__ModuleLoader__.load({
3386
3645
 
3387
3646
  const notifStatus = getFieldStatus('enableBrowserNotifications', draft?.enableBrowserNotifications, snap);
3388
3647
 
3389
- const dirty = maxIterStatus.isDirty || autoDriveStatus.isDirty || enableSoundStatus.isDirty || quickLaunchStatus.isDirty || toolFailStatus.isDirty || notifStatus.isDirty;
3648
+ const budgetStatus = getFieldStatus('maxTokenBudget', draft?.maxTokenBudget, snap);
3649
+
3650
+ const budgetWarnStatus = getFieldStatus('budgetWarningThreshold', draft?.budgetWarningThreshold, snap);
3651
+
3652
+ const autoCheckpointStatus = getFieldStatus('autoCheckpointOnMilestone', draft?.autoCheckpointOnMilestone, snap);
3390
3653
 
3391
- const invalid = maxIterStatus.invalid || toolFailStatus.invalid;
3654
+ const dirty = maxIterStatus.isDirty || autoDriveStatus.isDirty || enableSoundStatus.isDirty || quickLaunchStatus.isDirty || toolFailStatus.isDirty || notifStatus.isDirty || budgetStatus.isDirty || budgetWarnStatus.isDirty || autoCheckpointStatus.isDirty;
3655
+
3656
+ const invalid = maxIterStatus.invalid || toolFailStatus.invalid || budgetStatus.invalid || budgetWarnStatus.invalid;
3392
3657
 
3393
3658
  const disabled = !scope || saving || snap?.writable === false;
3394
3659
 
@@ -3410,6 +3675,12 @@ window.__ModuleLoader__.load({
3410
3675
 
3411
3676
  enableBrowserNotifications: prev?.enableBrowserNotifications !== undefined ? prev.enableBrowserNotifications : notifStatus.value,
3412
3677
 
3678
+ maxTokenBudget: prev?.maxTokenBudget !== undefined ? prev.maxTokenBudget : budgetStatus.value,
3679
+
3680
+ budgetWarningThreshold: prev?.budgetWarningThreshold !== undefined ? prev.budgetWarningThreshold : budgetWarnStatus.value,
3681
+
3682
+ autoCheckpointOnMilestone: prev?.autoCheckpointOnMilestone !== undefined ? prev.autoCheckpointOnMilestone : autoCheckpointStatus.value,
3683
+
3413
3684
  [field]: val,
3414
3685
 
3415
3686
  }));
@@ -3939,6 +4210,101 @@ window.__ModuleLoader__.load({
3939
4210
 
3940
4211
  ),
3941
4212
 
4213
+ React.createElement(
4214
+ 'div',
4215
+ { className: 'dsh-goal-card-field' },
4216
+ React.createElement(
4217
+ 'div',
4218
+ { className: 'dsh-goal-field-label-row' },
4219
+ React.createElement('label', { htmlFor: 'dsh-goal-token-budget' }, t('budgetLabel') || 'Token Budget (0 for unlimited):'),
4220
+ React.createElement(
4221
+ 'div',
4222
+ { className: 'dsh-goal-field-meta' },
4223
+ budgetStatus.isOverridden
4224
+ ? React.createElement('span', { className: 'dsh-goal-override-tag' }, t('overridden') || 'overridden')
4225
+ : null,
4226
+ budgetStatus.isOverridden
4227
+ ? React.createElement(
4228
+ 'button',
4229
+ {
4230
+ type: 'button',
4231
+ className: 'dsh-goal-btn-inline-reset',
4232
+ title: t('resetField') || 'Reset to default',
4233
+ onClick: () => edit('maxTokenBudget', snap?.base?.maxTokenBudget ?? DEFAULT_SETTINGS.maxTokenBudget),
4234
+ },
4235
+ React.createElement(IconRotateCcw, { size: 12 }),
4236
+ t('resetField') || 'Default'
4237
+ )
4238
+ : null
4239
+ )
4240
+ ),
4241
+ React.createElement('input', {
4242
+ id: 'dsh-goal-token-budget',
4243
+ type: 'number',
4244
+ min: 0,
4245
+ step: 10000,
4246
+ placeholder: String(budgetStatus.baseValue),
4247
+ value: budgetStatus.value !== undefined ? String(budgetStatus.value) : '',
4248
+ disabled,
4249
+ className: 'dsh-goal-card-input',
4250
+ onChange: (e) => edit('maxTokenBudget', parseNumberField(e.target.value)),
4251
+ })
4252
+ ),
4253
+
4254
+ React.createElement(
4255
+ 'div',
4256
+ { className: 'dsh-goal-card-field' },
4257
+ React.createElement(
4258
+ 'div',
4259
+ { className: 'dsh-goal-field-label-row' },
4260
+ React.createElement('label', { htmlFor: 'dsh-goal-warn-thresh' }, t('budgetWarnLabel') || 'Budget Warning Threshold (%):'),
4261
+ React.createElement(
4262
+ 'div',
4263
+ { className: 'dsh-goal-field-meta' },
4264
+ budgetWarnStatus.isOverridden
4265
+ ? React.createElement('span', { className: 'dsh-goal-override-tag' }, t('overridden') || 'overridden')
4266
+ : null,
4267
+ budgetWarnStatus.isOverridden
4268
+ ? React.createElement(
4269
+ 'button',
4270
+ {
4271
+ type: 'button',
4272
+ className: 'dsh-goal-btn-inline-reset',
4273
+ title: t('resetField') || 'Reset to default',
4274
+ onClick: () => edit('budgetWarningThreshold', snap?.base?.budgetWarningThreshold ?? DEFAULT_SETTINGS.budgetWarningThreshold),
4275
+ },
4276
+ React.createElement(IconRotateCcw, { size: 12 }),
4277
+ t('resetField') || 'Default'
4278
+ )
4279
+ : null
4280
+ )
4281
+ ),
4282
+ React.createElement('input', {
4283
+ id: 'dsh-goal-warn-thresh',
4284
+ type: 'number',
4285
+ min: 10,
4286
+ max: 100,
4287
+ placeholder: String(budgetWarnStatus.baseValue),
4288
+ value: budgetWarnStatus.value !== undefined ? String(budgetWarnStatus.value) : '',
4289
+ disabled,
4290
+ className: 'dsh-goal-card-input',
4291
+ onChange: (e) => edit('budgetWarningThreshold', parseNumberField(e.target.value)),
4292
+ })
4293
+ ),
4294
+
4295
+ React.createElement(
4296
+ 'div',
4297
+ { className: 'dsh-goal-check-row' },
4298
+ React.createElement('input', {
4299
+ id: 'dsh-goal-auto-checkpoint',
4300
+ type: 'checkbox',
4301
+ checked: Boolean(autoCheckpointStatus.value),
4302
+ disabled,
4303
+ onChange: (e) => edit('autoCheckpointOnMilestone', e.target.checked),
4304
+ }),
4305
+ React.createElement('label', { htmlFor: 'dsh-goal-auto-checkpoint' }, t('autoCheckpointLabel') || 'Auto-create Git checkpoint on milestone completed')
4306
+ ),
4307
+
3942
4308
  React.createElement(
3943
4309
 
3944
4310
  'div',
@@ -4077,6 +4443,19 @@ window.__ModuleLoader__.load({
4077
4443
  checking: 'Checking…',
4078
4444
  updateDone: 'Updated successfully. Please restart DSH to apply.',
4079
4445
  updateFailed: 'Update failed: ',
4446
+ compactDock: 'Collapse to compact pill',
4447
+ expandDock: 'Expand goal banner',
4448
+ saveArtifact: 'Save Run Artifact',
4449
+ artifactSaved: '✅ Artifact Saved (.dsh/goals/)',
4450
+ saveArtifactFailed: 'Artifact save failed',
4451
+ extendBudgetBtn: '+50k Tokens & Resume',
4452
+ budgetExceededBadge: 'Budget Limit Reached',
4453
+ budgetLabel: 'Token Budget (0 for unlimited):',
4454
+ budgetWarnLabel: 'Budget Warning Threshold (%):',
4455
+ autoCheckpointLabel: 'Auto-create Git checkpoint on milestone completed',
4456
+ rollbackBtn: 'Rollback to Checkpoint',
4457
+ rollbackConfirm: 'Rollback working tree to this checkpoint?',
4458
+ rollbackSuccess: 'Rolled back to checkpoint',
4080
4459
 
4081
4460
  goalCompleted: 'Goal completed',
4082
4461
 
@@ -4190,6 +4569,19 @@ window.__ModuleLoader__.load({
4190
4569
  checking: '正在检查…',
4191
4570
  updateDone: '更新成功。请重启 DSH 以生效。',
4192
4571
  updateFailed: '更新失败:',
4572
+ compactDock: '收起为迷你胶囊',
4573
+ expandDock: '展开目标横幅',
4574
+ saveArtifact: '导出运行报告工件',
4575
+ artifactSaved: '✅ 工件已保存 (.dsh/goals/)',
4576
+ saveArtifactFailed: '工件保存失败',
4577
+ extendBudgetBtn: '追加 50k Tokens 并恢复',
4578
+ budgetExceededBadge: '已达 Token 预算上限',
4579
+ budgetLabel: 'Token 预算上限 (0 为不限制):',
4580
+ budgetWarnLabel: '预算预警阈值 (%):',
4581
+ autoCheckpointLabel: '里程碑完成时自动创建 Git 检查点',
4582
+ rollbackBtn: '回滚到该检查点',
4583
+ rollbackConfirm: '确定将工作区代码回滚至该里程碑检查点吗?',
4584
+ rollbackSuccess: '已成功回滚到检查点',
4193
4585
 
4194
4586
  goalCompleted: '目标已完成',
4195
4587
 
@@ -4296,146 +4688,158 @@ window.__ModuleLoader__.load({
4296
4688
  module.exports.inject = ['slots', 'locale', 'settingsScope'];
4297
4689
 
4298
4690
  module.exports.apply = function apply(ctx) {
4691
+ if (typeof ctx.effect === 'function') {
4692
+ ctx.effect(() => {
4693
+ try {
4694
+ ctx.locale?.register?.(NS, LOCALES);
4695
+ } catch (err) { /* handled defensively */ }
4696
+
4697
+ const settingsScope =
4698
+ ctx.settingsScope && typeof ctx.settingsScope.bind === 'function'
4699
+ ? ctx.settingsScope.bind({ namespace: NS })
4700
+ : null;
4701
+
4702
+ const disposers = [];
4703
+ const registerSlotSafe = (name, entry, comp) => {
4704
+ try {
4705
+ if (typeof ctx.slots?.inject === 'function') {
4706
+ const unreg = ctx.slots.inject(name, () => {
4707
+ try {
4708
+ return ctx.slots.register(entry, comp);
4709
+ } catch (err) { /* handled defensively */ }
4710
+ });
4711
+ if (typeof unreg === 'function') disposers.push(unreg);
4712
+ } else if (typeof ctx.slots?.register === 'function') {
4713
+ const unreg = ctx.slots.register(entry, comp);
4714
+ if (typeof unreg === 'function') disposers.push(unreg);
4715
+ }
4716
+ } catch (err) { /* handled defensively */ }
4717
+ };
4718
+
4719
+ registerSlotSafe(
4720
+ 'conversation.input.dock',
4721
+ {
4722
+ name: 'conversation.input.dock',
4723
+ id: '@goodandready/dsh-goal',
4724
+ order: 10,
4725
+ locale: NS,
4726
+ inject: (slotProps) => ({ ctx, ...slotProps }),
4727
+ },
4728
+ GoalTopBanner,
4729
+ );
4730
+
4731
+ registerSlotSafe(
4732
+ 'plugins.item',
4733
+ {
4734
+ name: 'plugins.item',
4735
+ id: ROW_ID,
4736
+ order: 60,
4737
+ label: () => 'Goal',
4738
+ locale: NS,
4739
+ inject: () => ({ ctx, scope: settingsScope }),
4740
+ },
4741
+ GoalSettingsCard,
4742
+ );
4743
+
4744
+ registerSlotSafe(
4745
+ 'plugins.row.config',
4746
+ {
4747
+ name: 'plugins.row.config',
4748
+ key: ROW_CONFIG_KEY,
4749
+ locale: NS,
4750
+ inject: () => ({ ctx, scope: settingsScope }),
4751
+ },
4752
+ GoalSettingsCard,
4753
+ );
4754
+
4755
+ registerSlotSafe(
4756
+ 'settings.plugin.item',
4757
+ {
4758
+ name: 'settings.plugin.item',
4759
+ key: NS,
4760
+ locale: NS,
4761
+ inject: () => ({ ctx, scope: settingsScope }),
4762
+ },
4763
+ GoalSettingsCard,
4764
+ );
4299
4765
 
4300
- // Register localization
4301
-
4302
- try {
4303
-
4304
- ctx.locale?.register?.(NS, LOCALES);
4305
-
4306
- } catch (err) { /* handled defensively */ }
4307
-
4308
- // Plugin settings scope
4309
-
4310
- const settingsScope =
4311
-
4312
- ctx.settingsScope && typeof ctx.settingsScope.bind === 'function'
4313
-
4314
- ? ctx.settingsScope.bind({ namespace: NS })
4315
-
4316
- : null;
4317
-
4318
- const registerSlotSafe = (name, entry, comp) => {
4319
-
4766
+ return () => {
4767
+ for (const dispose of disposers) {
4768
+ try { dispose(); } catch (e) { /* handled defensively */ }
4769
+ }
4770
+ };
4771
+ }, 'dsh-goal: client slots & locale');
4772
+ } else {
4320
4773
  try {
4321
-
4322
- if (typeof ctx.slots?.inject === 'function') {
4323
-
4324
- ctx.slots.inject(name, () => {
4325
-
4326
- try {
4327
-
4328
- return ctx.slots.register(entry, comp);
4329
-
4330
- } catch (err) { /* handled defensively */ }
4331
-
4332
- });
4333
-
4334
- } else if (typeof ctx.slots?.register === 'function') {
4335
-
4336
- ctx.slots.register(entry, comp);
4337
-
4338
- }
4339
-
4774
+ ctx.locale?.register?.(NS, LOCALES);
4340
4775
  } catch (err) { /* handled defensively */ }
4341
4776
 
4342
- };
4343
-
4344
- // 1. Register composer dock widget
4345
-
4346
- registerSlotSafe(
4347
-
4348
- 'conversation.input.dock',
4349
-
4350
- {
4351
-
4352
- name: 'conversation.input.dock',
4353
-
4354
- id: '@goodandready/dsh-goal',
4355
-
4356
- order: 10,
4357
-
4358
- locale: NS,
4359
-
4360
- inject: (slotProps) => ({ ctx, ...slotProps }),
4361
-
4362
- },
4363
-
4364
- GoalTopBanner,
4365
-
4366
- );
4777
+ const settingsScope =
4778
+ ctx.settingsScope && typeof ctx.settingsScope.bind === 'function'
4779
+ ? ctx.settingsScope.bind({ namespace: NS })
4780
+ : null;
4367
4781
 
4368
- // 2. Register settings card.
4369
- // List seat (plugins.item): the seat the Plugins page renders as the plugin's own
4370
- // page with its configuration. The label is a static string on purpose — it is
4371
- // resolved while the page renders, and a locale lookup there would take the whole
4372
- // client batch down with it.
4373
- registerSlotSafe(
4374
-
4375
- 'plugins.item',
4376
-
4377
- {
4378
-
4379
- name: 'plugins.item',
4380
-
4381
- id: ROW_ID,
4382
-
4383
- order: 60,
4384
-
4385
- label: () => 'Goal',
4386
-
4387
- locale: NS,
4388
-
4389
- inject: () => ({ ctx, scope: settingsScope }),
4390
-
4391
- },
4392
-
4393
- GoalSettingsCard,
4394
-
4395
- );
4396
-
4397
- // Row seat and the legacy seat kept as fallbacks.
4398
-
4399
- registerSlotSafe(
4400
-
4401
- 'plugins.row.config',
4402
-
4403
- {
4404
-
4405
- name: 'plugins.row.config',
4406
-
4407
- key: ROW_CONFIG_KEY,
4408
-
4409
- locale: NS,
4410
-
4411
- inject: () => ({ ctx, scope: settingsScope }),
4412
-
4413
- },
4414
-
4415
- GoalSettingsCard,
4416
-
4417
- );
4418
-
4419
- registerSlotSafe(
4420
-
4421
- 'settings.plugin.item',
4422
-
4423
- {
4424
-
4425
- name: 'settings.plugin.item',
4426
-
4427
- key: NS,
4428
-
4429
- locale: NS,
4430
-
4431
- inject: () => ({ ctx, scope: settingsScope }),
4782
+ const registerSlotSafe = (name, entry, comp) => {
4783
+ try {
4784
+ if (typeof ctx.slots?.inject === 'function') {
4785
+ ctx.slots.inject(name, () => {
4786
+ try {
4787
+ return ctx.slots.register(entry, comp);
4788
+ } catch (err) { /* handled defensively */ }
4789
+ });
4790
+ } else if (typeof ctx.slots?.register === 'function') {
4791
+ ctx.slots.register(entry, comp);
4792
+ }
4793
+ } catch (err) { /* handled defensively */ }
4794
+ };
4432
4795
 
4433
- },
4796
+ registerSlotSafe(
4797
+ 'conversation.input.dock',
4798
+ {
4799
+ name: 'conversation.input.dock',
4800
+ id: '@goodandready/dsh-goal',
4801
+ order: 10,
4802
+ locale: NS,
4803
+ inject: (slotProps) => ({ ctx, ...slotProps }),
4804
+ },
4805
+ GoalTopBanner,
4806
+ );
4434
4807
 
4435
- GoalSettingsCard,
4808
+ registerSlotSafe(
4809
+ 'plugins.item',
4810
+ {
4811
+ name: 'plugins.item',
4812
+ id: ROW_ID,
4813
+ order: 60,
4814
+ label: () => 'Goal',
4815
+ locale: NS,
4816
+ inject: () => ({ ctx, scope: settingsScope }),
4817
+ },
4818
+ GoalSettingsCard,
4819
+ );
4436
4820
 
4437
- );
4821
+ registerSlotSafe(
4822
+ 'plugins.row.config',
4823
+ {
4824
+ name: 'plugins.row.config',
4825
+ key: ROW_CONFIG_KEY,
4826
+ locale: NS,
4827
+ inject: () => ({ ctx, scope: settingsScope }),
4828
+ },
4829
+ GoalSettingsCard,
4830
+ );
4438
4831
 
4832
+ registerSlotSafe(
4833
+ 'settings.plugin.item',
4834
+ {
4835
+ name: 'settings.plugin.item',
4836
+ key: NS,
4837
+ locale: NS,
4838
+ inject: () => ({ ctx, scope: settingsScope }),
4839
+ },
4840
+ GoalSettingsCard,
4841
+ );
4842
+ }
4439
4843
  };
4440
4844
 
4441
4845
  return module.exports;