@goodandready/dsh-goal 0.1.3 → 0.1.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/lib/client.js CHANGED
@@ -3,7 +3,7 @@ window.__ModuleLoader__.load({
3
3
  factory: (require) => {
4
4
  const module = { exports: {} };
5
5
  const React = require('react');
6
- const { useState, useEffect, useRef, useCallback } = React;
6
+ const { useState, useEffect, useRef, useCallback, useMemo } = React;
7
7
 
8
8
  const NS = 'dsh-goal';
9
9
 
@@ -108,7 +108,6 @@ window.__ModuleLoader__.load({
108
108
  strokeWidth: 2.5,
109
109
  strokeLinecap: 'round',
110
110
  strokeLinejoin: 'round',
111
- style: { color: 'var(--dsw-alias-status-success)' },
112
111
  className,
113
112
  },
114
113
  React.createElement('polyline', { points: '20 6 9 17 4 12' }),
@@ -173,7 +172,7 @@ window.__ModuleLoader__.load({
173
172
  );
174
173
  }
175
174
 
176
- // --- СТИЛИ И CSS ПРАВИЛА (ЧИСТЫЕ DSH ТОКЕНЫ БЕЗ ХАРДКОД-ФОЛЛБЕКОВ) ---
175
+ // --- СТИЛИ И CSS ПРАВИЛА (СТАНДАРТ DSH-CLINEBOT & ДИЗАЙН-СИСТЕМА DSH) ---
177
176
  const CSS_STYLES = `
178
177
  .dsh-goal-dock {
179
178
  box-sizing: border-box;
@@ -189,16 +188,23 @@ window.__ModuleLoader__.load({
189
188
  display: flex;
190
189
  align-items: center;
191
190
  justify-content: space-between;
192
- background: var(--dsw-alias-bg-layer-2);
191
+ background: var(--dsw-alias-bg-layer-3);
193
192
  border: 1px solid var(--dsw-alias-border-l2);
194
193
  border-radius: 12px;
195
- padding: 6px 14px;
194
+ padding: 7px 14px;
196
195
  font-family: inherit;
197
196
  font-size: 13px;
198
197
  color: var(--dsw-alias-label-primary);
199
198
  user-select: none;
200
199
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08);
201
- transition: all 0.2s ease;
200
+ transition: all 0.15s ease;
201
+ }
202
+ .dsh-goal-banner:hover {
203
+ border-color: var(--dsw-alias-label-tertiary);
204
+ }
205
+ .dsh-goal-banner.completed {
206
+ border-color: var(--dsw-alias-state-success-primary);
207
+ background: var(--dsw-alias-bg-layer-3);
202
208
  }
203
209
  .dsh-goal-left {
204
210
  display: flex;
@@ -213,13 +219,36 @@ window.__ModuleLoader__.load({
213
219
  display: flex;
214
220
  align-items: center;
215
221
  }
222
+ .dsh-goal-badge {
223
+ font-size: 11px;
224
+ padding: 2px 8px;
225
+ border-radius: 999px;
226
+ border: 1px solid var(--dsw-alias-border-l2);
227
+ display: inline-flex;
228
+ align-items: center;
229
+ gap: 4px;
230
+ font-weight: 600;
231
+ white-space: nowrap;
232
+ background: var(--dsw-alias-bg-layer-2);
233
+ color: var(--dsw-alias-label-primary);
234
+ }
235
+ .dsh-goal-badge-ok {
236
+ border-color: var(--dsw-alias-state-success-primary);
237
+ color: var(--dsw-alias-state-success-primary);
238
+ background: rgba(16, 185, 129, 0.08);
239
+ }
240
+ .dsh-goal-badge-warn {
241
+ border-color: var(--dsw-alias-state-warning-primary);
242
+ color: var(--dsw-alias-state-warning-primary);
243
+ background: rgba(245, 158, 11, 0.08);
244
+ }
216
245
  .dsh-goal-label {
217
246
  font-weight: 600;
218
247
  color: var(--dsw-alias-label-primary);
219
248
  white-space: nowrap;
220
249
  }
221
250
  .dsh-goal-label.completed {
222
- color: var(--dsw-alias-status-success);
251
+ color: var(--dsw-alias-state-success-primary);
223
252
  }
224
253
  .dsh-goal-title {
225
254
  color: var(--dsw-alias-label-secondary);
@@ -249,21 +278,38 @@ window.__ModuleLoader__.load({
249
278
  flex-shrink: 0;
250
279
  }
251
280
  .dsh-goal-btn {
252
- background: transparent;
253
- border: 0;
254
- padding: 4px;
281
+ appearance: none;
282
+ font: inherit;
283
+ background: var(--dsw-alias-bg-layer-2);
284
+ border: 1px solid var(--dsw-alias-border-l2);
285
+ padding: 5px 8px;
255
286
  margin: 0;
256
- color: var(--dsw-alias-label-tertiary);
287
+ color: var(--dsw-alias-label-primary);
257
288
  cursor: pointer;
258
- display: flex;
289
+ display: inline-flex;
259
290
  align-items: center;
260
291
  justify-content: center;
261
- border-radius: 6px;
292
+ gap: 5px;
293
+ border-radius: 8px;
294
+ font-size: 12px;
295
+ font-weight: 500;
262
296
  transition: all 0.15s ease;
263
297
  }
264
- .dsh-goal-btn:hover {
298
+ .dsh-goal-btn:hover:not(:disabled) {
299
+ background: var(--dsw-alias-bg-layer-4, var(--dsw-alias-bg-layer-2));
300
+ border-color: var(--dsw-alias-label-dimmed, var(--dsw-alias-border-l2));
301
+ }
302
+ .dsh-goal-btn.icon-only {
303
+ padding: 4px 6px;
304
+ border-radius: 6px;
305
+ border-color: transparent;
306
+ background: transparent;
307
+ color: var(--dsw-alias-label-tertiary);
308
+ }
309
+ .dsh-goal-btn.icon-only:hover {
265
310
  color: var(--dsw-alias-label-primary);
266
- background: var(--dsw-alias-bg-layer-3);
311
+ background: var(--dsw-alias-bg-layer-2);
312
+ border-color: var(--dsw-alias-border-l2);
267
313
  }
268
314
  .dsh-goal-btn.close {
269
315
  font-size: 13px;
@@ -271,10 +317,23 @@ window.__ModuleLoader__.load({
271
317
  padding: 3px 6px;
272
318
  }
273
319
  .dsh-goal-btn.close:hover {
274
- color: var(--dsw-alias-status-danger);
320
+ color: var(--dsw-alias-state-error-primary);
321
+ }
322
+ .dsh-goal-btn-primary {
323
+ background: var(--dsw-alias-label-primary);
324
+ color: var(--dsw-alias-bg-layer-3);
325
+ border-color: transparent;
326
+ }
327
+ .dsh-goal-btn-primary:hover:not(:disabled) {
328
+ opacity: 0.88;
275
329
  }
276
- .dsh-goal-btn.danger:hover {
277
- color: var(--dsw-alias-status-danger);
330
+ .dsh-goal-btn-danger {
331
+ color: var(--dsw-alias-state-error-primary);
332
+ border-color: rgba(239, 68, 68, 0.3);
333
+ }
334
+ .dsh-goal-btn-danger:hover:not(:disabled) {
335
+ background: rgba(239, 68, 68, 0.12) !important;
336
+ border-color: rgba(239, 68, 68, 0.5);
278
337
  }
279
338
 
280
339
  /* Modal Details */
@@ -292,7 +351,7 @@ window.__ModuleLoader__.load({
292
351
  background: var(--dsw-alias-bg-layer-3);
293
352
  border: 1px solid var(--dsw-alias-border-l2);
294
353
  border-radius: 12px;
295
- width: 520px;
354
+ width: 560px;
296
355
  max-width: 90vw;
297
356
  max-height: 80vh;
298
357
  display: flex;
@@ -300,48 +359,81 @@ window.__ModuleLoader__.load({
300
359
  box-shadow: 0 16px 36px rgba(0, 0, 0, 0.4);
301
360
  }
302
361
  .dsh-goal-modal-head {
303
- padding: 14px 18px;
362
+ padding: 16px 20px;
304
363
  border-bottom: 1px solid var(--dsw-alias-border-l2);
305
364
  display: flex;
306
365
  align-items: center;
307
366
  justify-content: space-between;
308
367
  }
309
368
  .dsh-goal-modal-title {
310
- font-size: 15px;
369
+ font-size: 16px;
311
370
  font-weight: 600;
312
371
  color: var(--dsw-alias-label-primary);
372
+ display: flex;
373
+ align-items: center;
374
+ gap: 8px;
313
375
  }
314
376
  .dsh-goal-modal-body {
315
- padding: 16px 18px;
377
+ padding: 18px 20px;
316
378
  overflow-y: auto;
317
379
  display: flex;
318
380
  flex-direction: column;
319
- gap: 12px;
381
+ gap: 14px;
382
+ }
383
+ .dsh-goal-stat-grid {
384
+ display: grid;
385
+ grid-template-columns: repeat(auto-fit, minmax(130px, 1fr));
386
+ gap: 10px;
387
+ }
388
+ .dsh-goal-stat-box {
389
+ padding: 10px 12px;
390
+ border: 1px solid var(--dsw-alias-border-l2);
391
+ border-radius: 8px;
392
+ background: var(--dsw-alias-bg-layer-2);
393
+ display: flex;
394
+ flex-direction: column;
395
+ gap: 2px;
396
+ }
397
+ .dsh-goal-stat-val {
398
+ font-size: 16px;
399
+ font-weight: 700;
400
+ color: var(--dsw-alias-label-primary);
401
+ }
402
+ .dsh-goal-stat-lbl {
403
+ font-size: 11px;
404
+ color: var(--dsw-alias-label-secondary);
320
405
  }
321
406
  .dsh-goal-milestone-item {
322
407
  display: flex;
323
408
  align-items: flex-start;
324
409
  gap: 10px;
325
- padding: 8px 10px;
410
+ padding: 10px 12px;
326
411
  border-radius: 8px;
327
412
  background: var(--dsw-alias-bg-layer-2);
413
+ border: 1px solid var(--dsw-alias-border-l2);
328
414
  font-size: 13px;
415
+ transition: all 0.15s ease;
329
416
  }
330
417
  .dsh-goal-modal-foot {
331
- padding: 12px 18px;
418
+ padding: 14px 20px;
332
419
  border-top: 1px solid var(--dsw-alias-border-l2);
333
420
  display: flex;
334
421
  justify-content: flex-end;
335
- gap: 8px;
422
+ align-items: center;
423
+ gap: 10px;
336
424
  }
337
425
 
338
- /* Settings Card */
426
+ /* Settings Card (clinebot pattern) */
339
427
  .dsh-goal-card {
340
428
  border: 1px solid var(--dsw-alias-border-l2);
341
429
  background: var(--dsw-alias-bg-layer-3);
342
430
  border-radius: 12px;
343
431
  list-style: none;
344
- margin-bottom: 10px;
432
+ margin-bottom: 12px;
433
+ transition: border-color 0.15s ease;
434
+ }
435
+ .dsh-goal-card:hover {
436
+ border-color: var(--dsw-alias-label-tertiary);
345
437
  }
346
438
  .dsh-goal-card-head {
347
439
  appearance: none;
@@ -350,13 +442,13 @@ window.__ModuleLoader__.load({
350
442
  color: inherit;
351
443
  text-align: left;
352
444
  cursor: pointer;
353
- background: 0 0;
445
+ background: transparent;
354
446
  border: 0;
355
447
  border-radius: 12px;
356
448
  display: flex;
357
449
  align-items: center;
358
450
  gap: 12px;
359
- padding: 14px 16px;
451
+ padding: 16px 20px;
360
452
  }
361
453
  .dsh-goal-card-title {
362
454
  color: var(--dsw-alias-label-primary);
@@ -370,14 +462,16 @@ window.__ModuleLoader__.load({
370
462
  }
371
463
  .dsh-goal-card-body {
372
464
  border-top: 1px solid var(--dsw-alias-border-l2);
373
- margin: 0 16px;
374
- padding-bottom: 12px;
465
+ margin: 0 20px;
466
+ padding: 16px 0 16px;
467
+ display: flex;
468
+ flex-direction: column;
469
+ gap: 14px;
375
470
  }
376
471
  .dsh-goal-card-field {
377
472
  display: flex;
378
473
  flex-direction: column;
379
474
  gap: 6px;
380
- padding: 12px 0;
381
475
  }
382
476
  .dsh-goal-field-label-row {
383
477
  display: flex;
@@ -385,6 +479,7 @@ window.__ModuleLoader__.load({
385
479
  justify-content: space-between;
386
480
  font-size: 13px;
387
481
  font-weight: 500;
482
+ color: var(--dsw-alias-label-primary);
388
483
  }
389
484
  .dsh-goal-field-meta {
390
485
  display: flex;
@@ -395,28 +490,35 @@ window.__ModuleLoader__.load({
395
490
  }
396
491
  .dsh-goal-override-tag {
397
492
  font-size: 11px;
398
- padding: 1px 6px;
493
+ padding: 2px 6px;
399
494
  border-radius: 4px;
400
495
  background: var(--dsw-alias-bg-layer-2);
401
496
  color: var(--dsw-alias-label-secondary);
497
+ border: 1px solid var(--dsw-alias-border-l2);
402
498
  }
403
499
  .dsh-goal-card-input {
404
- height: 34px;
500
+ height: 36px;
405
501
  border: 1px solid var(--dsw-alias-border-l2);
406
- background: var(--dsw-alias-bg-layer-3);
502
+ background: var(--dsw-alias-bg-layer-2);
407
503
  color: var(--dsw-alias-label-primary);
408
504
  border-radius: 8px;
409
505
  padding: 0 12px;
410
506
  font-size: 13px;
507
+ width: 100%;
508
+ box-sizing: border-box;
509
+ }
510
+ .dsh-goal-card-input:focus {
511
+ outline: none;
512
+ border-color: var(--dsw-alias-state-brand-primary);
411
513
  }
412
514
  .dsh-goal-card-input[aria-invalid="true"] {
413
- border-color: var(--dsw-alias-status-danger);
515
+ border-color: var(--dsw-alias-state-error-primary);
414
516
  }
415
517
  .dsh-goal-chev {
416
518
  margin-left: auto;
417
519
  flex: none;
418
520
  color: var(--dsw-alias-label-tertiary);
419
- transition: transform .16s;
521
+ transition: transform .16s ease;
420
522
  }
421
523
  .dsh-goal-chev-open {
422
524
  transform: rotate(180deg);
@@ -425,7 +527,7 @@ window.__ModuleLoader__.load({
425
527
  display: flex;
426
528
  align-items: center;
427
529
  justify-content: space-between;
428
- padding: 8px 0;
530
+ padding: 6px 0;
429
531
  }
430
532
  .dsh-goal-check {
431
533
  display: flex;
@@ -440,11 +542,11 @@ window.__ModuleLoader__.load({
440
542
  display: flex;
441
543
  justify-content: flex-end;
442
544
  align-items: center;
443
- gap: 8px;
444
- padding: 12px 0 4px;
545
+ gap: 10px;
546
+ padding: 14px 0 4px;
445
547
  }
446
548
  .dsh-goal-foot-note {
447
- color: var(--dsw-alias-status-danger);
549
+ color: var(--dsw-alias-state-error-primary);
448
550
  font-size: 12px;
449
551
  margin-right: auto;
450
552
  }
@@ -452,7 +554,7 @@ window.__ModuleLoader__.load({
452
554
  background: transparent;
453
555
  border: 0;
454
556
  cursor: pointer;
455
- padding: 2px 4px;
557
+ padding: 2px 6px;
456
558
  display: flex;
457
559
  align-items: center;
458
560
  gap: 4px;
@@ -470,19 +572,19 @@ window.__ModuleLoader__.load({
470
572
  cursor: pointer;
471
573
  border: 1px solid var(--dsw-alias-border-l2);
472
574
  border-radius: 8px;
473
- padding: 5px 14px;
575
+ padding: 6px 14px;
474
576
  font-size: 13px;
475
- background: transparent;
476
- color: var(--dsw-alias-label-secondary);
577
+ background: var(--dsw-alias-bg-layer-2);
578
+ color: var(--dsw-alias-label-primary);
477
579
  transition: all 0.15s ease;
478
580
  }
479
581
  .dsh-goal-discard:hover:not(:disabled) {
480
- color: var(--dsw-alias-label-primary);
481
- background: var(--dsw-alias-bg-layer-2);
582
+ background: var(--dsw-alias-bg-layer-4, var(--dsw-alias-bg-layer-2));
583
+ border-color: var(--dsw-alias-label-dimmed, var(--dsw-alias-border-l2));
482
584
  }
483
585
  .dsh-goal-discard:disabled {
484
586
  opacity: 0.4;
485
- cursor: default;
587
+ cursor: not-allowed;
486
588
  }
487
589
  .dsh-goal-save {
488
590
  appearance: none;
@@ -490,16 +592,19 @@ window.__ModuleLoader__.load({
490
592
  cursor: pointer;
491
593
  border: 1px solid transparent;
492
594
  border-radius: 8px;
493
- padding: 5px 14px;
595
+ padding: 6px 16px;
494
596
  font-size: 13px;
495
597
  background: var(--dsw-alias-label-primary);
496
598
  color: var(--dsw-alias-bg-layer-3);
497
599
  font-weight: 500;
498
600
  transition: opacity 0.15s ease;
499
601
  }
602
+ .dsh-goal-save:hover:not(:disabled) {
603
+ opacity: 0.88;
604
+ }
500
605
  .dsh-goal-save:disabled {
501
606
  opacity: 0.4;
502
- cursor: default;
607
+ cursor: not-allowed;
503
608
  }
504
609
  `;
505
610
 
@@ -508,6 +613,7 @@ window.__ModuleLoader__.load({
508
613
  if (document.getElementById('dsh-goal-styles')) return;
509
614
  const style = document.createElement('style');
510
615
  style.id = 'dsh-goal-styles';
616
+ style.dataset.dshPlugin = NS;
511
617
  style.textContent = CSS_STYLES;
512
618
  document.head.appendChild(style);
513
619
  }
@@ -521,31 +627,31 @@ window.__ModuleLoader__.load({
521
627
 
522
628
  return React.createElement(
523
629
  'div',
524
- {
525
- className: 'dsh-goal-modal-overlay',
526
- onClick: (e) => {
527
- if (e.target === e.currentTarget) onClose();
528
- },
529
- },
630
+ { className: 'dsh-goal-modal-overlay', onClick: onClose },
530
631
  React.createElement(
531
632
  'div',
532
- { className: 'dsh-goal-modal' },
633
+ {
634
+ className: 'dsh-goal-modal',
635
+ onClick: (e) => e.stopPropagation(),
636
+ },
533
637
  React.createElement(
534
638
  'div',
535
639
  { className: 'dsh-goal-modal-head' },
536
640
  React.createElement(
537
641
  'div',
538
- { style: { display: 'flex', alignItems: 'center', gap: 8 } },
539
- isCompleted ? React.createElement(IconCheck, { size: 18 }) : React.createElement(IconTarget, { size: 18 }),
540
- React.createElement(
541
- 'span',
542
- { className: 'dsh-goal-modal-title' },
543
- isCompleted ? (t('modalTitleCompleted') || 'Цель выполнена: план и результаты') : (t('modalTitle') || 'План и статус цели'),
544
- ),
642
+ { className: 'dsh-goal-modal-title' },
643
+ isCompleted
644
+ ? React.createElement(IconCheck, { size: 18, className: 'cb-badge-ok' })
645
+ : React.createElement(IconTarget, { size: 18 }),
646
+ isCompleted ? (t('modalTitleCompleted') || 'Цель выполнена: план и результаты') : (t('modalTitle') || 'План и статус цели'),
545
647
  ),
546
648
  React.createElement(
547
649
  'button',
548
- { className: 'dsh-goal-btn', onClick: onClose, title: t('close') || 'Закрыть' },
650
+ {
651
+ className: 'dsh-goal-btn icon-only close',
652
+ onClick: onClose,
653
+ title: t('close') || 'Закрыть',
654
+ },
549
655
  '✕',
550
656
  ),
551
657
  ),
@@ -555,25 +661,42 @@ window.__ModuleLoader__.load({
555
661
  React.createElement(
556
662
  'div',
557
663
  null,
558
- React.createElement('div', { style: { fontWeight: 600, fontSize: 15, marginBottom: 4 } }, state.title),
559
- state.description ? React.createElement('div', { style: { color: 'var(--dsw-alias-label-secondary)', fontSize: 13, marginBottom: 4 } }, state.description) : null,
664
+ React.createElement('div', { style: { fontWeight: 600, fontSize: 16, marginBottom: 4, color: 'var(--dsw-alias-label-primary)' } }, state.title),
665
+ state.description ? React.createElement('div', { style: { color: 'var(--dsw-alias-label-secondary)', fontSize: 13, marginBottom: 6, lineHeight: 1.4 } }, state.description) : null,
560
666
  ),
561
667
  React.createElement(
562
668
  'div',
563
- { style: { display: 'flex', justifyContent: 'flex-end', fontSize: 12, color: 'var(--dsw-alias-label-tertiary)' } },
564
- React.createElement('span', null, `${t('time') || 'Время'}: ${state.formattedElapsed || '0s'}`),
669
+ { className: 'dsh-goal-stat-grid' },
670
+ React.createElement(
671
+ 'div',
672
+ { className: 'dsh-goal-stat-box' },
673
+ React.createElement('span', { className: 'dsh-goal-stat-val' }, state.formattedElapsed || '0s'),
674
+ React.createElement('span', { className: 'dsh-goal-stat-lbl' }, t('time') || 'Время работы'),
675
+ ),
676
+ React.createElement(
677
+ 'div',
678
+ { className: 'dsh-goal-stat-box' },
679
+ React.createElement('span', { className: 'dsh-goal-stat-val' }, `${state.iterationsCount || 0}/${state.maxIterations || 25}`),
680
+ React.createElement('span', { className: 'dsh-goal-stat-lbl' }, 'Итерации'),
681
+ ),
682
+ React.createElement(
683
+ 'div',
684
+ { className: 'dsh-goal-stat-box' },
685
+ React.createElement('span', { className: 'dsh-goal-stat-val' }, `${state.progressPercent || 0}%`),
686
+ React.createElement('span', { className: 'dsh-goal-stat-lbl' }, 'Прогресс'),
687
+ ),
565
688
  ),
566
689
  React.createElement(
567
690
  'div',
568
691
  { style: { display: 'flex', flexDirection: 'column', gap: 8, marginTop: 4 } },
569
- React.createElement('div', { style: { fontWeight: 600, fontSize: 13 } }, t('milestones') || 'План работ:'),
692
+ React.createElement('div', { style: { fontWeight: 600, fontSize: 13, color: 'var(--dsw-alias-label-primary)' } }, t('milestones') || 'План работ:'),
570
693
  milestones.length === 0
571
694
  ? React.createElement('div', { style: { color: 'var(--dsw-alias-label-tertiary)', fontSize: 12, padding: '8px 0' } }, t('noMilestones') || 'Агент формирует план работ...')
572
695
  : milestones.map((m, i) =>
573
696
  React.createElement(
574
697
  'div',
575
698
  { key: m.id || i, className: 'dsh-goal-milestone-item' },
576
- React.createElement('span', { style: { fontSize: 14 } }, m.status === 'completed' ? '✅' : m.status === 'in_progress' ? '🔄' : '⏳'),
699
+ React.createElement('span', { style: { fontSize: 15 } }, m.status === 'completed' ? '✅' : m.status === 'in_progress' ? '🔄' : '⏳'),
577
700
  React.createElement(
578
701
  'div',
579
702
  { style: { flex: 1, minWidth: 0 } },
@@ -601,12 +724,11 @@ window.__ModuleLoader__.load({
601
724
  ? React.createElement(
602
725
  'button',
603
726
  {
604
- className: 'dsh-goal-btn danger',
727
+ className: 'dsh-goal-btn dsh-goal-btn-danger',
605
728
  onClick: () => {
606
729
  onAction('clear');
607
730
  onClose();
608
731
  },
609
- style: { padding: '6px 12px', fontSize: 13 },
610
732
  },
611
733
  t('closeBanner') || 'Закрыть плашку цели',
612
734
  )
@@ -614,9 +736,8 @@ window.__ModuleLoader__.load({
614
736
  React.createElement(
615
737
  'button',
616
738
  {
617
- className: 'dsh-goal-save',
739
+ className: 'dsh-goal-btn dsh-goal-btn-primary',
618
740
  onClick: onClose,
619
- style: { padding: '6px 16px' },
620
741
  },
621
742
  t('close') || 'Закрыть',
622
743
  ),
@@ -625,12 +746,32 @@ window.__ModuleLoader__.load({
625
746
  );
626
747
  }
627
748
 
749
+ function sessionIdOf(ctx, props) {
750
+ try {
751
+ if (props?.session?.id) return String(props.session.id);
752
+ if (props?.session?.sessionId) return String(props.session.sessionId);
753
+ if (props?.input?.sessionId) return String(props.input.sessionId);
754
+ if (ctx?.scope?.session) return String(ctx.scope.session.id || ctx.scope.session.header?.id || '');
755
+ if (typeof ctx?.scope?.id === 'string') return ctx.scope.id;
756
+ if (ctx?.session) return String(ctx.session.id || ctx.session.header?.id || '');
757
+ if (typeof window !== 'undefined' && window.location) {
758
+ const params = new URLSearchParams(window.location.search);
759
+ const fromUrl = params.get('session') || params.get('sessionId');
760
+ if (fromUrl) return fromUrl;
761
+ }
762
+ } catch (_) {}
763
+ return 'default';
764
+ }
765
+
628
766
  // --- ПАНЕЛЬ ЦЕЛИ НАД ПОЛЕМ ВВОДА (COMPOSER DOCK WIDGET) ---
629
767
  function GoalTopBanner(props) {
630
768
  const { ctx } = props || {};
769
+ const sid = sessionIdOf(ctx, props);
631
770
  const [state, setState] = useState(null);
632
771
  const [isModalOpen, setIsModalOpen] = useState(false);
633
772
  const [now, setNow] = useState(() => Date.now());
773
+ const stateRef = useRef(null);
774
+ stateRef.current = state;
634
775
 
635
776
  const t = (key) => {
636
777
  if (ctx?.locale?.getSnapshot) {
@@ -655,26 +796,76 @@ window.__ModuleLoader__.load({
655
796
 
656
797
  const fetchState = useCallback(async () => {
657
798
  try {
658
- const res = await fetch('/dsh-goal/state');
799
+ const query = sid && sid !== 'default' ? `?sessionId=${encodeURIComponent(sid)}` : '';
800
+ const res = await fetch(`/dsh-goal/state${query}`, {
801
+ headers: { 'x-dsh-session-id': sid },
802
+ });
659
803
  if (res.ok) {
660
804
  const data = await res.json();
661
805
  setState(data);
662
806
  }
663
807
  } catch (_) {}
664
- }, []);
808
+ }, [sid]);
665
809
 
810
+ // Адаптивный поллинг: 1.5s когда RUNNING и вкладка активна, 8s когда IDLE/PAUSED/COMPLETED или вкладка скрыта
666
811
  useEffect(() => {
667
- fetchState();
668
- const timer = setInterval(fetchState, 1500);
669
- return () => clearInterval(timer);
812
+ let timer = null;
813
+ let isDisposed = false;
814
+
815
+ const scheduleNextPoll = () => {
816
+ if (isDisposed) return;
817
+ const currentState = stateRef.current;
818
+ const isDocHidden = typeof document !== 'undefined' && document.hidden;
819
+ const isRunning = currentState?.hasActiveGoal && currentState?.state === 'RUNNING';
820
+
821
+ let delay = 1500;
822
+ if (isDocHidden) {
823
+ delay = 10000;
824
+ } else if (!isRunning) {
825
+ delay = 8000;
826
+ }
827
+
828
+ timer = setTimeout(async () => {
829
+ await fetchState();
830
+ scheduleNextPoll();
831
+ }, delay);
832
+ };
833
+
834
+ fetchState().then(() => {
835
+ scheduleNextPoll();
836
+ });
837
+
838
+ const onVisibilityChange = () => {
839
+ if (typeof document !== 'undefined' && !document.hidden) {
840
+ if (timer) clearTimeout(timer);
841
+ fetchState().then(() => {
842
+ scheduleNextPoll();
843
+ });
844
+ }
845
+ };
846
+
847
+ if (typeof document !== 'undefined' && document.addEventListener) {
848
+ document.addEventListener('visibilitychange', onVisibilityChange);
849
+ }
850
+
851
+ return () => {
852
+ isDisposed = true;
853
+ if (timer) clearTimeout(timer);
854
+ if (typeof document !== 'undefined' && document.removeEventListener) {
855
+ document.removeEventListener('visibilitychange', onVisibilityChange);
856
+ }
857
+ };
670
858
  }, [fetchState]);
671
859
 
672
860
  const handleAction = async (action, extra = {}) => {
673
861
  try {
674
862
  const res = await fetch('/dsh-goal/action', {
675
863
  method: 'POST',
676
- headers: { 'Content-Type': 'application/json' },
677
- body: JSON.stringify({ action, ...extra }),
864
+ headers: {
865
+ 'Content-Type': 'application/json',
866
+ 'x-dsh-session-id': sid,
867
+ },
868
+ body: JSON.stringify({ action, sessionId: sid, ...extra }),
678
869
  });
679
870
  if (res.ok) {
680
871
  const data = await res.json();
@@ -719,8 +910,10 @@ window.__ModuleLoader__.load({
719
910
  ),
720
911
  React.createElement(
721
912
  'span',
722
- { className: `dsh-goal-label${isCompleted ? ' completed' : ''}` },
723
- isCompleted ? (t('goalCompleted') || 'Цель выполнена') : (t('goalLabel') || 'Текущая цель'),
913
+ {
914
+ className: `dsh-goal-badge ${isCompleted ? 'dsh-goal-badge-ok' : isPaused ? 'dsh-goal-badge-warn' : ''}`,
915
+ },
916
+ isCompleted ? (t('goalCompleted') || 'Цель выполнена') : isPaused ? (t('pause') || 'На паузе') : (t('goalLabel') || 'Текущая цель'),
724
917
  ),
725
918
  React.createElement('span', { className: 'dsh-goal-title', title: state.title }, state.title),
726
919
  ),
@@ -736,7 +929,7 @@ window.__ModuleLoader__.load({
736
929
  ? React.createElement(
737
930
  'button',
738
931
  {
739
- className: 'dsh-goal-btn',
932
+ className: 'dsh-goal-btn icon-only',
740
933
  title: isPaused ? (t('resume') || 'Возобновить') : (t('pause') || 'Приостановить'),
741
934
  onClick: () => handleAction(isPaused ? 'resume' : 'pause'),
742
935
  },
@@ -746,7 +939,7 @@ window.__ModuleLoader__.load({
746
939
  React.createElement(
747
940
  'button',
748
941
  {
749
- className: 'dsh-goal-btn',
942
+ className: 'dsh-goal-btn icon-only',
750
943
  title: t('details') || 'Развернуть детали цели',
751
944
  onClick: () => setIsModalOpen(true),
752
945
  },
@@ -756,7 +949,7 @@ window.__ModuleLoader__.load({
756
949
  ? React.createElement(
757
950
  'button',
758
951
  {
759
- className: 'dsh-goal-btn close',
952
+ className: 'dsh-goal-btn icon-only close',
760
953
  title: t('closeBanner') || 'Закрыть плашку цели',
761
954
  onClick: () => handleAction('clear'),
762
955
  },
@@ -778,12 +971,6 @@ window.__ModuleLoader__.load({
778
971
  }
779
972
 
780
973
  // --- ЛОГИКА ФОРМЫ НАСТРОЕК ---
781
- // Архитектурное решение (Issue #23):
782
- // Чистая логика состояния формы настроек вынесена в `lib/card-form-state.js` для запуска
783
- // быстрых изолированных unit-тестов через `node --test`. В `lib/client.js` эти функции
784
- // продублированы встроенным образом, поскольку клиентский бандл загружается браузерным
785
- // загрузчиком DSH (`window.__ModuleLoader__`) как самодостаточный автономный скрипт
786
- // без этапа сборки (bundler / webpack) и не может динамически импортировать локальные файлы.
787
974
  const DEFAULT_SETTINGS = {
788
975
  maxIterations: 25,
789
976
  autoDrive: true,
@@ -829,7 +1016,7 @@ window.__ModuleLoader__.load({
829
1016
  }
830
1017
  } else {
831
1018
  if (draftValue !== undefined) {
832
- isDirty = draftValue !== (userVal !== undefined ? userVal : baseVal);
1019
+ isDirty = Boolean(draftValue) !== (userVal !== undefined ? Boolean(userVal) : Boolean(baseVal));
833
1020
  }
834
1021
  }
835
1022
 
@@ -846,8 +1033,8 @@ window.__ModuleLoader__.load({
846
1033
  function computeSavePlan(draft, snap) {
847
1034
  if (!draft) return [];
848
1035
 
849
- const writes = [];
850
1036
  const fields = ['maxIterations', 'autoDrive', 'enableSound'];
1037
+ const writes = [];
851
1038
 
852
1039
  for (const f of fields) {
853
1040
  if (draft[f] === undefined) continue;
@@ -996,124 +1183,124 @@ window.__ModuleLoader__.load({
996
1183
  React.createElement(
997
1184
  'div',
998
1185
  { className: 'dsh-goal-card-field' },
999
- React.createElement(
1000
- 'div',
1001
- { className: 'dsh-goal-field-label-row' },
1002
- React.createElement('label', { htmlFor: 'dsh-goal-max-iter' }, t('maxIterLabel') || 'Максимум итераций (Safety Limit):'),
1003
- React.createElement(
1004
- 'div',
1005
- { className: 'dsh-goal-field-meta' },
1006
- maxIterStatus.isOverridden
1007
- ? React.createElement('span', { className: 'dsh-goal-override-tag' }, t('overridden') || 'изменено')
1008
- : null,
1009
- maxIterStatus.isOverridden
1010
- ? React.createElement(
1186
+ React.createElement(
1187
+ 'div',
1188
+ { className: 'dsh-goal-field-label-row' },
1189
+ React.createElement('label', { htmlFor: 'dsh-goal-max-iter' }, t('maxIterLabel') || 'Максимум итераций (Safety Limit):'),
1190
+ React.createElement(
1191
+ 'div',
1192
+ { className: 'dsh-goal-field-meta' },
1193
+ maxIterStatus.isOverridden
1194
+ ? React.createElement('span', { className: 'dsh-goal-override-tag' }, t('overridden') || 'изменено')
1195
+ : null,
1196
+ maxIterStatus.isOverridden
1197
+ ? React.createElement(
1198
+ 'button',
1199
+ {
1200
+ type: 'button',
1201
+ className: 'dsh-goal-btn-inline-reset',
1202
+ title: t('resetField') || 'Сбросить к значению по умолчанию',
1203
+ onClick: () => resetFieldToDefault('maxIterations'),
1204
+ },
1205
+ React.createElement(IconRotateCcw, { size: 12 }),
1206
+ t('resetField') || 'По умолчанию',
1207
+ )
1208
+ : null,
1209
+ ),
1210
+ ),
1211
+ React.createElement('input', {
1212
+ id: 'dsh-goal-max-iter',
1213
+ type: 'number',
1214
+ min: 1,
1215
+ placeholder: String(maxIterStatus.baseValue),
1216
+ className: 'dsh-goal-card-input',
1217
+ value: maxIterStatus.value !== undefined ? maxIterStatus.value : '',
1218
+ disabled,
1219
+ 'aria-invalid': invalid,
1220
+ onChange: (e) => edit('maxIterations', e.target.value),
1221
+ }),
1222
+ ),
1223
+ React.createElement(
1224
+ 'div',
1225
+ { className: 'dsh-goal-check-row' },
1226
+ React.createElement(
1227
+ 'label',
1228
+ { className: 'dsh-goal-check' },
1229
+ React.createElement('input', {
1230
+ type: 'checkbox',
1231
+ checked: autoDriveStatus.value === true,
1232
+ disabled,
1233
+ onChange: (e) => edit('autoDrive', e.target.checked),
1234
+ }),
1235
+ t('autoDriveLabel') || 'Авто-драйв: продолжать цикл автоматически',
1236
+ ),
1237
+ autoDriveStatus.isOverridden
1238
+ ? React.createElement(
1239
+ 'button',
1240
+ {
1241
+ type: 'button',
1242
+ className: 'dsh-goal-btn-inline-reset',
1243
+ title: t('resetField') || 'Сбросить к значению по умолчанию',
1244
+ onClick: () => resetFieldToDefault('autoDrive'),
1245
+ },
1246
+ React.createElement(IconRotateCcw, { size: 12 }),
1247
+ )
1248
+ : null,
1249
+ ),
1250
+ React.createElement(
1251
+ 'div',
1252
+ { className: 'dsh-goal-check-row' },
1253
+ React.createElement(
1254
+ 'label',
1255
+ { className: 'dsh-goal-check' },
1256
+ React.createElement('input', {
1257
+ type: 'checkbox',
1258
+ checked: enableSoundStatus.value === true,
1259
+ disabled,
1260
+ onChange: (e) => edit('enableSound', e.target.checked),
1261
+ }),
1262
+ t('soundLabel') || 'Звук по завершении цели',
1263
+ ),
1264
+ enableSoundStatus.isOverridden
1265
+ ? React.createElement(
1266
+ 'button',
1267
+ {
1268
+ type: 'button',
1269
+ className: 'dsh-goal-btn-inline-reset',
1270
+ title: t('resetField') || 'Сбросить к значению по умолчанию',
1271
+ onClick: () => resetFieldToDefault('enableSound'),
1272
+ },
1273
+ React.createElement(IconRotateCcw, { size: 12 }),
1274
+ )
1275
+ : null,
1276
+ ),
1277
+ React.createElement(
1278
+ 'div',
1279
+ { className: 'dsh-goal-foot' },
1280
+ failed
1281
+ ? React.createElement('span', { className: 'dsh-goal-foot-note' }, t('saveFailed') || 'Не сохранено — исправьте и повторите')
1282
+ : null,
1283
+ React.createElement(
1011
1284
  'button',
1012
1285
  {
1013
- type: 'button',
1014
- className: 'dsh-goal-btn-inline-reset',
1015
- title: t('resetField') || 'Сбросить к значению по умолчанию',
1016
- onClick: () => resetFieldToDefault('maxIterations'),
1286
+ className: 'dsh-goal-discard',
1287
+ disabled: !dirty || disabled,
1288
+ onClick: () => {
1289
+ setFailed(false);
1290
+ setDraft(null);
1291
+ },
1017
1292
  },
1018
- React.createElement(IconRotateCcw, { size: 12 }),
1019
- t('resetField') || 'По умолчанию',
1020
- )
1021
- : null,
1022
- ),
1023
- ),
1024
- React.createElement('input', {
1025
- id: 'dsh-goal-max-iter',
1026
- type: 'number',
1027
- min: 1,
1028
- placeholder: String(maxIterStatus.baseValue),
1029
- className: 'dsh-goal-card-input',
1030
- value: maxIterStatus.value !== undefined ? maxIterStatus.value : '',
1031
- disabled,
1032
- 'aria-invalid': invalid,
1033
- onChange: (e) => edit('maxIterations', e.target.value),
1034
- }),
1035
- ),
1036
- React.createElement(
1037
- 'div',
1038
- { className: 'dsh-goal-check-row' },
1039
- React.createElement(
1040
- 'label',
1041
- { className: 'dsh-goal-check' },
1042
- React.createElement('input', {
1043
- type: 'checkbox',
1044
- checked: autoDriveStatus.value === true,
1045
- disabled,
1046
- onChange: (e) => edit('autoDrive', e.target.checked),
1047
- }),
1048
- t('autoDriveLabel') || 'Авто-драйв: продолжать цикл автоматически',
1049
- ),
1050
- autoDriveStatus.isOverridden
1051
- ? React.createElement(
1052
- 'button',
1053
- {
1054
- type: 'button',
1055
- className: 'dsh-goal-btn-inline-reset',
1056
- title: t('resetField') || 'Сбросить к значению по умолчанию',
1057
- onClick: () => resetFieldToDefault('autoDrive'),
1058
- },
1059
- React.createElement(IconRotateCcw, { size: 12 }),
1060
- )
1061
- : null,
1062
- ),
1063
- React.createElement(
1064
- 'div',
1065
- { className: 'dsh-goal-check-row' },
1066
- React.createElement(
1067
- 'label',
1068
- { className: 'dsh-goal-check' },
1069
- React.createElement('input', {
1070
- type: 'checkbox',
1071
- checked: enableSoundStatus.value === true,
1072
- disabled,
1073
- onChange: (e) => edit('enableSound', e.target.checked),
1074
- }),
1075
- t('soundLabel') || 'Звук по завершении цели',
1076
- ),
1077
- enableSoundStatus.isOverridden
1078
- ? React.createElement(
1079
- 'button',
1080
- {
1081
- type: 'button',
1082
- className: 'dsh-goal-btn-inline-reset',
1083
- title: t('resetField') || 'Сбросить к значению по умолчанию',
1084
- onClick: () => resetFieldToDefault('enableSound'),
1085
- },
1086
- React.createElement(IconRotateCcw, { size: 12 }),
1087
- )
1088
- : null,
1089
- ),
1090
- React.createElement(
1091
- 'div',
1092
- { className: 'dsh-goal-foot' },
1093
- failed
1094
- ? React.createElement('span', { className: 'dsh-goal-foot-note' }, t('saveFailed') || 'Не сохранено — исправьте и повторите')
1095
- : null,
1096
- React.createElement(
1097
- 'button',
1098
- {
1099
- className: 'dsh-goal-discard',
1100
- disabled: !dirty || disabled,
1101
- onClick: () => {
1102
- setFailed(false);
1103
- setDraft(null);
1104
- },
1105
- },
1106
- t('discard') || 'Отменить правки',
1107
- ),
1108
- React.createElement(
1109
- 'button',
1110
- { className: 'dsh-goal-save', disabled: !dirty || disabled || invalid, onClick: save },
1111
- saving ? t('saving') || 'Сохранение…' : t('save') || 'Сохранить',
1112
- ),
1113
- ),
1293
+ t('discard') || 'Отменить правки',
1294
+ ),
1295
+ React.createElement(
1296
+ 'button',
1297
+ { className: 'dsh-goal-save', disabled: !dirty || disabled || invalid, onClick: save },
1298
+ saving ? t('saving') || 'Сохранение…' : t('save') || 'Сохранить',
1299
+ ),
1300
+ ),
1301
+ ),
1114
1302
  )
1115
- )
1116
- : null,
1303
+ : null,
1117
1304
  );
1118
1305
  }
1119
1306
 
@@ -1129,7 +1316,7 @@ window.__ModuleLoader__.load({
1129
1316
  details: 'Развернуть детали цели',
1130
1317
  modalTitle: 'План и статус цели',
1131
1318
  modalTitleCompleted: 'Цель выполнена: план и результаты',
1132
- time: 'Время',
1319
+ time: 'Время работы',
1133
1320
  milestones: 'План работ:',
1134
1321
  noMilestones: 'Агент формирует план работ...',
1135
1322
  close: 'Закрыть',
@@ -1157,7 +1344,7 @@ window.__ModuleLoader__.load({
1157
1344
  details: 'Expand Goal Details',
1158
1345
  modalTitle: 'Goal Plan & Status',
1159
1346
  modalTitleCompleted: 'Goal Completed: Plan & Results',
1160
- time: 'Time',
1347
+ time: 'Running time',
1161
1348
  milestones: 'Plan of work:',
1162
1349
  noMilestones: 'Agent is preparing the plan of work...',
1163
1350
  close: 'Close',
@@ -1212,7 +1399,7 @@ window.__ModuleLoader__.load({
1212
1399
  id: '@goodandready/dsh-goal',
1213
1400
  order: 10,
1214
1401
  locale: NS,
1215
- inject: () => ({ ctx }),
1402
+ inject: (slotProps) => ({ ctx, ...slotProps }),
1216
1403
  },
1217
1404
  GoalTopBanner,
1218
1405
  );