@goodandready/dsh-goal 0.2.1 → 0.2.3

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 CHANGED
@@ -225,6 +225,18 @@ npm test
225
225
 
226
226
  ---
227
227
 
228
+ ---
229
+
230
+ ## 📦 Release Notes
231
+
232
+ ### v0.2.3
233
+ - **Auto-Updater**: Integrated one-click update API endpoint at `/api/dsh-goal/update` with fail-closed origin checks.
234
+ - **Modular Architecture**: Decomposed server codebase into focused submodules (<600 lines each) for routes, tools, store, reports, and updater.
235
+ - **Strict i18n Separation**: Purged hardcoded internal Russian strings/branches from `lib/*.js`; canonical English and Chinese core with Russian provided via `@goodandready/dsh-russian-lang`.
236
+ - **Reliability & Resilience**: Eliminated all empty catch blocks, added contextual logging on deferred tool completions, and prune dead SSE streams on error.
237
+ - **Design System Fidelity**: Replaced foreign CSS classes and hardcoded RGBA colors with native DSH theme design tokens (`--dsw-alias-*`).
238
+ - **Settings Scope Protection**: Resolved null/loading status handling in the settings card to prevent false `ready` state.
239
+
228
240
  ## 📄 License
229
241
 
230
242
  MIT © [GooDAnDReaDY](https://github.com/GooDAnDReaDY)
package/README.ru.md CHANGED
@@ -155,6 +155,18 @@ npm test
155
155
 
156
156
  ---
157
157
 
158
+ ---
159
+
160
+ ## 📦 История версий (Release Notes)
161
+
162
+ ### v0.2.3
163
+ - **Автообновление**: Встроен эндпоинт API автообновления в один клик по адресу `/api/dsh-goal/update` с fail-closed проверкой источника.
164
+ - **Модульная архитектура**: Серверная половина плагина полностью декомпозирована на специализированные модули (<600 строк каждый): маршруты, инструменты, хранилище, отчёты и модуль обновления.
165
+ - **Чистое разделение локализации**: Из `lib/*.js` удалены все зашитые русские строки и ветвления; ядро поддерживает только `en` и `zh`, а русская локализация поставляется плагином `@goodandready/dsh-russian-lang`.
166
+ - **Надёжность**: Устранены все пустые блоки `catch`, добавлено контекстное логирование завершения целей и автоматическая очистка отключившихся SSE-клиентов.
167
+ - **Дизайн-система**: Чужеродные CSS-классы и явные цвета RGBA заменены на стандартные токены дизайн-системы DSH (`--dsw-alias-*`).
168
+ - **Защита карточки настроек**: Исправлена обработка снимка настроек при загрузке или отсутствии сервиса, исключающая ложный статус `ready`.
169
+
158
170
  ## 📄 Лицензия
159
171
 
160
172
  MIT © [GooDAnDReaDY](https://github.com/GooDAnDReaDY)
package/README.zh.md CHANGED
@@ -219,3 +219,15 @@ npm test
219
219
  ## 📄 开源许可
220
220
 
221
221
  MIT © [GooDAnDReaDY](https://github.com/GooDAnDReaDY)
222
+
223
+ ---
224
+
225
+ ## 📦 版本历史 (Release Notes)
226
+
227
+ ### v0.2.3
228
+ - **自动更新**: 在 `/api/dsh-goal/update` 集成了一键更新 API 端点,并配备安全的原点验证。
229
+ - **模块化架构**: 将服务端完整解耦为高内聚子模块(各模块均低于 600 行):路由、工具、持久化存储、报告生成与更新器。
230
+ - **严格的多语言规范**: 从 `lib/*.js` 中清除了硬编码的俄语字符串与分支;核心原生支持英文(默认)与中文,俄语由 `@goodandready/dsh-russian-lang` 插件提供。
231
+ - **健壮性与错误处理**: 消除了所有空 catch 块,在目标完成与阻塞的上下文延迟中添加了日志记录,并自动清理断开的 SSE 连接。
232
+ - **设计系统规范**: 将第三方 CSS 类与硬编码 RGBA 颜色替换为原生 DSH 主题设计变量(`--dsw-alias-*`)。
233
+ - **设置卡片状态保护**: 修复了设置作用域未加载时错误显示 `ready` 状态的问题,未就绪时正确显示不可用/加载状态。
@@ -1,33 +1,40 @@
1
1
  /**
2
- * Чистая логика состояния и валидации формы настроек dsh-goal.
3
- * Не зависит от DOM/React, полностью тестируема через node --test.
2
+ * Pure state and validation logic for dsh-goal settings form.
3
+ * Independent from DOM/React, 100% testable via node --test.
4
4
  */
5
5
 
6
6
  export const DEFAULT_SETTINGS = {
7
7
  maxIterations: 25,
8
8
  autoDrive: true,
9
9
  enableSound: true,
10
+ showQuickLaunchButton: true,
11
+ consecutiveToolFailureLimit: 3,
12
+ enableBrowserNotifications: true,
13
+ storagePath: '',
10
14
  };
11
15
 
12
16
  /**
13
- * Парсинг числового значения настроек:
14
- * - пустая строка / null / undefined -> undefined (сброс к базовому значению)
15
- * - валидное число >= 1 -> number
16
- * - иначе -> NaN (invalid)
17
+ * Parse numeric settings field:
18
+ * - empty string / null / undefined -> undefined (reset override)
19
+ * - valid integer >= min -> number
20
+ * - otherwise -> NaN (invalid)
21
+ * @param {any} raw
22
+ * @param {number} [min=1]
23
+ * @returns {number|undefined}
17
24
  */
18
- export function parseNumberField(raw) {
25
+ export function parseNumberField(raw, min = 1) {
19
26
  if (raw === '' || raw === null || raw === undefined) {
20
27
  return undefined;
21
28
  }
22
29
  const n = Number(raw);
23
- if (!Number.isFinite(n) || n < 1 || !Number.isInteger(n)) {
30
+ if (!Number.isFinite(n) || n < min || !Number.isInteger(n)) {
24
31
  return NaN;
25
32
  }
26
33
  return n;
27
34
  }
28
35
 
29
36
  /**
30
- * Вычисление состояния конкретного поля:
37
+ * Compute status for a specific setting field
31
38
  * @param {string} field
32
39
  * @param {any} draftValue
33
40
  * @param {Object} snap - { value, base, user }
@@ -46,11 +53,10 @@ export function getFieldStatus(field, draftValue, snap) {
46
53
  if (field === 'maxIterations') {
47
54
  if (draftValue !== undefined) {
48
55
  if (draftValue === '') {
49
- // Пустая строка = сброс к базовому значению (не ошибка)
50
56
  invalid = false;
51
- isDirty = userVal !== undefined; // dirty если было переопределено
57
+ isDirty = userVal !== undefined;
52
58
  } else {
53
- const parsed = parseNumberField(draftValue);
59
+ const parsed = parseNumberField(draftValue, 1);
54
60
  if (Number.isNaN(parsed)) {
55
61
  invalid = true;
56
62
  isDirty = true;
@@ -59,10 +65,30 @@ export function getFieldStatus(field, draftValue, snap) {
59
65
  }
60
66
  }
61
67
  }
68
+ } else if (field === 'consecutiveToolFailureLimit') {
69
+ if (draftValue !== undefined) {
70
+ if (draftValue === '') {
71
+ invalid = false;
72
+ isDirty = userVal !== undefined;
73
+ } else {
74
+ const parsed = parseNumberField(draftValue, 0);
75
+ if (Number.isNaN(parsed)) {
76
+ invalid = true;
77
+ isDirty = true;
78
+ } else {
79
+ isDirty = parsed !== (userVal !== undefined ? userVal : baseVal);
80
+ }
81
+ }
82
+ }
83
+ } else if (field === 'storagePath') {
84
+ if (draftValue !== undefined) {
85
+ const cleanVal = typeof draftValue === 'string' ? draftValue.trim() : '';
86
+ isDirty = cleanVal !== (userVal !== undefined ? userVal : (baseVal || ''));
87
+ }
62
88
  } else {
63
- // boolean fields (autoDrive, enableSound)
89
+ // boolean fields (autoDrive, enableSound, showQuickLaunchButton, enableBrowserNotifications)
64
90
  if (draftValue !== undefined) {
65
- isDirty = draftValue !== (userVal !== undefined ? userVal : baseVal);
91
+ isDirty = Boolean(draftValue) !== (userVal !== undefined ? Boolean(userVal) : Boolean(baseVal));
66
92
  }
67
93
  }
68
94
 
@@ -77,7 +103,7 @@ export function getFieldStatus(field, draftValue, snap) {
77
103
  }
78
104
 
79
105
  /**
80
- * Вычисление списка операций записи (writes) для scope.set:
106
+ * Compute list of key-value writes for scope.set
81
107
  * @param {Object|null} draft
82
108
  * @param {Object} snap - { value, base, user }
83
109
  * @returns {Array<[string, any]>}
@@ -86,7 +112,15 @@ export function computeSavePlan(draft, snap) {
86
112
  if (!draft) return [];
87
113
 
88
114
  const writes = [];
89
- const fields = ['maxIterations', 'autoDrive', 'enableSound'];
115
+ const fields = [
116
+ 'maxIterations',
117
+ 'autoDrive',
118
+ 'enableSound',
119
+ 'showQuickLaunchButton',
120
+ 'consecutiveToolFailureLimit',
121
+ 'enableBrowserNotifications',
122
+ 'storagePath',
123
+ ];
90
124
 
91
125
  for (const f of fields) {
92
126
  if (draft[f] === undefined) continue;
@@ -95,11 +129,21 @@ export function computeSavePlan(draft, snap) {
95
129
  if (status.invalid) continue;
96
130
 
97
131
  if (status.isDirty) {
98
- if (f === 'maxIterations' && draft[f] === '') {
99
- // Сброс переопределения
100
- writes.push([f, undefined]);
101
- } else if (f === 'maxIterations') {
102
- writes.push([f, parseNumberField(draft[f])]);
132
+ if (f === 'maxIterations') {
133
+ if (draft[f] === '') {
134
+ writes.push([f, undefined]);
135
+ } else {
136
+ writes.push([f, parseNumberField(draft[f], 1)]);
137
+ }
138
+ } else if (f === 'consecutiveToolFailureLimit') {
139
+ if (draft[f] === '') {
140
+ writes.push([f, undefined]);
141
+ } else {
142
+ writes.push([f, parseNumberField(draft[f], 0)]);
143
+ }
144
+ } else if (f === 'storagePath') {
145
+ const clean = typeof draft[f] === 'string' ? draft[f].trim() : '';
146
+ writes.push([f, clean ? clean : undefined]);
103
147
  } else {
104
148
  writes.push([f, Boolean(draft[f])]);
105
149
  }