@goodandready/dsh-cost-meter 0.7.5 → 0.7.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.
@@ -1,6 +1,6 @@
1
1
  # Публичный релиз и deployment
2
2
 
3
- Релизная версия повышается patch-изменением (v0.7.4). После merge в `main`
3
+ Релизная версия повышается patch-изменением (v0.7.6). После merge в `main`
4
4
  создаются annotated tag, пакет в npm (`@goodandready/dsh-cost-meter`) и
5
5
  публичный GitHub release в `GooDAnDReaDY/dsh-cost-meter`.
6
6
 
@@ -82,6 +82,9 @@
82
82
  - Не допускать утечек локальных путей и инфраструктурных данных в коде или документации.
83
83
 
84
84
  ## History & Decisions
85
+ - 2026-09-05 (v0.7.7):
86
+ - Hotfix: исправлен ReferenceError (t is not defined) в объекте словаря ru (lib/client.js) (#15).
87
+ - Добавлен автоматический тест на чистое исполнение фабрики client loader (`test/public-route.test.mjs`).
85
88
  - 2026-09-05 (v0.7.4):
86
89
  - Добавлена нативная карточка настроек в слот `settings.plugin.item` (`#12`).
87
90
  - Реализована полная локализация клиентской части через `ctx.locale.register` (`#2`).
@@ -0,0 +1,16 @@
1
+ # Technical Findings & Architecture Decisions (v0.7.7)
2
+
3
+ ## 1. Zod Usage Rationale
4
+ - `zod` is used in `lib/index.js` for `usageByModelProjection`:
5
+ DeepSeek Harness core projection machinery requires `.parse(view)` method on projection schemas for runtime boundary contract validation. Schemastery does not provide `.parse()`, so `zod` is strictly required here and is not overengineering.
6
+
7
+ ## 2. Browser Reactivity & Settings
8
+ - Reactivity in browser UI (`lib/client.js`):
9
+ Using `React.useSyncExternalStore` connected to `ctx.settingsScope` allows instant updates of `currency` and `usdRate` in the header chip and popover without needing a page refresh or roundtrip state reload.
10
+
11
+ ## 3. Slot Hygiene
12
+ - Cleaned up slot injection in `lib/client.js`:
13
+ Removed `setTimeout(..., 500)` fallback registering `settings.section`. The standard `settings.plugin.item` slot cleanly renders the settings card inside the Harness Settings view.
14
+
15
+ ## 4. Style Isolation & Deduplication
16
+ - Added `data-dsh-plugin="dsh-cost-meter"` attribute to injected `<style>` tags to adhere to DSH UI design guidelines and prevent duplicate stylesheets.
@@ -0,0 +1,15 @@
1
+ # Implementation Progress (v0.7.7)
2
+
3
+ - [x] Create Gitea Issue #17
4
+ - [x] Create isolated worktree `refactor/stability-and-quality`
5
+ - [x] Bump version to 0.7.7 in `package.json`
6
+ - [x] Refactor `lib/client.js` for reactive settings, clean slot registration, and style tagging
7
+ - [x] Verify syntax and run test suite (`npm test`, 9 passed)
8
+ - [ ] Document design contract in `docs/design/DESIGN.md`
9
+ - [ ] Commit changes with conventional commits (`git-antigravity`)
10
+ - [ ] Create Pull Request and merge into `main`
11
+ - [ ] Test package on MiniPC isolated test server (`192.168.1.123:3082`)
12
+ - [ ] Smoke-test package on production candidate (`192.168.1.111:3080`)
13
+ - [ ] Publish v0.7.7 to npm and GitHub Release
14
+ - [ ] Install v0.7.7 from npm on MiniAI and verify live health
15
+ - [ ] Close Gitea Issue #17 and clean up worktree
@@ -0,0 +1,25 @@
1
+ # Task Plan: Refactor dsh-cost-meter for stability, simplification & performance
2
+
3
+ - **Issue**: Gitea #17
4
+ - **Worktree**: `.worktrees/refactor-stability`
5
+ - **Branch**: `refactor/stability-and-quality`
6
+
7
+ ## Phases
8
+
9
+ - [ ] **Phase 1: Architecture & Overengineering Cleanup**
10
+ - Remove `zod` dependency from `lib/index.js`, standardize on `@deepseek-ai/schemastery`.
11
+ - Remove dynamic fragile `require('@deepseek-ai/dsh-client-ui-primitives')` from `lib/client.js`, standardize on clean native inline SVG chevron.
12
+ - Remove race-prone `setTimeout(..., 500)` fallback slot registration in `lib/client.js`.
13
+ - [ ] **Phase 2: Fault Tolerance & Safe Caching**
14
+ - Wrap catalog filesystem operations (`mkdirSync`, `writeFileSync`, `readFileSync`) in safe `try/catch`.
15
+ - Add safe error catching in `lib/client.js` for `/dsh-cost-meter/state` fetch to prevent console error spam on DSH reload.
16
+ - [ ] **Phase 3: Performance & Settings Reactivity**
17
+ - Implement in-memory catalog index Map in `lib/index.js` for O(1) model rate lookups.
18
+ - Subscribe to `settingsScope` updates in `lib/client.js` to dynamically refresh currency and USD rate without reload.
19
+ - [ ] **Phase 4: Verification, Tests & Documentation**
20
+ - Run regression test suite `npm test` and add tests for schema validation and safe catalog failure.
21
+ - Update `docs/design/DESIGN.md` and release notes for v0.7.7.
22
+ - [ ] **Phase 5: Release Gate & Production Rollout**
23
+ - Test on MiniPC test server (`192.168.1.123:3082`).
24
+ - Merge PR, tag v0.7.7, publish to npm & GitHub release.
25
+ - Deploy to production web profile on MiniAI (`192.168.1.111:3080`).
package/lib/client.js CHANGED
@@ -56,17 +56,17 @@ window.__ModuleLoader__.load({
56
56
  subtitle: 'Стоимость сессии в шапке, тарифы токенов и детализация по моделям',
57
57
  chipUnknown: 'тариф ?',
58
58
  chipRate: 'тариф',
59
- offPeak: t('offPeak'),
59
+ offPeak: 'не-пик',
60
60
  peak: 'пик',
61
61
  flat: 'единый',
62
- modelsBreakdown: t('modelsBreakdown'),
63
- sessionTokens: t('sessionTokens'),
64
- totalCost: t('totalCost'),
65
- noPriceSpecified: t('noPriceSpecified'),
66
- noUsageYet: t('noUsageYet'),
62
+ modelsBreakdown: 'Расход по моделям',
63
+ sessionTokens: 'Токены сессии',
64
+ totalCost: 'Итого за сессию',
65
+ noPriceSpecified: 'не указана стоимость',
66
+ noUsageYet: 'Провайдер ещё не сообщил usage для этой сессии.',
67
67
  routeUnknown: 'маршрут неизвестен',
68
68
  catalogUnavailable: 'Каталог OpenRouter недоступен: ',
69
- modelNotFound: t('modelNotFound'),
69
+ modelNotFound: 'Модель не найдена в каталоге OpenRouter. Задайте ставки вручную в настройках (prices) или сопоставьте модель через modelMap.',
70
70
  'settings.loading': 'Загрузка настроек…',
71
71
  'settings.unavailable': 'Настройки недоступны (пространство ещё не зарегистрировано).',
72
72
  'settings.save': 'Сохранить',
@@ -131,6 +131,7 @@ window.__ModuleLoader__.load({
131
131
  if (document.getElementById('dsh-cost-meter-card-styles')) return
132
132
  const el = document.createElement('style')
133
133
  el.id = 'dsh-cost-meter-card-styles'
134
+ el.dataset.dshPlugin = 'dsh-cost-meter'
134
135
  el.textContent = cardCss
135
136
  document.head.appendChild(el)
136
137
  }
@@ -471,6 +472,14 @@ window.__ModuleLoader__.load({
471
472
  const [open, setOpen] = React.useState(false)
472
473
  const [now, setNow] = React.useState(() => new Date())
473
474
  const box = React.useRef(null)
475
+ const settingsScope = ctx.settingsScope || (ctx.services && ctx.services.settingsScope)
476
+ const settingsSnap = React.useSyncExternalStore
477
+ ? React.useSyncExternalStore(
478
+ (cb) => (settingsScope && typeof settingsScope.subscribe === 'function' ? settingsScope.subscribe(cb) : () => {}),
479
+ () => (settingsScope && typeof settingsScope.get === 'function' ? settingsScope.get(NS) : null),
480
+ () => null,
481
+ )
482
+ : null
474
483
 
475
484
  // Every model this session actually billed, so each part is priced on its
476
485
  // own tariff instead of the current model's. Usage arrives split into
@@ -507,8 +516,9 @@ window.__ModuleLoader__.load({
507
516
 
508
517
  if (!state) return null
509
518
 
510
- const currency = state.currency || '$'
511
- const fx = Number(state.usdRate) || 1
519
+ const stored = (settingsSnap && settingsSnap.value) || {}
520
+ const currency = stored.currency !== undefined ? String(stored.currency) : (state.currency || '$')
521
+ const fx = stored.usdRate !== undefined && !isNaN(Number(stored.usdRate)) ? Number(stored.usdRate) : (Number(state.usdRate) || 1)
512
522
  const schedule = state.schedule
513
523
  const hasUsage = usage !== undefined && usage !== null
514
524
  const minute = now.getUTCHours() * 60 + now.getUTCMinutes()
@@ -734,27 +744,12 @@ window.__ModuleLoader__.load({
734
744
  ),
735
745
  )
736
746
 
737
- let placed = false
738
- ctx.slots.inject('settings.plugin.item', () => {
739
- placed = true
740
- return ctx.slots.register(
747
+ ctx.slots.inject('settings.plugin.item', () =>
748
+ ctx.slots.register(
741
749
  { name: 'settings.plugin.item', key: NS, locale: NS, order: 20, inject: () => ({ ctx }) },
742
750
  (p) => React.createElement(CostMeterCard, { ...p, ctx }),
743
- )
744
- })
745
-
746
- // Fallback to settings.section if settings.plugin.item is not declared
747
- setTimeout(() => {
748
- if (placed) return
749
- try {
750
- ctx.slots.inject('settings.section', () =>
751
- ctx.slots.register(
752
- { name: 'settings.section', id: '@goodandready/dsh-cost-meter', order: 25, locale: NS, label: () => makeT(useActiveLocale(ctx) === 'ru' ? ru : en, en)('title'), inject: () => ({ ctx }) },
753
- (p) => React.createElement(CostMeterCard, { ...p, ctx }),
754
- ),
755
- )
756
- } catch (_) {}
757
- }, 500)
751
+ ),
752
+ )
758
753
  }
759
754
 
760
755
  module.exports = { apply, inject: ['slots', 'locale', 'settingsScope'] }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@goodandready/dsh-cost-meter",
3
- "version": "0.7.5",
3
+ "version": "0.7.7",
4
4
  "description": "DeepSeek Harness plugin: a session cost chip in the conversation header - live token usage by bucket, peak/off-peak tariff, and a countdown to the next tariff switch.",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",