tensorgrid-ui 1.0.0

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/LICENSE ADDED
@@ -0,0 +1,95 @@
1
+ TENSOR GRID — Proprietary Software License
2
+ Copyright (c) 2026 TENSOR GRID. All rights reserved.
3
+
4
+ ENGLISH TEXT GOVERNS. The Russian translation below is informational only.
5
+
6
+ 1. GRANT
7
+
8
+ Subject to the terms below, you are granted a non-exclusive,
9
+ non-transferable, revocable right to install and use this software,
10
+ unmodified, for your own purposes, including inside a commercial
11
+ organization.
12
+
13
+ 2. RESTRICTIONS
14
+
15
+ You may NOT:
16
+
17
+ a) redistribute, publish, sell, rent, lease, or sublicense this
18
+ software or any part of it, whether modified or not;
19
+ b) create derivative works for distribution to third parties;
20
+ c) remove or alter any copyright, trademark, or attribution notice;
21
+ d) use the name "TENSOR GRID", its logo, or any confusingly similar
22
+ mark to identify your own product.
23
+
24
+ Inspecting the source in order to use the software, verify its
25
+ behaviour, or ensure its security is expressly permitted.
26
+
27
+ 3. RESERVED RIGHTS
28
+
29
+ All rights not expressly granted are reserved by the copyright
30
+ holder. This license grants no rights in any patent or trademark.
31
+
32
+ 4. THIRD-PARTY SOFTWARE
33
+
34
+ This software runs on DeepSeek Harness and other components
35
+ distributed under their own licenses. Nothing in this license alters
36
+ the terms of those components, and nothing here grants rights in
37
+ them.
38
+
39
+ 5. NO WARRANTY
40
+
41
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
42
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
43
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND
44
+ NONINFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE
45
+ FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY ARISING FROM THE USE OF
46
+ THIS SOFTWARE.
47
+
48
+ 6. TERMINATION
49
+
50
+ This license terminates automatically if you breach any of its
51
+ terms. On termination you must stop using the software and remove
52
+ all copies.
53
+
54
+ ────────────────────────────────────────────────────────────────────────
55
+
56
+ ПЕРЕВОД (справочный; юридическую силу имеет английский текст выше)
57
+
58
+ 1. ЧТО РАЗРЕШЕНО
59
+
60
+ Устанавливать и использовать эту программу в неизменённом виде для
61
+ своих целей, в том числе внутри коммерческой организации.
62
+
63
+ 2. ЧТО ЗАПРЕЩЕНО
64
+
65
+ а) распространять, публиковать, продавать, сдавать в аренду или
66
+ передавать по сублицензии — как есть или изменённой;
67
+ б) создавать производные версии для передачи третьим лицам;
68
+ в) удалять или изменять указания об авторстве и правах;
69
+ г) использовать название «TENSOR GRID», его логотип или сходное до
70
+ смешения обозначение для собственного продукта.
71
+
72
+ Изучать исходный код, чтобы пользоваться программой, проверять её
73
+ поведение или убедиться в её безопасности, — разрешено прямо.
74
+
75
+ 3. ОСТАЛЬНЫЕ ПРАВА
76
+
77
+ Все права, не переданные явно, сохраняются за правообладателем.
78
+ Права на патенты и товарные знаки этой лицензией не передаются.
79
+
80
+ 4. ЧУЖИЕ КОМПОНЕНТЫ
81
+
82
+ Программа работает поверх DeepSeek Harness и других компонентов,
83
+ распространяемых по собственным лицензиям. Эта лицензия их условий
84
+ не меняет и прав на них не даёт.
85
+
86
+ 5. БЕЗ ГАРАНТИЙ
87
+
88
+ Программа поставляется «как есть». Правообладатель не отвечает за
89
+ любые убытки, связанные с её использованием.
90
+
91
+ 6. ПРЕКРАЩЕНИЕ
92
+
93
+ Лицензия прекращается автоматически при нарушении любого из её
94
+ условий. После этого использование должно быть прекращено, а копии
95
+ удалены.
package/README.md ADDED
@@ -0,0 +1,486 @@
1
+ # Obsidian / Ion
2
+
3
+ Клиентский UI-пакет для веб-клиента DeepSeek Harness: футуристичная палитра
4
+ из 13 токенов темы плюс живой ambient-слой над фреймом.
5
+
6
+ Это **долговечная** форма дизайна. Динамический Cordis-плагин (`obsid-1`)
7
+ существует только в текущем процессе и исчезает при перезапуске; этот пакет
8
+ живёт файлом и монтируется при каждом старте профиля.
9
+
10
+ ## Из чего состоит
11
+
12
+ | Файл | Роль |
13
+ |---|---|
14
+ | `package.json` | секция `dsh.client` — по ней рантайм понимает, что у пакета есть браузерная половина |
15
+ | `lib/index.js` | host-половина: пустая по существу, нужна как строка композиции |
16
+ | `lib/client.js` | браузерная половина: палитра, стили, компонент слоя |
17
+
18
+ `lib/client.js` написан руками в том же формате, что и поставочные
19
+ `@deepseek-ai/dsh-client-ui-*`: регистрация в `window.__ModuleLoader__`,
20
+ зависимости через `require`, экспорт `apply` и `inject`. Сборщик не нужен.
21
+
22
+ ## Что пакет занимает
23
+
24
+ Только **аддитивные** слоты — те, у которых `replaceRisk: none`. Ни один
25
+ поставочный компонент не замещается, поэтому улучшения dsh продолжают
26
+ доезжать до интерфейса. Это условие закреплено в тесте белым списком.
27
+
28
+ | Слот | Вид | Что даёт |
29
+ |---|---|---|
30
+ | `shell.overlay` | list | ambient-слой: сферы, проход, кромка, зерно, виньетка, отклик |
31
+ | `settings.general.item` | list | строка «Атмосфера» — регулятор интенсивности |
32
+ | `settings.general.item` | list | строка «Акцент» — выбор ведущего тона |
33
+ | `conversation.input.dock` | list | невидимый драйвер отклика на работу агента |
34
+ | `conversation.hero.brand.mark` | single | знак на экране пустой сессии |
35
+
36
+ Плюс слой переопределения токенов через `theme.overrideTokens` — публичный
37
+ метод сервиса темы.
38
+
39
+ ### Две CSS-переменные вместо связи компонентов
40
+
41
+ - `--dsx-intensity` — пользовательский регулятор (0 / 0.5 / 1 / 1.7),
42
+ умножает непрозрачность всех слоёв. Хранится в `localStorage`.
43
+ - `--dsx-activity` — 0 в покое, 1 пока идёт ход агента.
44
+ - `--dsx-accent-rgb` и `--dsx-accent2-rgb` — ведущий тон и контрапункт
45
+ выбранного акцента, тройками каналов для `rgba(var(…), α)`.
46
+
47
+ Смена акцента бьёт по двум сторонам сразу: свечение ведут переменные, а
48
+ `--dsw-alias-brand-primary` переписывается повторным вызовом
49
+ `overrideTokens` с тем же источником — служба документирует это как замену
50
+ всего слоя целиком.
51
+
52
+ Переменная на `documentElement` — единственный мост между двумя поддеревьями:
53
+ ambient-слой живёт в корневом `shell.overlay`, у которого нет сессии, а хук
54
+ `useSession` раздаётся только сессионным слотам. Драйвер сидит в сессионном
55
+ слоте, ничего не рисует и лишь ведёт переменную.
56
+
57
+ ### Почему нет своих карточек инструментов
58
+
59
+ `tool.call.toolview` ключуется именем инструмента, и механизм сохранения
60
+ дочерних слотов существует: в опциях регистрации есть `children`, а
61
+ компонент получает `renderSlot` — так поставочная строка `read_image`
62
+ объявляет себе `tool.call.images`. То есть технически заменить карточку,
63
+ ничего не потеряв, можно.
64
+
65
+ Отказ по другой причине. Владельческие пропсы — `{ callId, toolName, block,
66
+ openFile, cwd, home, loadImage, inspect }`, и вся суть карточки в разборе
67
+ `block`: варианты, заголовок, вывод, ошибки, diff, терминал, поиск, файл.
68
+ Своя карточка обязана воспроизвести этот разбор и застывает на сегодняшней
69
+ форме внутренней структуры. Это ровно тот случай замороженной копии, ради
70
+ исключения которого пакет и держится аддитивных слотов.
71
+
72
+ ### Почему тема не регистрируется через theme.register
73
+
74
+ `ThemeDefinition` пиннит одну `colorScheme`, поэтому выбор такой темы ломает
75
+ режим «следовать системе». Слой переопределения несёт пару значений на
76
+ каждый токен и переключение схемы переживает сам. Вдобавок поставочная
77
+ строка «Оформление» знает ровно три значения — `appearance.light`,
78
+ `appearance.dark`, `appearance.system` — и отрисовать стороннюю тему не
79
+ может. Регистрация темы была бы регрессом, а не улучшением.
80
+
81
+ ## Установка одной командой
82
+
83
+ ```powershell
84
+ git clone https://github.com/tayyarg07/tensorgrid-ui.git
85
+ cd tensorgrid-ui
86
+ .\install.ps1
87
+ ```
88
+
89
+ Установщик проверит Node.js и dsh (поставит, если нет), создаст профиль,
90
+ разложит пакет, патч-слой и агент-пресет, заведёт автозапуск и проверит,
91
+ что всё на месте. Тем же файлом обновляют: он повторно запускаемый и
92
+ чужого не трогает — настройки, ключи и историю сессий не переписывает.
93
+
94
+ | Ключ | Что делает |
95
+ |---|---|
96
+ | `-DryRun` | показать план, ничего не меняя |
97
+ | `-NoAutostart` | не заводить автозапуск при входе в Windows |
98
+ | `-ProfileId <имя>` | другой идентификатор профиля и пресета |
99
+
100
+ Ниже — то же самое вручную, если нужно понимать каждый шаг.
101
+
102
+ ## Что у меня стоит и не отстало ли
103
+
104
+ ```powershell
105
+ .\status.ps1
106
+ ```
107
+
108
+ Ничего не меняет, только смотрит. Отвечает на четыре вопроса, которые
109
+ иначе выясняются перепиской: версия dsh и есть ли новее, версия продукта и
110
+ не отстала ли от репозитория, все ли части на месте, работает ли
111
+ приложение прямо сейчас и на том ли профиле.
112
+
113
+ Ключ `-Offline` пропускает обращение к npm.
114
+
115
+ ### Про метку `latest` в npm
116
+
117
+ Она указывает на `0.1.5-rc.1`, тогда как `0.1.5-rc.2` опубликован позже, —
118
+ обычное дело для предрелизов. Поэтому установщик ставит **закреплённую**
119
+ версию из `env/snapshot.json`, а не `latest`: иначе сотрудник получил бы
120
+ версию старше той, на которой продукт проверялся.
121
+
122
+ `status.ps1` показывает обе и подсвечивает расхождение.
123
+
124
+ ### Обновления приходят с двух сторон
125
+
126
+ | Откуда | Что | Как применяется |
127
+ |---|---|---|
128
+ | **от вас** | новый коммит в репозитории | `git pull` + `install.ps1` |
129
+ | **от DeepSeek** | новая версия dsh в npm | `npm install -g "@deepseek-ai/dsh@X"` |
130
+
131
+ Каналы независимы, но не безобидны по отдельности. Опасен **второй без
132
+ первого**: пакет опирается на контракты dsh — имена слотов, методы
133
+ сервисов, пространства имён перевода. Если обновить dsh, не переснимая
134
+ снимок и не прогоняя тест, поломка обнаружится у сотрудника.
135
+
136
+ Поэтому **обновление dsh должно приходить через вас**: вы проверяете новую
137
+ версию и выпускаете версию продукта с отметкой «проверено на такой-то».
138
+ Установщик ставит закреплённую версию из `env/snapshot.json`, а не
139
+ `latest`, — сотрудник не получит непроверенный dsh случайно.
140
+
141
+ ### Порядок выпуска
142
+
143
+ Когда вышла новая версия dsh:
144
+
145
+ ```powershell
146
+ npm install -g "@deepseek-ai/dsh@<новая>" # 1. поставить у себя
147
+ .\verify.ps1 # 2. прогнать всю цепочку
148
+ git diff env/snapshot.json # 3. посмотреть, что изменилось
149
+ # 4. починить, если что-то отвалилось
150
+ git commit -am "проверено на dsh <новая>" # 5. зафиксировать
151
+ git push # 6. выпустить
152
+ ```
153
+
154
+ `verify.ps1` — единственная проверка, которую нужно помнить перед
155
+ выпуском. Она прогоняет всё в правильном порядке и **останавливается на
156
+ первом провале**: сверяет версию dsh со снимком, переснимает окружение,
157
+ обновляет эталон строк, проверяет связность перевода, пересобирает
158
+ словари, гоняет 143 проверки контракта и смотрит, всё ли зафиксировано.
159
+
160
+ Порядок шагов не случаен: снимок снимается **до** проверки контракта,
161
+ потому что тест сверяется именно с ним.
162
+
163
+ ### Обновление без новой версии dsh
164
+
165
+ Автоматического обновления нет ни у dsh, ни у продукта, и никто не
166
+ уведомляет о новых версиях — это проверено, а не предположено.
167
+
168
+ ```powershell
169
+ git pull # забрать новую версию продукта
170
+ .\install.ps1 # применить; профиль и настройки не трогаются
171
+ ```
172
+
173
+ Обновление самого dsh — отдельно и вручную:
174
+
175
+ ```powershell
176
+ npm install -g "@deepseek-ai/dsh@<версия>"
177
+ ```
178
+
179
+ После него стоит переснять снимок окружения и посмотреть, что изменилось
180
+ (см. раздел «После обновления dsh»).
181
+
182
+ ## Установка вручную
183
+
184
+ Единица приложения — **профиль**, а не пакет. Профиль задаёт, какие бандлы
185
+ составляют сборку, и добавляет поверх них свои строки. Поставочный профиль
186
+ `web` при этом не трогается и остаётся запасным вариантом.
187
+
188
+ Всё лежит в `~/.dsh/`, который не перезаписывается обновлением dsh —
189
+ в отличие от каталога установки `node_modules/@deepseek-ai/dsh`.
190
+
191
+ 1. Создать профиль из поставочного шаблона. Флаг `--dump-config` заставляет
192
+ команду создать профиль и выйти, не поднимая сервер:
193
+
194
+ ```
195
+ dsh --profile tensorgrid --from-default-profile web --dump-config
196
+ ```
197
+
198
+ 2. Скопировать пакет (каталоги разработки в рантайме не нужны):
199
+
200
+ ```powershell
201
+ robocopy <источник> "$env:USERPROFILE\.dsh\profiles\tensorgrid\packages\dsh-ui-obsidian-ion" `
202
+ /MIR /XD locale test tools /NFL /NDL /NJH /NJS
203
+ ```
204
+
205
+ 3. Положить патч-слой профиля — готовый файл лежит в репозитории:
206
+
207
+ ```
208
+ profile/cordis.patch.yml → ~/.dsh/profiles/tensorgrid/cordis.patch.yml
209
+ ```
210
+
211
+ Его содержимое:
212
+
213
+ ```yaml
214
+ - insert:
215
+ - id: ui-obsidian-ion
216
+ name: './packages/dsh-ui-obsidian-ion/lib/index.js'
217
+ ```
218
+
219
+ Указывать нужно **файл host-половины, а не каталог**: спецификатор
220
+ разрешается штатным ESM-резолвом, а тот не умеет импортировать каталог.
221
+ Прецедент такого подключения есть и в поставке — профиль `claude`
222
+ монтирует локальный пакет строкой
223
+ `name: './packages/dsh-claude-subscription/index.mjs'`.
224
+
225
+ 4. Положить агент-пресет:
226
+
227
+ ```
228
+ preset/ → ~/.dsh/.agent-presets/tensorgrid/
229
+ ```
230
+
231
+ Профиль задаёт **приложение** — какие бандлы его составляют. Пресет
232
+ задаёт **агента** — его персону, набор инструментов и делегирование.
233
+ Это две разные плоскости, и живут они в разных местах.
234
+
235
+ 5. Запустить:
236
+
237
+ ```
238
+ dsh --profile tensorgrid
239
+ ```
240
+
241
+ Пресет выбирается в интерфейсе, в настройках агента: в списке появится
242
+ «TENSOR GRID» рядом с поставочными.
243
+
244
+ ### Проверка установки
245
+
246
+ Профиль можно поднять на свободном порту, не мешая уже запущенному:
247
+
248
+ ```
249
+ dsh --profile tensorgrid --port 3081 --no-open
250
+ ```
251
+
252
+ Команда напечатает адрес с токеном. Две проверки по нему:
253
+
254
+ - `GET /favicon.svg` обязан вернуть SVG с `aria-label="TENSOR GRID"` —
255
+ значит host-половина заняла маршрут;
256
+ - в HTML страницы (с токеном) обязано встречаться `dsh-ui-obsidian-ion` —
257
+ значит пакет попал в загрузочный граф `__DSH_BOOT__` и клиентская
258
+ половина будет загружена.
259
+
260
+ ### Откат
261
+
262
+ `dsh web` поднимает стоковый интерфейс. Удаление продукта — удалить каталог
263
+ `~/.dsh/profiles/tensorgrid`.
264
+
265
+ ## Что проверено, а что нет
266
+
267
+ **Проверено по живому рантайму:**
268
+
269
+ - контракт `window.__ModuleLoader__.load({ id, factory })` и экспорт
270
+ `apply`/`inject` — снято с `@deepseek-ai/dsh-client-ui-brand-official`;
271
+ - способ вставки CSS тегом `<style data-plugin-css>` — снят с
272
+ `@deepseek-ai/dsh-client-ui-layout`;
273
+ - имена и требования всех 13 токенов — через `Theme.listTokens`;
274
+ - контракт слота `shell.overlay` (list, `replaceRisk: none`) — через
275
+ `Slots.listSubTree`;
276
+ - сам дизайн работает: он уже запущен как динамический Package `obsid-1/pkg-2`.
277
+
278
+ - **обнаружение пакета** — прослежено по коду
279
+ `@deepseek-ai/dsh-client-modules` (`lib/index.js`, `locatePkgJson` /
280
+ `nearestPackage` / `resolveMeta`). Цепочка для нашей строки:
281
+
282
+ 1. спецификатор начинается с `.` → считается путём;
283
+ 2. резолвится через ту же ESM-резолюцию, что импортировала host-половину;
284
+ 3. `nearestPackage()` идёт вверх от файла до ближайшего `package.json`.
285
+ Имя заранее не ожидается, поэтому подходит **любое** — пакету не нужно
286
+ быть разрешимым в `node_modules`, workspace и `dependencies` не нужны;
287
+ 4. `dsh.client.platform` должен быть ровно `"web"`;
288
+ 5. `exports["./client"]` принимается и строкой, и объектом с `default`;
289
+ 6. путь бандла = `dirname(package.json) + exports["./client"]`.
290
+
291
+ - **исполнение бандла** — `node test/smoke.mjs`, 47 проверок. Секция про
292
+ манифест проверена негативным контролем: при `platform: "node"` и снятом
293
+ `exports["./client"]` тест валится на четырёх проверках.
294
+
295
+ **Не проверено запуском:**
296
+
297
+ - фактическая отрисовка в браузере.
298
+
299
+ ### Почему загрузку нельзя проверить со стороны Host
300
+
301
+ Хотелось бы спросить рантайм «применились ли мои токены», но такого ответа
302
+ нет:
303
+
304
+ - маршрут `/plugins/<id>/client.js` требует токен авторизации, снаружи
305
+ отдаёт 401/404;
306
+ - `theme.getTheme()` возвращает `{ preference, fontSize, active, themes,
307
+ revision }`, где `active.tokens` — карта токенов *определения темы*
308
+ (у встроенных тем пустая). Переопределения из `overrideTokens` лежат
309
+ отдельным слоем, который снимок не публикует;
310
+ - динамический клиентский плагин по устройству песочницы не имеет ни
311
+ `document`, ни `window`, поэтому прочитать вычисленную CSS-переменную
312
+ или найти вставленный тег стилей нельзя.
313
+
314
+ Что проверяется со стороны Host — это регистрация: сервис `clientModules`,
315
+ метод `clientPath(id)`. Если он возвращает путь к `lib/client.js` внутри
316
+ профиля, значит манифест найден и принят. Остальное показывает только
317
+ перезагрузка страницы.
318
+
319
+ ## Агент-пресет
320
+
321
+ `preset/` — копия поставочного `standard`, сделанная штатной службой
322
+ `agentPresets`. Поставочные пресеты править нельзя: их затирает обновление
323
+ dsh, а порча `cordis` отключила бы саму возможность авторства пресетов.
324
+
325
+ Отличий от поставки немного — 47 добавленных строк и 3 убранных:
326
+
327
+ - **persona** брендирована и задаёт язык ответа;
328
+ - **делегирование в Codex и Claude Code** снабжено инструкцией включения.
329
+
330
+ Всё остальное дословно как в поставке — чтобы при обновлении dsh diff
331
+ показал ровно то, что изменили авторы, и это было легко перенести.
332
+
333
+ ### Переключение харнесса
334
+
335
+ Агент может отдавать задачу наружу — в Codex или Claude Code, которые
336
+ работают под капотом как отдельные продукты. Интерфейс, история сессий и
337
+ брендинг остаются нашими.
338
+
339
+ Заменить **главный** цикл агента так нельзя: `agent-loop` регистрирует одну
340
+ фабрику и падает на второй. Это делегирование, а не подмена.
341
+
342
+ Включается в таком порядке:
343
+
344
+ ```
345
+ dsh plugin --profile tensorgrid add @deepseek-ai/dsh-subagent-codex
346
+ dsh plugin --profile tensorgrid add @deepseek-ai/dsh-subagent-claude-code
347
+ ```
348
+
349
+ затем перезапуск dsh, затем снять `disabled: true` с нужной строки в
350
+ `preset/agent.cordis.yml`, затем авторизоваться в самом продукте —
351
+ установка бандла аккаунт не заводит.
352
+
353
+ Порядок не случаен: наличие провайдера на Host само по себе прав не даёт, а
354
+ снятый `disabled` без установленного бандла уронит ряд.
355
+
356
+ ### Проверка пресета
357
+
358
+ Композиция проверяется до запуска сессии — через `agentPresets.resolve()` и
359
+ `standingKeyFor()`. Если пресет сломан, они скажут об этом сразу, а не
360
+ молчаливым отсутствием инструментов в сессии.
361
+
362
+ ## Эксплуатация
363
+
364
+ ### Запуск и перезапуск
365
+
366
+ ```
367
+ dsh web
368
+ ```
369
+
370
+ Перезапуск — закрыть окно (или `Ctrl+C`) и запустить снова. Автозапуск при
371
+ входе в Windows настроен файлом `DeepSeek Harness.cmd` в папке
372
+ `shell:startup`; чтобы отключить — удалить его.
373
+
374
+ | Команда | Что делает |
375
+ |---|---|
376
+ | `dsh --profile web --dump-config` | показать собранное дерево плагинов |
377
+ | `dsh plugin --profile web add <пакет>` | поставить плагин в профиль |
378
+ | `dsh --version` | версия |
379
+
380
+ ### После обновления dsh
381
+
382
+ Переустанавливать ничего не нужно: `npm update -g @deepseek-ai/dsh` меняет
383
+ только каталог установки, а пакет и строка композиции лежат в `~/.dsh/`.
384
+ При первом же запуске всё смонтируется само.
385
+
386
+ Но контракты, на которые опирается пакет, могли сдвинуться. Проверка:
387
+
388
+ ```
389
+ cd C:\тест\dsh-ui-obsidian-ion
390
+ node tools/extract-locales.mjs # обновить эталон, увидеть новые ключи
391
+ node tools/audit-ru.mjs # связность перевода
392
+ node tools/build-ru.mjs # пересобрать словари
393
+ node test/smoke.mjs # 139 проверок контракта
394
+ ```
395
+
396
+ Новые строки интерфейса, которых нет в русском словаре, покажутся
397
+ по-английски — это штатный фолбэк, а не поломка.
398
+
399
+ ### Синхронизация в профиль
400
+
401
+ ```powershell
402
+ robocopy C:\тест\dsh-ui-obsidian-ion `
403
+ "$env:USERPROFILE\.dsh\profiles\web\packages\dsh-ui-obsidian-ion" `
404
+ /MIR /XD locale test tools /NFL /NDL /NJH /NJS
405
+ ```
406
+
407
+ Каталоги `locale`, `test` и `tools` в рантайме не нужны и исключены.
408
+ Правка `lib/client.js` подхватывается обновлением страницы; правка
409
+ `lib/index.js` (host-половина) требует перезапуска `dsh` — модуль кешируется.
410
+
411
+ ## Цикл правок
412
+
413
+ Источник истины — копия в рабочем каталоге; в профиле лежит рабочая копия.
414
+
415
+ 1. Править `lib/client.js` здесь.
416
+ 2. Прогнать `node test/smoke.mjs`. Файл уезжает в браузер как есть — ни
417
+ сборщик, ни тайпчекер его не смотрят, поэтому опечатка ломает загрузку
418
+ молча. Тест исполняет бандл в Node с подставными `window`, `document` и
419
+ `require` и проверяет: регистрацию в `__ModuleLoader__`, экспорт
420
+ `apply`/`inject`, вставку тега стилей, все 13 токенов со светлой и тёмной
421
+ парой, занятие слота `shell.overlay`, гашение `pointer-events` на слоях,
422
+ совпадение имён классов в разметке и CSS, определённость всех
423
+ `@keyframes` и отсутствие неиспользуемых.
424
+ 3. Скопировать каталог в профиль (каталог `test/` в рантайме не нужен).
425
+ 4. Обновить страницу. Путь к бандлу кэшируется, содержимое — нет: граф
426
+ пересобирается на загрузке страницы и отдаёт свежий файл.
427
+
428
+ Перезапуск профиля не нужен даже при правке `cordis.patch.yml`: у профиля
429
+ `web` выставлен `patchReload: live`.
430
+
431
+ ## Откат
432
+
433
+ ### Вернуть предыдущую установку
434
+
435
+ ```powershell
436
+ .\rollback.ps1 -List # какие копии есть
437
+ .\rollback.ps1 # вернуть самую свежую
438
+ .\rollback.ps1 -To 2026-09-14_22-32-41
439
+ ```
440
+
441
+ `install.ps1` перед каждой перезаписью складывает текущее состояние в
442
+ `~/.dsh/profiles/<id>/.backup/<дата>/` — пакет, патч-слой, пресет и отметку
443
+ об установке. Хранятся три последние копии.
444
+
445
+ **Git для этого не нужен.** Если обновление сломало интерфейс, чинить надо
446
+ быстро, а не разбираться с репозиторием; у сотрудника git может быть вовсе
447
+ не настроен.
448
+
449
+ Откат возвращает ровно три вещи: пакет, патч-слой и пресет. Настройки,
450
+ ключи и история сессий не трогаются — они не входят в установку и от неё
451
+ сломаться не могли.
452
+
453
+ **Версию dsh откат не меняет**: она глобальная и общая для всех профилей,
454
+ включая стоковый `web`. Скрипт лишь сообщит, если сохранённая копия
455
+ ставилась на другой версии, и подскажет команду.
456
+
457
+ ### Выключить продукт целиком
458
+
459
+ Убрать строку из `cordis.patch.yml` — перезапуск не нужен. Оба эффекта
460
+ (токены и слой) сняты диспозерами, следов не остаётся.
461
+
462
+ Стоковый интерфейс всегда доступен командой `dsh web`.
463
+
464
+ ### Удалить полностью
465
+
466
+ Удалить каталог профиля `~/.dsh/profiles/<id>` и каталог пресета
467
+ `~/.dsh/.agent-presets/<id>`.
468
+
469
+ ## Почему здесь нет mix-blend-mode
470
+
471
+ Первая версия полагалась на `mix-blend-mode: screen`, рассчитывая, что
472
+ свечение сложится с фоном приложения и само погаснет на светлой теме. Это
473
+ не работает: контейнер слота `shell.overlay` объявлен с `z-index: 20`,
474
+ то есть создаёт изолированный контекст наложения, и смешивание происходит
475
+ не с приложением, а с прозрачным фоном самого контейнера.
476
+
477
+ Поэтому режимы наложения убраны как мёртвый код, а различие тем берётся из
478
+ штатного контракта самой темы — атрибута `data-ds-dark-theme` на `body`.
479
+ Его выставляет bootstrap-скрипт `ui-theme` ещё до монтирования плагинов и
480
+ дальше поддерживает `ThemePresenter` из `ui-layout`. Базовые правила в
481
+ `client.js` рассчитаны на светлую тему и сдержанны; селекторы
482
+ `body[data-ds-dark-theme] …` поднимают интенсивность для тёмной.
483
+
484
+ Это единственная опора на DOM приложения, и она сознательная: атрибут —
485
+ объявленный интерфейс темы, а не внутреннее имя класса, которое сменится
486
+ при следующей сборке.