noteconnection 1.6.0 → 1.6.2

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
@@ -1,4 +1,4 @@
1
- # 2026-03-04 v1.5.13
1
+ # 2026-03-24 v1.6.0
2
2
 
3
3
  # NoteConnection Knowledge Graph
4
4
 
@@ -279,9 +279,26 @@ npm start -- --path "E:/Knowledge/ObsidianVault" --no-gpu
279
279
  Managing your knowledge base source is now easier than ever.
280
280
 
281
281
  - **First Run Setup**: On first launch, you will be prompted to select your `Knowledge_Base` folder.
282
- - **Persistent Config**: Your selection is saved in `kb_config.json` and remembered across restarts.
282
+ - **Persistent Config (`app_config.toml`)**: Your KB path, language, and multi-window preferences are saved in `%LOCALAPPDATA%/NoteConnection/app_config.toml` (Windows default) and remembered across restarts.
283
+ - **Legacy Auto-Migration**: If a legacy `kb_config.json` exists in the same config directory, NoteConnection automatically migrates it to `app_config.toml`.
283
284
  - **Change Anytime**: Use the **File > Change Knowledge Base...** menu option to switch folders instantly.
284
285
  - **Reset**: Use **File > Reset to Default** to return to the bundled demo notes.
286
+ - **Config Path Overrides**: Set `NOTE_CONNECTION_CONFIG_PATH` (full file path) or `NOTE_CONNECTION_CONFIG_DIR` (directory) to customize where `app_config.toml` is stored.
287
+ - **Window Behavior Tuning**: Edit `[multi_window]` in `app_config.toml` (`single_window_mode`, `hide_tauri_when_pathmode_opens`, `restore_tauri_when_pathmode_exits`, `confirm_before_full_shutdown_from_godot`, `sync_language`).
288
+ - **Detailed Config Guide**: See [`docs/en/app_config.toml_guide.md`](docs/en/app_config.toml_guide.md) and template [`docs/examples/app_config.template.toml`](docs/examples/app_config.template.toml).
289
+
290
+ ```toml
291
+ # Minimal recommended app_config.toml
292
+ knowledge_base_path = "E:/Knowledge_project/NoteConnection_app/Knowledge_Base"
293
+ user_language = "en"
294
+
295
+ [multi_window]
296
+ single_window_mode = true
297
+ hide_tauri_when_pathmode_opens = true
298
+ restore_tauri_when_pathmode_exits = true
299
+ confirm_before_full_shutdown_from_godot = true
300
+ sync_language = true
301
+ ```
285
302
 
286
303
  ## 🏗️ Build & Deployment
287
304
 
@@ -294,6 +311,16 @@ For developers building from source, NoteConnection offers two build modes:
294
311
  - **GPU Dev Start (`npm run tauri:dev:mini:gpu`)**: Recommended GPU-enabled Tauri development command.
295
312
  - **Do not use** `npm run tauri:dev:mini --gpu` because npm treats `--gpu` as config and prints warnings.
296
313
 
314
+ ## 📚 Documentation Architecture (Diataxis + MkDocs)
315
+
316
+ - Canonical long-form docs remain under `docs/en/*` and `docs/zh/*`.
317
+ - Diataxis navigation pages are maintained under `docs/diataxis/<lang>/*`.
318
+ - Mapping governance is versioned in `docs/diataxis-map.json`.
319
+ - Run mapping validation: `npm run docs:diataxis:check`.
320
+ - Run local docs site preview: `npm run docs:site:serve`.
321
+ - Build static docs site: `npm run docs:site:build`.
322
+ - CI policy gate for docs mapping and site build: `.github/workflows/docs-diataxis-site.yml`.
323
+
297
324
  ## 🛠️ Hardware & Driver Requirements (AMDGPU)
298
325
 
299
326
  For optimal performance with "GPU Optimised Rendering", especially on AMD RDNA cards (like RX 7900XT):
@@ -309,6 +336,11 @@ For optimal performance with "GPU Optimised Rendering", especially on AMD RDNA c
309
336
 
310
337
  ### v1.6.0 - Unified Runtime, NoteMD Integration & Release Hardening (2026-03-23)
311
338
 
339
+ - **Tag Compare Snapshot (`v1.3.0..v1.6.0`)**:
340
+ - `107` commits, `301` files changed, `+125,957 / -10,083` churn.
341
+ - File-level status: `241` added, `56` modified, `3` deleted, `1` renamed.
342
+ - Largest engineering footprint: `src/`, `docs/`, `scripts/`, `path_mode/`, `src-tauri/`.
343
+
312
344
  - **Single-Window Runtime Orchestration**:
313
345
  - Implemented Tauri <-> Godot visibility handoff so only one primary window is shown at a time.
314
346
  - Added Godot close-confirm flow ("Return to main interface" vs "Close all windows") to prevent accidental full shutdown.
@@ -323,6 +355,7 @@ For optimal performance with "GPU Optimised Rendering", especially on AMD RDNA c
323
355
  - **Reliability & Security Gates**:
324
356
  - Expanded CI/workflow coverage for FixRisk operational readiness, mobile e2e contracts, wasm parity, SBOM, attestation, and signature/privacy checks.
325
357
  - Added broad contract-level regression coverage across mobile/runtime/pathbridge/storage layers.
358
+ - Included pre-release CI compatibility fixes for runtime bridge invoke-contract assertions and unsigned SBOM transparency policy handling.
326
359
  - **Build Performance & Developer Experience**:
327
360
  - Added low-memory Tauri build wrappers and release-profile safeguards for constrained environments.
328
361
  - Added sidecar readiness preflight to skip redundant rebuilds during dev startup, reducing warm `tauri:dev:mini:gpu` startup latency.
@@ -854,7 +887,7 @@ For optimal performance with "GPU Optimised Rendering", especially on AMD RDNA c
854
887
  ## 中文文档
855
888
 
856
889
 
857
- # 2026-03-04 v1.5.13
890
+ # 2026-03-24 v1.6.0
858
891
  # NoteConnection: 层级知识图谱可视化系统
859
892
 
860
893
  <img width="606" height="309" alt="banner" src="https://github.com/user-attachments/assets/92e90de5-2b1a-4398-8e8b-6e142c92b6a2" />
@@ -1120,9 +1153,26 @@ npm start -- --path "E:/Knowledge/ObsidianVault" --no-gpu
1120
1153
  管理知识库源现在变得更加简单。
1121
1154
 
1122
1155
  - **首次运行设置**: 首次启动时,系统会提示您选择 `Knowledge_Base` 文件夹。
1123
- - **持久化配置**: 您的选择保存在 `kb_config.json` 中,并在重启后自动加载。
1156
+ - **持久化配置 (`app_config.toml`)**: KB 路径、语言及多窗口偏好默认保存到 `%LOCALAPPDATA%/NoteConnection/app_config.toml`(Windows),重启后自动恢复。
1157
+ - **旧配置自动迁移**: 若同目录存在旧版 `kb_config.json`,启动时会自动迁移到 `app_config.toml`。
1124
1158
  - **随时更改**: 使用 **文件 > 更改知识库...** 菜单选项即时切换文件夹。
1125
1159
  - **重置**: 使用 **文件 > 重置为默认** 返回由捆绑的演示笔记。
1160
+ - **配置路径覆盖**: 可通过 `NOTE_CONNECTION_CONFIG_PATH`(完整文件路径)或 `NOTE_CONNECTION_CONFIG_DIR`(目录)自定义 `app_config.toml` 位置。
1161
+ - **窗口行为可调**: 在 `app_config.toml` 的 `[multi_window]` 段调整 `single_window_mode`、`hide_tauri_when_pathmode_opens`、`restore_tauri_when_pathmode_exits`、`confirm_before_full_shutdown_from_godot`、`sync_language`。
1162
+ - **详细配置说明**: 参见 [`docs/zh/app_config.toml_guide.md`](docs/zh/app_config.toml_guide.md) 与模板 [`docs/examples/app_config.template.toml`](docs/examples/app_config.template.toml)。
1163
+
1164
+ ```toml
1165
+ # 推荐最小 app_config.toml
1166
+ knowledge_base_path = "E:/Knowledge_project/NoteConnection_app/Knowledge_Base"
1167
+ user_language = "en"
1168
+
1169
+ [multi_window]
1170
+ single_window_mode = true
1171
+ hide_tauri_when_pathmode_opens = true
1172
+ restore_tauri_when_pathmode_exits = true
1173
+ confirm_before_full_shutdown_from_godot = true
1174
+ sync_language = true
1175
+ ```
1126
1176
 
1127
1177
  ## 🏗️ 构建与部署 (Build & Deployment)
1128
1178
 
@@ -1135,6 +1185,16 @@ npm start -- --path "E:/Knowledge/ObsidianVault" --no-gpu
1135
1185
  - **GPU 开发启动(推荐)** (`npm run tauri:dev:mini:gpu`)。
1136
1186
  - **不要使用** `npm run tauri:dev:mini --gpu`,该写法会被 npm 当作配置参数并触发告警。
1137
1187
 
1188
+ ## 📚 文档架构(Diataxis + MkDocs)
1189
+
1190
+ - 权威长文档仍保持在 `docs/en/*` 与 `docs/zh/*`。
1191
+ - Diataxis 导航页维护在 `docs/diataxis/<lang>/*`。
1192
+ - 映射治理文件为 `docs/diataxis-map.json`。
1193
+ - 映射一致性校验:`npm run docs:diataxis:check`。
1194
+ - 本地预览文档站点:`npm run docs:site:serve`。
1195
+ - 构建静态文档站点:`npm run docs:site:build`。
1196
+ - CI 文档治理工作流:`.github/workflows/docs-diataxis-site.yml`。
1197
+
1138
1198
  ---
1139
1199
 
1140
1200
  <a id="changelog-zh"></a>
@@ -1143,6 +1203,11 @@ npm start -- --path "E:/Knowledge/ObsidianVault" --no-gpu
1143
1203
 
1144
1204
  ### v1.6.0 - 单窗口运行时、NoteMD 集成与发布加固 (2026-03-23)
1145
1205
 
1206
+ - **Tag 对比快照(`v1.3.0..v1.6.0`)**:
1207
+ - `107` 个提交、`301` 个变更文件、`+125,957 / -10,083` 代码/文档变更量。
1208
+ - 文件状态分布:新增 `241`、修改 `56`、删除 `3`、重命名 `1`。
1209
+ - 主要工程变更面集中在:`src/`、`docs/`、`scripts/`、`path_mode/`、`src-tauri/`。
1210
+
1146
1211
  - **单窗口运行时编排**:
1147
1212
  - 实现 Tauri <-> Godot 的可见性切换,同一时刻仅显示一个主窗口。
1148
1213
  - 增加 Godot 关闭确认流程(“返回主界面” / “关闭全部窗口”),避免误操作导致全局退出。
@@ -1157,6 +1222,7 @@ npm start -- --path "E:/Knowledge/ObsidianVault" --no-gpu
1157
1222
  - **可靠性与安全门禁**:
1158
1223
  - 扩展 CI/工作流:FixRisk 运维就绪、移动端 e2e 合约、wasm parity、SBOM、attestation、签名与隐私清单校验。
1159
1224
  - 新增多层合约回归覆盖(mobile/runtime/pathbridge/storage)。
1225
+ - 纳入发布前 CI 兼容修复:runtime bridge invoke 契约断言兼容与无签名 SBOM transparency 条件化策略。
1160
1226
  - **构建性能与开发体验**:
1161
1227
  - 增加低内存 Tauri 构建包装器与 release 配置保护,提升受限内存环境可构建性。
1162
1228
  - 增加 sidecar 预检,避免开发期重复重建,缩短 `tauri:dev:mini:gpu` 热启动耗时。
@@ -180,6 +180,7 @@ const ALLOWED_CONFIG_STRATEGY_VALUES = new Set(['foundational', 'core']);
180
180
  const ALLOWED_CONFIG_LAYOUT_VALUES = new Set(['vertical', 'horizontal', 'radial', 'orbital']);
181
181
  const ALLOWED_READING_MODE_VALUES = new Set(['window', 'fullscreen']);
182
182
  const ALLOWED_READER_RENDER_MODE_VALUES = new Set(['render', 'source']);
183
+ const ALLOWED_CONFIG_LANGUAGE_VALUES = new Set(['en', 'zh']);
183
184
  const ALLOWED_BACKGROUND_FILE_EXTENSIONS = ['.exr', '.hdr'];
184
185
  const CONFIG_TARGET_ID_MAX_LENGTH = 512;
185
186
  const CONFIG_SHORTCUT_MAX_LENGTH = 64;
@@ -195,6 +196,7 @@ const ALLOWED_CONFIG_KEYS = new Set([
195
196
  'mode',
196
197
  'strategy',
197
198
  'layout',
199
+ 'language',
198
200
  'targetId',
199
201
  'target_id',
200
202
  'targetIds',
@@ -345,6 +347,15 @@ function validateConfigurePayload(payload, policy) {
345
347
  return `configure payload.layout must be one of: ${Array.from(ALLOWED_CONFIG_LAYOUT_VALUES).join(', ')}.`;
346
348
  }
347
349
  }
350
+ if (payload.language !== undefined) {
351
+ if (!isNonEmptyString(payload.language)) {
352
+ return 'configure payload.language must be a non-empty string when provided.';
353
+ }
354
+ const normalizedLanguage = payload.language.trim().toLowerCase();
355
+ if (!ALLOWED_CONFIG_LANGUAGE_VALUES.has(normalizedLanguage)) {
356
+ return `configure payload.language must be one of: ${Array.from(ALLOWED_CONFIG_LANGUAGE_VALUES).join(', ')}.`;
357
+ }
358
+ }
348
359
  if (payload.targetId !== undefined && typeof payload.targetId !== 'string') {
349
360
  return 'configure payload.targetId must be a string when provided.';
350
361
  }
@@ -1,4 +1,4 @@
1
- # 2026-03-04 v1.5.13
1
+ # 2026-03-24 v1.6.0
2
2
 
3
3
  # NoteConnection Knowledge Graph
4
4
 
@@ -279,9 +279,26 @@ npm start -- --path "E:/Knowledge/ObsidianVault" --no-gpu
279
279
  Managing your knowledge base source is now easier than ever.
280
280
 
281
281
  - **First Run Setup**: On first launch, you will be prompted to select your `Knowledge_Base` folder.
282
- - **Persistent Config**: Your selection is saved in `kb_config.json` and remembered across restarts.
282
+ - **Persistent Config (`app_config.toml`)**: Your KB path, language, and multi-window preferences are saved in `%LOCALAPPDATA%/NoteConnection/app_config.toml` (Windows default) and remembered across restarts.
283
+ - **Legacy Auto-Migration**: If a legacy `kb_config.json` exists in the same config directory, NoteConnection automatically migrates it to `app_config.toml`.
283
284
  - **Change Anytime**: Use the **File > Change Knowledge Base...** menu option to switch folders instantly.
284
285
  - **Reset**: Use **File > Reset to Default** to return to the bundled demo notes.
286
+ - **Config Path Overrides**: Set `NOTE_CONNECTION_CONFIG_PATH` (full file path) or `NOTE_CONNECTION_CONFIG_DIR` (directory) to customize where `app_config.toml` is stored.
287
+ - **Window Behavior Tuning**: Edit `[multi_window]` in `app_config.toml` (`single_window_mode`, `hide_tauri_when_pathmode_opens`, `restore_tauri_when_pathmode_exits`, `confirm_before_full_shutdown_from_godot`, `sync_language`).
288
+ - **Detailed Config Guide**: See [`docs/en/app_config.toml_guide.md`](docs/en/app_config.toml_guide.md) and template [`docs/examples/app_config.template.toml`](docs/examples/app_config.template.toml).
289
+
290
+ ```toml
291
+ # Minimal recommended app_config.toml
292
+ knowledge_base_path = "E:/Knowledge_project/NoteConnection_app/Knowledge_Base"
293
+ user_language = "en"
294
+
295
+ [multi_window]
296
+ single_window_mode = true
297
+ hide_tauri_when_pathmode_opens = true
298
+ restore_tauri_when_pathmode_exits = true
299
+ confirm_before_full_shutdown_from_godot = true
300
+ sync_language = true
301
+ ```
285
302
 
286
303
  ## 🏗️ Build & Deployment
287
304
 
@@ -294,6 +311,16 @@ For developers building from source, NoteConnection offers two build modes:
294
311
  - **GPU Dev Start (`npm run tauri:dev:mini:gpu`)**: Recommended GPU-enabled Tauri development command.
295
312
  - **Do not use** `npm run tauri:dev:mini --gpu` because npm treats `--gpu` as config and prints warnings.
296
313
 
314
+ ## 📚 Documentation Architecture (Diataxis + MkDocs)
315
+
316
+ - Canonical long-form docs remain under `docs/en/*` and `docs/zh/*`.
317
+ - Diataxis navigation pages are maintained under `docs/diataxis/<lang>/*`.
318
+ - Mapping governance is versioned in `docs/diataxis-map.json`.
319
+ - Run mapping validation: `npm run docs:diataxis:check`.
320
+ - Run local docs site preview: `npm run docs:site:serve`.
321
+ - Build static docs site: `npm run docs:site:build`.
322
+ - CI policy gate for docs mapping and site build: `.github/workflows/docs-diataxis-site.yml`.
323
+
297
324
  ## 🛠️ Hardware & Driver Requirements (AMDGPU)
298
325
 
299
326
  For optimal performance with "GPU Optimised Rendering", especially on AMD RDNA cards (like RX 7900XT):
@@ -309,6 +336,11 @@ For optimal performance with "GPU Optimised Rendering", especially on AMD RDNA c
309
336
 
310
337
  ### v1.6.0 - Unified Runtime, NoteMD Integration & Release Hardening (2026-03-23)
311
338
 
339
+ - **Tag Compare Snapshot (`v1.3.0..v1.6.0`)**:
340
+ - `107` commits, `301` files changed, `+125,957 / -10,083` churn.
341
+ - File-level status: `241` added, `56` modified, `3` deleted, `1` renamed.
342
+ - Largest engineering footprint: `src/`, `docs/`, `scripts/`, `path_mode/`, `src-tauri/`.
343
+
312
344
  - **Single-Window Runtime Orchestration**:
313
345
  - Implemented Tauri <-> Godot visibility handoff so only one primary window is shown at a time.
314
346
  - Added Godot close-confirm flow ("Return to main interface" vs "Close all windows") to prevent accidental full shutdown.
@@ -323,6 +355,7 @@ For optimal performance with "GPU Optimised Rendering", especially on AMD RDNA c
323
355
  - **Reliability & Security Gates**:
324
356
  - Expanded CI/workflow coverage for FixRisk operational readiness, mobile e2e contracts, wasm parity, SBOM, attestation, and signature/privacy checks.
325
357
  - Added broad contract-level regression coverage across mobile/runtime/pathbridge/storage layers.
358
+ - Included pre-release CI compatibility fixes for runtime bridge invoke-contract assertions and unsigned SBOM transparency policy handling.
326
359
  - **Build Performance & Developer Experience**:
327
360
  - Added low-memory Tauri build wrappers and release-profile safeguards for constrained environments.
328
361
  - Added sidecar readiness preflight to skip redundant rebuilds during dev startup, reducing warm `tauri:dev:mini:gpu` startup latency.
@@ -854,7 +887,7 @@ For optimal performance with "GPU Optimised Rendering", especially on AMD RDNA c
854
887
  ## 中文文档
855
888
 
856
889
 
857
- # 2026-03-04 v1.5.13
890
+ # 2026-03-24 v1.6.0
858
891
  # NoteConnection: 层级知识图谱可视化系统
859
892
 
860
893
  <img width="606" height="309" alt="banner" src="https://github.com/user-attachments/assets/92e90de5-2b1a-4398-8e8b-6e142c92b6a2" />
@@ -1120,9 +1153,26 @@ npm start -- --path "E:/Knowledge/ObsidianVault" --no-gpu
1120
1153
  管理知识库源现在变得更加简单。
1121
1154
 
1122
1155
  - **首次运行设置**: 首次启动时,系统会提示您选择 `Knowledge_Base` 文件夹。
1123
- - **持久化配置**: 您的选择保存在 `kb_config.json` 中,并在重启后自动加载。
1156
+ - **持久化配置 (`app_config.toml`)**: KB 路径、语言及多窗口偏好默认保存到 `%LOCALAPPDATA%/NoteConnection/app_config.toml`(Windows),重启后自动恢复。
1157
+ - **旧配置自动迁移**: 若同目录存在旧版 `kb_config.json`,启动时会自动迁移到 `app_config.toml`。
1124
1158
  - **随时更改**: 使用 **文件 > 更改知识库...** 菜单选项即时切换文件夹。
1125
1159
  - **重置**: 使用 **文件 > 重置为默认** 返回由捆绑的演示笔记。
1160
+ - **配置路径覆盖**: 可通过 `NOTE_CONNECTION_CONFIG_PATH`(完整文件路径)或 `NOTE_CONNECTION_CONFIG_DIR`(目录)自定义 `app_config.toml` 位置。
1161
+ - **窗口行为可调**: 在 `app_config.toml` 的 `[multi_window]` 段调整 `single_window_mode`、`hide_tauri_when_pathmode_opens`、`restore_tauri_when_pathmode_exits`、`confirm_before_full_shutdown_from_godot`、`sync_language`。
1162
+ - **详细配置说明**: 参见 [`docs/zh/app_config.toml_guide.md`](docs/zh/app_config.toml_guide.md) 与模板 [`docs/examples/app_config.template.toml`](docs/examples/app_config.template.toml)。
1163
+
1164
+ ```toml
1165
+ # 推荐最小 app_config.toml
1166
+ knowledge_base_path = "E:/Knowledge_project/NoteConnection_app/Knowledge_Base"
1167
+ user_language = "en"
1168
+
1169
+ [multi_window]
1170
+ single_window_mode = true
1171
+ hide_tauri_when_pathmode_opens = true
1172
+ restore_tauri_when_pathmode_exits = true
1173
+ confirm_before_full_shutdown_from_godot = true
1174
+ sync_language = true
1175
+ ```
1126
1176
 
1127
1177
  ## 🏗️ 构建与部署 (Build & Deployment)
1128
1178
 
@@ -1135,6 +1185,16 @@ npm start -- --path "E:/Knowledge/ObsidianVault" --no-gpu
1135
1185
  - **GPU 开发启动(推荐)** (`npm run tauri:dev:mini:gpu`)。
1136
1186
  - **不要使用** `npm run tauri:dev:mini --gpu`,该写法会被 npm 当作配置参数并触发告警。
1137
1187
 
1188
+ ## 📚 文档架构(Diataxis + MkDocs)
1189
+
1190
+ - 权威长文档仍保持在 `docs/en/*` 与 `docs/zh/*`。
1191
+ - Diataxis 导航页维护在 `docs/diataxis/<lang>/*`。
1192
+ - 映射治理文件为 `docs/diataxis-map.json`。
1193
+ - 映射一致性校验:`npm run docs:diataxis:check`。
1194
+ - 本地预览文档站点:`npm run docs:site:serve`。
1195
+ - 构建静态文档站点:`npm run docs:site:build`。
1196
+ - CI 文档治理工作流:`.github/workflows/docs-diataxis-site.yml`。
1197
+
1138
1198
  ---
1139
1199
 
1140
1200
  <a id="changelog-zh"></a>
@@ -1143,6 +1203,11 @@ npm start -- --path "E:/Knowledge/ObsidianVault" --no-gpu
1143
1203
 
1144
1204
  ### v1.6.0 - 单窗口运行时、NoteMD 集成与发布加固 (2026-03-23)
1145
1205
 
1206
+ - **Tag 对比快照(`v1.3.0..v1.6.0`)**:
1207
+ - `107` 个提交、`301` 个变更文件、`+125,957 / -10,083` 代码/文档变更量。
1208
+ - 文件状态分布:新增 `241`、修改 `56`、删除 `3`、重命名 `1`。
1209
+ - 主要工程变更面集中在:`src/`、`docs/`、`scripts/`、`path_mode/`、`src-tauri/`。
1210
+
1146
1211
  - **单窗口运行时编排**:
1147
1212
  - 实现 Tauri <-> Godot 的可见性切换,同一时刻仅显示一个主窗口。
1148
1213
  - 增加 Godot 关闭确认流程(“返回主界面” / “关闭全部窗口”),避免误操作导致全局退出。
@@ -1157,6 +1222,7 @@ npm start -- --path "E:/Knowledge/ObsidianVault" --no-gpu
1157
1222
  - **可靠性与安全门禁**:
1158
1223
  - 扩展 CI/工作流:FixRisk 运维就绪、移动端 e2e 合约、wasm parity、SBOM、attestation、签名与隐私清单校验。
1159
1224
  - 新增多层合约回归覆盖(mobile/runtime/pathbridge/storage)。
1225
+ - 纳入发布前 CI 兼容修复:runtime bridge invoke 契约断言兼容与无签名 SBOM transparency 条件化策略。
1160
1226
  - **构建性能与开发体验**:
1161
1227
  - 增加低内存 Tauri 构建包装器与 release 配置保护,提升受限内存环境可构建性。
1162
1228
  - 增加 sidecar 预检,避免开发期重复重建,缩短 `tauri:dev:mini:gpu` 热启动耗时。
@@ -458,6 +458,40 @@ function isGraphA11yZhMode() {
458
458
  return String(window.i18n.currentLanguage).toLowerCase().startsWith('zh');
459
459
  }
460
460
 
461
+ function getRuntimeAppConfig() {
462
+ if (
463
+ window.NoteConnectionRuntime &&
464
+ typeof window.NoteConnectionRuntime.getAppRuntimeConfig === 'function'
465
+ ) {
466
+ return window.NoteConnectionRuntime.getAppRuntimeConfig();
467
+ }
468
+ if (window.__NC_APP_CONFIG && typeof window.__NC_APP_CONFIG === 'object') {
469
+ return window.__NC_APP_CONFIG;
470
+ }
471
+ return null;
472
+ }
473
+
474
+ function resolveRuntimeMultiWindowOptions() {
475
+ const defaults = {
476
+ singleWindowMode: true,
477
+ hideTauriWhenPathmodeOpens: true,
478
+ restoreTauriWhenPathmodeExits: true,
479
+ confirmBeforeFullShutdownFromGodot: true,
480
+ syncLanguage: true
481
+ };
482
+ const config = getRuntimeAppConfig();
483
+ if (!config || typeof config !== 'object' || !config.multiWindow || typeof config.multiWindow !== 'object') {
484
+ return defaults;
485
+ }
486
+ const next = { ...defaults };
487
+ Object.keys(defaults).forEach((key) => {
488
+ if (typeof config.multiWindow[key] === 'boolean') {
489
+ next[key] = config.multiWindow[key];
490
+ }
491
+ });
492
+ return next;
493
+ }
494
+
461
495
  function getGraphRendererMode() {
462
496
  const checked = document.querySelector('input[name="rendererMode"]:checked');
463
497
  if (!checked || (checked.value !== 'svg' && checked.value !== 'canvas')) {
@@ -967,10 +1001,13 @@ document.querySelectorAll('input[name="degreeMode"]').forEach(radio => {
967
1001
  // Localization is now handled by i18n.js
968
1002
  // We just need to listen for changes to update dynamic UI components like Analysis Panel
969
1003
  if (window.i18n) {
970
- window.i18n.onLanguageChange(() => {
1004
+ window.i18n.onLanguageChange((newLang) => {
971
1005
  if (typeof window.updateAnalysisUI === 'function') {
972
1006
  window.updateAnalysisUI();
973
1007
  }
1008
+ if (window.pathApp && typeof window.pathApp.syncLanguageWithBridge === 'function') {
1009
+ window.pathApp.syncLanguageWithBridge(newLang);
1010
+ }
974
1011
  scheduleGraphSemanticA11yRefresh('Language changed');
975
1012
  });
976
1013
  }
@@ -3525,6 +3562,22 @@ if (
3525
3562
  window.__TAURI__.event.listen('notemd-open-request', () => {
3526
3563
  showEmbeddedNoteMD({ source: 'tauri-event' });
3527
3564
  });
3565
+ window.__TAURI__.event.listen('app-language-updated', async (event) => {
3566
+ const language = event && event.payload && typeof event.payload.language === 'string'
3567
+ ? event.payload.language
3568
+ : '';
3569
+ if (!language || !window.i18n || typeof window.i18n.setLanguage !== 'function') {
3570
+ return;
3571
+ }
3572
+ if (window.i18n.currentLanguage === language) {
3573
+ return;
3574
+ }
3575
+ try {
3576
+ await window.i18n.setLanguage(language);
3577
+ } catch (error) {
3578
+ console.warn('[i18n] Failed to apply app-language-updated event payload:', error);
3579
+ }
3580
+ });
3528
3581
  }
3529
3582
 
3530
3583
  if (btnNotemd) {
@@ -3571,8 +3624,10 @@ if (btnPathMode) {
3571
3624
  // We check `nodes` length directly because it is the effective runtime source here.
3572
3625
  const hasData = (typeof nodes !== 'undefined' && nodes.length > 0);
3573
3626
 
3574
- if (!hasData) {
3575
- const msg = (window.i18n && window.i18n.currentLanguage === 'zh') ? "请先加载知识库。" : "Please load a Knowledge Base first.";
3627
+ if (!hasData) {
3628
+ const msg = (window.i18n && typeof window.i18n.t === 'function')
3629
+ ? window.i18n.t('pathMode.loadKbFirst')
3630
+ : 'Please load a Knowledge Base first.';
3576
3631
 
3577
3632
  // Inline Feedback
3578
3633
  let feedbackEl = document.getElementById('path-mode-feedback');
@@ -3600,6 +3655,7 @@ if (btnPathMode) {
3600
3655
  // Check for active selection for Diffusion Learning
3601
3656
  const highlightState = window.highlightManager ? window.highlightManager.getState() : null;
3602
3657
  const selectedNode = (highlightState && highlightState.currentNode) ? highlightState.currentNode : null;
3658
+ const multiWindowOptions = resolveRuntimeMultiWindowOptions();
3603
3659
 
3604
3660
  console.log('[Path Mode] Entering...', selectedNode ? `Target: ${selectedNode.id}` : 'Domain Mode');
3605
3661
 
@@ -3634,14 +3690,19 @@ if (btnPathMode) {
3634
3690
  if (window.__TAURI__ && window.__TAURI__.core && typeof window.__TAURI__.core.invoke === 'function') {
3635
3691
  try {
3636
3692
  // 1. Hide the Tauri window via Rust IPC.
3637
- await window.__TAURI__.core.invoke('toggle_pathmode_window', { showGodot: true });
3638
- // 2. Send setWindowVisible to Godot via PathBridge WebSocket.
3639
- if (window.pathApp && window.pathApp.ws && window.pathApp.ws.readyState === WebSocket.OPEN) {
3640
- window.pathApp.ws.send(JSON.stringify({
3641
- type: 'setWindowVisible',
3642
- payload: { visible: true }
3643
- }));
3644
- }
3693
+ await window.__TAURI__.core.invoke('toggle_pathmode_window', { showGodot: true });
3694
+ // 2. Send setWindowVisible to Godot via PathBridge WebSocket.
3695
+ if (
3696
+ multiWindowOptions.singleWindowMode &&
3697
+ window.pathApp &&
3698
+ window.pathApp.ws &&
3699
+ window.pathApp.ws.readyState === WebSocket.OPEN
3700
+ ) {
3701
+ window.pathApp.ws.send(JSON.stringify({
3702
+ type: 'setWindowVisible',
3703
+ payload: { visible: true }
3704
+ }));
3705
+ }
3645
3706
  console.log('[Path Mode] Single-window toggle: Tauri hidden, Godot shown.');
3646
3707
  } catch (err) {
3647
3708
  console.warn('[Path Mode] toggle_pathmode_window failed:', err);
@@ -211,12 +211,15 @@
211
211
  "controls": "Controls"
212
212
  }
213
213
  },
214
- "notifications": {
215
- "buildSuccess": "Build Success! Reloading interface...",
216
- "buildFailed": "Build failed. Please check your knowledge base path.",
217
- "noData": "No data available. Please load a knowledge base first."
218
- },
219
- "manual": {
214
+ "notifications": {
215
+ "buildSuccess": "Build Success! Reloading interface...",
216
+ "buildFailed": "Build failed. Please check your knowledge base path.",
217
+ "noData": "No data available. Please load a knowledge base first."
218
+ },
219
+ "pathMode": {
220
+ "loadKbFirst": "Please load a Knowledge Base first."
221
+ },
222
+ "manual": {
220
223
  "loading": "Loading documentation...",
221
224
  "error": "Error Loading Documentation",
222
225
  "errorDetail": "Could not load User_Manual.md or README.md.",
@@ -211,12 +211,15 @@
211
211
  "controls": "控制"
212
212
  }
213
213
  },
214
- "notifications": {
215
- "buildSuccess": "构建成功!重新加载界面...",
216
- "buildFailed": "构建失败。请检查您的知识库路径。",
217
- "noData": "无可用数据。请先加载知识库。"
218
- },
219
- "nodes": "节点",
214
+ "notifications": {
215
+ "buildSuccess": "构建成功!重新加载界面...",
216
+ "buildFailed": "构建失败。请检查您的知识库路径。",
217
+ "noData": "无可用数据。请先加载知识库。"
218
+ },
219
+ "pathMode": {
220
+ "loadKbFirst": "请先加载知识库。"
221
+ },
222
+ "nodes": "节点",
220
223
  "edges": "边",
221
224
  "show_all": "显示全部",
222
225
  "show_in": "仅入度",
@@ -34,7 +34,8 @@ window.pathApp = {
34
34
  autoReconstruct: true,
35
35
  retainHistory: true
36
36
  },
37
- bridgeMermaidRenderQueue: Promise.resolve(),
37
+ bridgeLanguageListenerRegistered: false,
38
+ bridgeMermaidRenderQueue: Promise.resolve(),
38
39
  semanticA11yLastSummaryKey: '',
39
40
  semanticA11yLastAnnouncementAt: 0,
40
41
 
@@ -95,10 +96,12 @@ window.pathApp = {
95
96
  this.ws = socket;
96
97
  }
97
98
 
98
- this.ws.onopen = () => {
99
- console.log('[PathApp] Connected to Bridge');
100
- this._sendBridgeMessage('identify', this._getBridgeIdentifyPayload('frontend'));
101
- };
99
+ this.ws.onopen = () => {
100
+ console.log('[PathApp] Connected to Bridge');
101
+ this._sendBridgeMessage('identify', this._getBridgeIdentifyPayload('frontend'));
102
+ this._ensureLanguageSyncListener();
103
+ this.syncLanguageWithBridge();
104
+ };
102
105
  this.ws.onmessage = (e) => {
103
106
  try {
104
107
  const msg = this._parseBridgeIncomingMessage(e.data);
@@ -246,20 +249,24 @@ window.pathApp = {
246
249
  console.warn('[PathApp] Bridge socket error:', err);
247
250
  };
248
251
 
249
- if (hasActiveSocket && this.ws.readyState === WebSocket.OPEN) {
250
- console.log('[PathApp] Reusing existing Bridge socket');
251
- this._sendBridgeMessage('identify', this._getBridgeIdentifyPayload('frontend'));
252
- }
253
- },
254
-
255
- setupWebSocket: function() {
256
- if (!this._supportsSidecarBridge()) {
257
- console.log('[PathApp] Sidecar bridge is disabled for this runtime; skipping setupWebSocket.');
258
- return;
259
- }
252
+ if (hasActiveSocket && this.ws.readyState === WebSocket.OPEN) {
253
+ console.log('[PathApp] Reusing existing Bridge socket');
254
+ this._sendBridgeMessage('identify', this._getBridgeIdentifyPayload('frontend'));
255
+ this._ensureLanguageSyncListener();
256
+ this.syncLanguageWithBridge();
257
+ }
258
+ },
260
259
 
261
- const bridge = (typeof window !== 'undefined') ? window.NoteConnectionRuntime : null;
262
- const waitForRuntime = this._isTauriMode() && bridge && typeof bridge.whenReady === 'function';
260
+ setupWebSocket: function() {
261
+ if (!this._supportsSidecarBridge()) {
262
+ console.log('[PathApp] Sidecar bridge is disabled for this runtime; skipping setupWebSocket.');
263
+ return;
264
+ }
265
+
266
+ this._ensureLanguageSyncListener();
267
+
268
+ const bridge = (typeof window !== 'undefined') ? window.NoteConnectionRuntime : null;
269
+ const waitForRuntime = this._isTauriMode() && bridge && typeof bridge.whenReady === 'function';
263
270
 
264
271
  if (!waitForRuntime) {
265
272
  this._connectBridgeSocket();
@@ -344,14 +351,110 @@ window.pathApp = {
344
351
  return '';
345
352
  },
346
353
 
347
- _getBridgeIdentifyPayload: function(clientTag) {
348
- const payload = { client: clientTag };
349
- const authToken = this._getBridgeAuthToken();
350
- if (authToken) {
351
- payload.token = authToken;
352
- }
353
- return payload;
354
- },
354
+ _getBridgeIdentifyPayload: function(clientTag) {
355
+ const payload = { client: clientTag };
356
+ const authToken = this._getBridgeAuthToken();
357
+ if (authToken) {
358
+ payload.token = authToken;
359
+ }
360
+ return payload;
361
+ },
362
+
363
+ _normalizeLanguageCode: function(rawLanguage) {
364
+ const value = String(rawLanguage || '').trim().toLowerCase();
365
+ return value.startsWith('zh') ? 'zh' : 'en';
366
+ },
367
+
368
+ _getActiveLanguage: function() {
369
+ if (window.i18n && typeof window.i18n.currentLanguage === 'string') {
370
+ return this._normalizeLanguageCode(window.i18n.currentLanguage);
371
+ }
372
+ const languageSelect = document.getElementById('set-language');
373
+ if (languageSelect && typeof languageSelect.value === 'string') {
374
+ return this._normalizeLanguageCode(languageSelect.value);
375
+ }
376
+ const appConfig = this._getAppRuntimeConfig();
377
+ if (appConfig && typeof appConfig.language === 'string') {
378
+ return this._normalizeLanguageCode(appConfig.language);
379
+ }
380
+ return 'en';
381
+ },
382
+
383
+ _getAppRuntimeConfig: function() {
384
+ if (
385
+ typeof window !== 'undefined' &&
386
+ window.NoteConnectionRuntime &&
387
+ typeof window.NoteConnectionRuntime.getAppRuntimeConfig === 'function'
388
+ ) {
389
+ return window.NoteConnectionRuntime.getAppRuntimeConfig();
390
+ }
391
+ if (typeof window !== 'undefined' && window.__NC_APP_CONFIG && typeof window.__NC_APP_CONFIG === 'object') {
392
+ return window.__NC_APP_CONFIG;
393
+ }
394
+ return null;
395
+ },
396
+
397
+ _resolveMultiWindowOptions: function() {
398
+ const defaults = {
399
+ singleWindowMode: true,
400
+ hideTauriWhenPathmodeOpens: true,
401
+ restoreTauriWhenPathmodeExits: true,
402
+ confirmBeforeFullShutdownFromGodot: true,
403
+ syncLanguage: true
404
+ };
405
+ const appConfig = this._getAppRuntimeConfig();
406
+ if (!appConfig || !appConfig.multiWindow || typeof appConfig.multiWindow !== 'object') {
407
+ return defaults;
408
+ }
409
+ return {
410
+ singleWindowMode: typeof appConfig.multiWindow.singleWindowMode === 'boolean'
411
+ ? appConfig.multiWindow.singleWindowMode
412
+ : defaults.singleWindowMode,
413
+ hideTauriWhenPathmodeOpens: typeof appConfig.multiWindow.hideTauriWhenPathmodeOpens === 'boolean'
414
+ ? appConfig.multiWindow.hideTauriWhenPathmodeOpens
415
+ : defaults.hideTauriWhenPathmodeOpens,
416
+ restoreTauriWhenPathmodeExits: typeof appConfig.multiWindow.restoreTauriWhenPathmodeExits === 'boolean'
417
+ ? appConfig.multiWindow.restoreTauriWhenPathmodeExits
418
+ : defaults.restoreTauriWhenPathmodeExits,
419
+ confirmBeforeFullShutdownFromGodot: typeof appConfig.multiWindow.confirmBeforeFullShutdownFromGodot === 'boolean'
420
+ ? appConfig.multiWindow.confirmBeforeFullShutdownFromGodot
421
+ : defaults.confirmBeforeFullShutdownFromGodot,
422
+ syncLanguage: typeof appConfig.multiWindow.syncLanguage === 'boolean'
423
+ ? appConfig.multiWindow.syncLanguage
424
+ : defaults.syncLanguage
425
+ };
426
+ },
427
+
428
+ _buildLanguageConfigurePayload: function(rawLanguage) {
429
+ return {
430
+ language: this._normalizeLanguageCode(rawLanguage || this._getActiveLanguage())
431
+ };
432
+ },
433
+
434
+ syncLanguageWithBridge: function(rawLanguage) {
435
+ const options = this._resolveMultiWindowOptions();
436
+ if (!options.syncLanguage) {
437
+ return false;
438
+ }
439
+ if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
440
+ return false;
441
+ }
442
+ const payload = this._buildLanguageConfigurePayload(rawLanguage);
443
+ return this._sendBridgeMessage('configure', payload);
444
+ },
445
+
446
+ _ensureLanguageSyncListener: function() {
447
+ if (this.bridgeLanguageListenerRegistered) {
448
+ return;
449
+ }
450
+ if (!window.i18n || typeof window.i18n.onLanguageChange !== 'function') {
451
+ return;
452
+ }
453
+ this.bridgeLanguageListenerRegistered = true;
454
+ window.i18n.onLanguageChange((lang) => {
455
+ this.syncLanguageWithBridge(lang);
456
+ });
457
+ },
355
458
 
356
459
  _getRuntimeBridgeAdapter: function() {
357
460
  if (
@@ -1543,40 +1646,60 @@ window.pathApp = {
1543
1646
 
1544
1647
  if (pathContainer) pathContainer.style.display = 'none';
1545
1648
  if (graphWrapper) graphWrapper.style.display = 'block';
1546
- if (sidebar) {
1547
- sidebar.style.transform = 'translateX(100%)';
1548
- sidebar.style.display = 'none';
1549
- }
1649
+ if (sidebar) {
1650
+ sidebar.style.transform = 'translateX(100%)';
1651
+ sidebar.style.display = 'none';
1652
+ }
1653
+ const multiWindowOptions = this._resolveMultiWindowOptions();
1550
1654
 
1551
1655
  // Single-window toggle: hide Godot, show Tauri.
1552
1656
  // 单窗口切换:隐藏 Godot,显示 Tauri。
1553
1657
  if (window.__TAURI__ && window.__TAURI__.core && typeof window.__TAURI__.core.invoke === 'function') {
1554
1658
  // 1. Hide Godot window via PathBridge WebSocket.
1555
- if (this.ws && this.ws.readyState === WebSocket.OPEN) {
1659
+ if (
1660
+ multiWindowOptions.singleWindowMode &&
1661
+ this.ws &&
1662
+ this.ws.readyState === WebSocket.OPEN
1663
+ ) {
1556
1664
  this.ws.send(JSON.stringify({
1557
1665
  type: 'setWindowVisible',
1558
1666
  payload: { visible: false }
1559
1667
  }));
1560
1668
  }
1561
1669
  // 2. Restore Tauri window via Rust IPC.
1562
- window.__TAURI__.core.invoke('toggle_pathmode_window', { showGodot: false })
1563
- .then(() => console.log('[PathApp] Single-window toggle: Godot hidden, Tauri restored.'))
1564
- .catch((err) => console.warn('[PathApp] toggle_pathmode_window restore failed:', err));
1670
+ if (multiWindowOptions.restoreTauriWhenPathmodeExits) {
1671
+ window.__TAURI__.core.invoke('toggle_pathmode_window', { showGodot: false })
1672
+ .then(() => console.log('[PathApp] Single-window toggle: Godot hidden, Tauri restored.'))
1673
+ .catch((err) => console.warn('[PathApp] toggle_pathmode_window restore failed:', err));
1674
+ }
1565
1675
  }
1566
1676
 
1567
- window.dispatchEvent(new Event('resize'));
1568
- },
1677
+ window.dispatchEvent(new Event('resize'));
1678
+ },
1569
1679
 
1570
1680
  applyRemoteConfigure: function(config) {
1571
1681
  if (!config || typeof config !== 'object') return;
1572
1682
 
1573
- const incomingTargetId = typeof config.targetId === 'string'
1574
- ? config.targetId
1575
- : (typeof config.target_id === 'string' ? config.target_id : null);
1576
-
1577
- if (typeof config.mode === 'string') {
1578
- this.runtimeConfig.mode = config.mode === 'diffusion' ? 'diffusion' : 'domain';
1579
- }
1683
+ const incomingTargetId = typeof config.targetId === 'string'
1684
+ ? config.targetId
1685
+ : (typeof config.target_id === 'string' ? config.target_id : null);
1686
+
1687
+ if (typeof config.language === 'string') {
1688
+ const normalizedLanguage = this._normalizeLanguageCode(config.language);
1689
+ if (
1690
+ window.i18n &&
1691
+ typeof window.i18n.setLanguage === 'function' &&
1692
+ window.i18n.currentLanguage !== normalizedLanguage
1693
+ ) {
1694
+ void window.i18n.setLanguage(normalizedLanguage).catch((error) => {
1695
+ console.warn('[PathApp] Failed to apply language from remote configure payload:', error);
1696
+ });
1697
+ }
1698
+ }
1699
+
1700
+ if (typeof config.mode === 'string') {
1701
+ this.runtimeConfig.mode = config.mode === 'diffusion' ? 'diffusion' : 'domain';
1702
+ }
1580
1703
  if (typeof config.strategy === 'string') {
1581
1704
  this.runtimeConfig.strategy = config.strategy === 'core' ? 'core' : 'foundational';
1582
1705
  }
@@ -3056,11 +3179,13 @@ window.pathApp = {
3056
3179
  return;
3057
3180
  }
3058
3181
 
3059
- this.ws.onopen = () => {
3060
- console.log('[PathApp] Early WS Connected to Bridge');
3061
- this._sendBridgeMessage('identify', this._getBridgeIdentifyPayload('frontend-early'));
3062
-
3063
- const initialCentralId = this._getPreferredStandaloneCentralId(preferredCentralId);
3182
+ this.ws.onopen = () => {
3183
+ console.log('[PathApp] Early WS Connected to Bridge');
3184
+ this._sendBridgeMessage('identify', this._getBridgeIdentifyPayload('frontend-early'));
3185
+ this._ensureLanguageSyncListener();
3186
+ this.syncLanguageWithBridge();
3187
+
3188
+ const initialCentralId = this._getPreferredStandaloneCentralId(preferredCentralId);
3064
3189
  if (initialCentralId) {
3065
3190
  this.centralNodeId = initialCentralId;
3066
3191
  this.sendPathToBridgeStandalone(initialCentralId);
@@ -98,6 +98,31 @@
98
98
  return value.replace(/\/+$/, '');
99
99
  }
100
100
 
101
+ function normalizeLanguageCode(rawValue) {
102
+ const value = String(rawValue || '').trim().toLowerCase();
103
+ return value.startsWith('zh') ? 'zh' : 'en';
104
+ }
105
+
106
+ function normalizeBoolean(value, fallback) {
107
+ if (typeof value === 'boolean') {
108
+ return value;
109
+ }
110
+ return fallback;
111
+ }
112
+
113
+ function getDefaultAppRuntimeConfig() {
114
+ return {
115
+ language: 'en',
116
+ multiWindow: {
117
+ singleWindowMode: true,
118
+ hideTauriWhenPathmodeOpens: true,
119
+ restoreTauriWhenPathmodeExits: true,
120
+ confirmBeforeFullShutdownFromGodot: true,
121
+ syncLanguage: true
122
+ }
123
+ };
124
+ }
125
+
101
126
  const state = {
102
127
  baseUrl: normalizeBaseUrl(window.__NC_SIDECAR_RUNTIME && window.__NC_SIDECAR_RUNTIME.baseUrl),
103
128
  bridgeWsUrl: normalizeBridgeWsUrl(window.__NC_SIDECAR_RUNTIME && window.__NC_SIDECAR_RUNTIME.bridgeWsUrl),
@@ -106,6 +131,34 @@
106
131
  port: Number((window.__NC_SIDECAR_RUNTIME && window.__NC_SIDECAR_RUNTIME.port) || 3000),
107
132
  bridgePort: Number((window.__NC_SIDECAR_RUNTIME && window.__NC_SIDECAR_RUNTIME.bridgePort) || 9876)
108
133
  };
134
+ const appState = getDefaultAppRuntimeConfig();
135
+ if (window.__NC_APP_CONFIG && typeof window.__NC_APP_CONFIG === 'object') {
136
+ const bootAppConfig = window.__NC_APP_CONFIG;
137
+ appState.language = normalizeLanguageCode(bootAppConfig.language || appState.language);
138
+ if (bootAppConfig.multiWindow && typeof bootAppConfig.multiWindow === 'object') {
139
+ const bootMultiWindow = bootAppConfig.multiWindow;
140
+ appState.multiWindow.singleWindowMode = normalizeBoolean(
141
+ bootMultiWindow.singleWindowMode,
142
+ appState.multiWindow.singleWindowMode
143
+ );
144
+ appState.multiWindow.hideTauriWhenPathmodeOpens = normalizeBoolean(
145
+ bootMultiWindow.hideTauriWhenPathmodeOpens,
146
+ appState.multiWindow.hideTauriWhenPathmodeOpens
147
+ );
148
+ appState.multiWindow.restoreTauriWhenPathmodeExits = normalizeBoolean(
149
+ bootMultiWindow.restoreTauriWhenPathmodeExits,
150
+ appState.multiWindow.restoreTauriWhenPathmodeExits
151
+ );
152
+ appState.multiWindow.confirmBeforeFullShutdownFromGodot = normalizeBoolean(
153
+ bootMultiWindow.confirmBeforeFullShutdownFromGodot,
154
+ appState.multiWindow.confirmBeforeFullShutdownFromGodot
155
+ );
156
+ appState.multiWindow.syncLanguage = normalizeBoolean(
157
+ bootMultiWindow.syncLanguage,
158
+ appState.multiWindow.syncLanguage
159
+ );
160
+ }
161
+ }
109
162
 
110
163
  let runtimeReadyResolved = false;
111
164
  let runtimeHydrationPromise = null;
@@ -126,6 +179,20 @@
126
179
  return window.__NC_SIDECAR_RUNTIME;
127
180
  }
128
181
 
182
+ function syncGlobalAppState() {
183
+ window.__NC_APP_CONFIG = {
184
+ language: appState.language,
185
+ multiWindow: {
186
+ singleWindowMode: appState.multiWindow.singleWindowMode,
187
+ hideTauriWhenPathmodeOpens: appState.multiWindow.hideTauriWhenPathmodeOpens,
188
+ restoreTauriWhenPathmodeExits: appState.multiWindow.restoreTauriWhenPathmodeExits,
189
+ confirmBeforeFullShutdownFromGodot: appState.multiWindow.confirmBeforeFullShutdownFromGodot,
190
+ syncLanguage: appState.multiWindow.syncLanguage
191
+ }
192
+ };
193
+ return window.__NC_APP_CONFIG;
194
+ }
195
+
129
196
  function finalizeRuntimeReady() {
130
197
  if (runtimeReadyResolved) {
131
198
  return runtimeReadyPromise;
@@ -170,6 +237,44 @@
170
237
  return syncGlobalState();
171
238
  }
172
239
 
240
+ function setAppRuntimeConfig(nextConfig) {
241
+ const merged = getDefaultAppRuntimeConfig();
242
+ if (nextConfig && typeof nextConfig === 'object') {
243
+ merged.language = normalizeLanguageCode(nextConfig.language || merged.language);
244
+ const nextMultiWindow = nextConfig.multiWindow && typeof nextConfig.multiWindow === 'object'
245
+ ? nextConfig.multiWindow
246
+ : {};
247
+ merged.multiWindow.singleWindowMode = normalizeBoolean(
248
+ nextMultiWindow.singleWindowMode,
249
+ merged.multiWindow.singleWindowMode
250
+ );
251
+ merged.multiWindow.hideTauriWhenPathmodeOpens = normalizeBoolean(
252
+ nextMultiWindow.hideTauriWhenPathmodeOpens,
253
+ merged.multiWindow.hideTauriWhenPathmodeOpens
254
+ );
255
+ merged.multiWindow.restoreTauriWhenPathmodeExits = normalizeBoolean(
256
+ nextMultiWindow.restoreTauriWhenPathmodeExits,
257
+ merged.multiWindow.restoreTauriWhenPathmodeExits
258
+ );
259
+ merged.multiWindow.confirmBeforeFullShutdownFromGodot = normalizeBoolean(
260
+ nextMultiWindow.confirmBeforeFullShutdownFromGodot,
261
+ merged.multiWindow.confirmBeforeFullShutdownFromGodot
262
+ );
263
+ merged.multiWindow.syncLanguage = normalizeBoolean(
264
+ nextMultiWindow.syncLanguage,
265
+ merged.multiWindow.syncLanguage
266
+ );
267
+ }
268
+
269
+ appState.language = merged.language;
270
+ appState.multiWindow = merged.multiWindow;
271
+ return syncGlobalAppState();
272
+ }
273
+
274
+ function getAppRuntimeConfig() {
275
+ return syncGlobalAppState();
276
+ }
277
+
173
278
  function buildUrl(resourcePath, query) {
174
279
  const normalizedPath = String(resourcePath || '').replace(/^\/+/, '');
175
280
  const url = new URL(normalizedPath, `${state.baseUrl}/`);
@@ -292,6 +397,11 @@
292
397
  };
293
398
  }
294
399
 
400
+ } catch (error) {
401
+ console.warn('[RuntimeBridge] Failed to hydrate runtime capabilities from Tauri. Using runtime bridge defaults.', error);
402
+ }
403
+
404
+ try {
295
405
  const runtimeCaps = window.__NC_RUNTIME_CAPS || {};
296
406
  if (runtimeCaps.supports_sidecar) {
297
407
  const runtimeConfig = await invokeTauriWithTimeout(
@@ -305,11 +415,23 @@
305
415
  console.warn('[RuntimeBridge] Failed to hydrate sidecar runtime config from Tauri. Using runtime bridge defaults.', error);
306
416
  }
307
417
 
418
+ try {
419
+ const appConfig = await invokeTauriWithTimeout(
420
+ () => invoke('get_app_runtime_config'),
421
+ 'get_app_runtime_config',
422
+ TAURI_RUNTIME_HYDRATE_TIMEOUT_MS
423
+ );
424
+ setAppRuntimeConfig(appConfig);
425
+ } catch (error) {
426
+ console.warn('[RuntimeBridge] Failed to hydrate app runtime config from Tauri. Using runtime defaults.', error);
427
+ }
428
+
308
429
  if (typeof window.dispatchEvent === 'function' && typeof window.CustomEvent === 'function') {
309
430
  window.dispatchEvent(new CustomEvent('noteconnection:runtime-ready', {
310
431
  detail: {
311
432
  runtime: syncGlobalState(),
312
- caps: window.__NC_RUNTIME_CAPS || null
433
+ caps: window.__NC_RUNTIME_CAPS || null,
434
+ appConfig: syncGlobalAppState()
313
435
  }
314
436
  }));
315
437
  }
@@ -330,6 +452,8 @@
330
452
  window.NoteConnectionRuntime = {
331
453
  setRuntimeConfig,
332
454
  getRuntimeConfig,
455
+ setAppRuntimeConfig,
456
+ getAppRuntimeConfig,
333
457
  buildUrl,
334
458
  buildFetchOptions,
335
459
  createAuthHeaders,
@@ -349,6 +473,7 @@
349
473
  };
350
474
 
351
475
  syncGlobalState();
476
+ syncGlobalAppState();
352
477
 
353
478
  if (document.readyState === 'loading') {
354
479
  document.addEventListener('DOMContentLoaded', () => {
@@ -41,6 +41,15 @@ const child_process_1 = require("child_process");
41
41
  function readJson(filePath) {
42
42
  return JSON.parse(fs.readFileSync(filePath, 'utf8'));
43
43
  }
44
+ function buildUnsignedGeneratorEnv() {
45
+ return {
46
+ ...process.env,
47
+ NOTE_CONNECTION_SBOM_SIGNING_PRIVATE_KEY_PEM: '',
48
+ NOTE_CONNECTION_SBOM_SIGNING_PRIVATE_KEY_FILE: '',
49
+ NOTE_CONNECTION_SBOM_ATTESTATION_ENABLE_TRANSPARENCY_LOG: '0',
50
+ NOTE_CONNECTION_SBOM_ATTESTATION_TRANSPARENCY_LOG_PATH: '',
51
+ };
52
+ }
44
53
  describe('sbom attestation policy contract', () => {
45
54
  const repoRoot = path.resolve(__dirname, '..');
46
55
  const packageJsonPath = path.join(repoRoot, 'package.json');
@@ -48,6 +57,33 @@ describe('sbom attestation policy contract', () => {
48
57
  const verifierPath = path.join(repoRoot, 'scripts', 'verify-sbom-attestation.js');
49
58
  const migrationWorkflowPath = path.join(repoRoot, '.github', 'workflows', 'migration-gates.yml');
50
59
  const npmPublishWorkflowPath = path.join(repoRoot, '.github', 'workflows', 'npm-publish.yml');
60
+ let noteConnectionEnvSnapshot;
61
+ beforeEach(() => {
62
+ // Keep contract tests hermetic even when CI injects NOTE_CONNECTION_* policy env vars.
63
+ noteConnectionEnvSnapshot = {};
64
+ for (const key of Object.keys(process.env)) {
65
+ if (!key.startsWith('NOTE_CONNECTION_')) {
66
+ continue;
67
+ }
68
+ noteConnectionEnvSnapshot[key] = process.env[key];
69
+ delete process.env[key];
70
+ }
71
+ });
72
+ afterEach(() => {
73
+ for (const key of Object.keys(process.env)) {
74
+ if (key.startsWith('NOTE_CONNECTION_')) {
75
+ delete process.env[key];
76
+ }
77
+ }
78
+ for (const [key, value] of Object.entries(noteConnectionEnvSnapshot || {})) {
79
+ if (typeof value === 'undefined') {
80
+ delete process.env[key];
81
+ }
82
+ else {
83
+ process.env[key] = value;
84
+ }
85
+ }
86
+ });
51
87
  test('exports attestation generation/verification scripts and gate wiring', () => {
52
88
  const packageJson = readJson(packageJsonPath);
53
89
  const scripts = packageJson.scripts || {};
@@ -124,6 +160,7 @@ describe('sbom attestation policy contract', () => {
124
160
  expect(npmPublishWorkflow).toContain('NOTE_CONNECTION_SBOM_ATTESTATION_VERIFY_TRANSPARENCY_LOG_INCLUSION');
125
161
  expect(npmPublishWorkflow).toContain('NOTE_CONNECTION_SBOM_ATTESTATION_TRANSPARENCY_EXPECT_SCHEMA');
126
162
  expect(npmPublishWorkflow).toContain('NOTE_CONNECTION_SBOM_ATTESTATION_TRANSPARENCY_EXPECT_VERSION');
163
+ expect(npmPublishWorkflow).toContain('npx jest --runInBand');
127
164
  });
128
165
  test('attestation generator and verifier work in unsigned strict policy mode', () => {
129
166
  const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'noteconnection-sbom-attestation-'));
@@ -142,6 +179,7 @@ describe('sbom attestation policy contract', () => {
142
179
  cwd: repoRoot,
143
180
  encoding: 'utf8',
144
181
  stdio: 'pipe',
182
+ env: buildUnsignedGeneratorEnv(),
145
183
  });
146
184
  expect(generateResult.status).toBe(0);
147
185
  const verifyResult = (0, child_process_1.spawnSync)(process.execPath, [
@@ -183,6 +221,7 @@ describe('sbom attestation policy contract', () => {
183
221
  cwd: repoRoot,
184
222
  encoding: 'utf8',
185
223
  stdio: 'pipe',
224
+ env: buildUnsignedGeneratorEnv(),
186
225
  });
187
226
  expect(generateResult.status).toBe(0);
188
227
  const verifyResult = (0, child_process_1.spawnSync)(process.execPath, [
@@ -232,6 +271,7 @@ describe('sbom attestation policy contract', () => {
232
271
  cwd: repoRoot,
233
272
  encoding: 'utf8',
234
273
  stdio: 'pipe',
274
+ env: buildUnsignedGeneratorEnv(),
235
275
  });
236
276
  expect(generateResult.status).toBe(0);
237
277
  const verifyResult = (0, child_process_1.spawnSync)(process.execPath, [
@@ -275,6 +315,7 @@ describe('sbom attestation policy contract', () => {
275
315
  cwd: repoRoot,
276
316
  encoding: 'utf8',
277
317
  stdio: 'pipe',
318
+ env: buildUnsignedGeneratorEnv(),
278
319
  });
279
320
  expect(generateResult.status).toBe(0);
280
321
  const verifyResult = (0, child_process_1.spawnSync)(process.execPath, [
@@ -243,6 +243,7 @@ describe('server migration settings routes', () => {
243
243
  let buildGraphMock;
244
244
  let renderMathPngMock;
245
245
  let renderMermaidPngMock;
246
+ let copyPngToClipboardMock;
246
247
  let originalArgv;
247
248
  beforeAll(async () => {
248
249
  temp = new TempDir('noteconnection-server');
@@ -283,6 +284,7 @@ describe('server migration settings routes', () => {
283
284
  width: 640,
284
285
  height: 360
285
286
  });
287
+ copyPngToClipboardMock = jest.fn().mockResolvedValue(undefined);
286
288
  jest.resetModules();
287
289
  originalArgv = [...process.argv];
288
290
  process.argv = process.argv.slice(0, 2);
@@ -296,6 +298,9 @@ describe('server migration settings routes', () => {
296
298
  renderMathPng: renderMathPngMock,
297
299
  renderMermaidPng: renderMermaidPngMock
298
300
  }));
301
+ jest.doMock('./native_clipboard', () => ({
302
+ copyPngToClipboard: copyPngToClipboardMock
303
+ }));
299
304
  const serverModule = require('./server');
300
305
  server = await serverModule.startServer({ port });
301
306
  buildGraphMock.mockClear();
@@ -319,6 +324,7 @@ describe('server migration settings routes', () => {
319
324
  jest.dontMock('./index');
320
325
  jest.dontMock('./core/PathBridge');
321
326
  jest.dontMock('./reader_renderer');
327
+ jest.dontMock('./native_clipboard');
322
328
  process.argv = originalArgv;
323
329
  temp.cleanup();
324
330
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "noteconnection",
3
- "version": "1.6.0",
3
+ "version": "1.6.2",
4
4
  "description": "Hierarchical Knowledge Graph Visualization System",
5
5
  "main": "dist/src/server.js",
6
6
  "bin": {
@@ -10,12 +10,12 @@
10
10
  "start": "node scripts/start-server.js",
11
11
  "start:prod": "node dist/src/server.js",
12
12
  "tauri": "tauri",
13
- "tauri:dev": "npm run build && npm run ensure:sidecar:dev && npm run cleanup:tauri:sidecars && npx kill-port 3000 && npx tauri dev",
14
- "tauri:dev:mini": "npm run build:mini && npm run ensure:sidecar:dev && npm run cleanup:tauri:sidecars && npx kill-port 3000 && npx tauri dev",
13
+ "tauri:dev": "npm run build && npm run ensure:sidecar:dev && npm run cleanup:tauri:sidecars && npx kill-port 3000 && npx tauri dev",
14
+ "tauri:dev:mini": "npm run build:mini && npm run ensure:sidecar:dev && npm run cleanup:tauri:sidecars && npx kill-port 3000 && npx tauri dev",
15
15
  "tauri:dev:gpu": "set NOTE_CONNECTION_GPU=1&& npm run tauri:dev",
16
16
  "tauri:dev:mini:gpu": "set NOTE_CONNECTION_GPU=1&& npm run tauri:dev:mini",
17
- "tauri:build": "npm run build && npm run build:sidecar && npm run cleanup:tauri:sidecars && node scripts/run-tauri-build.js",
18
- "tauri:build:mini": "npm run build:mini && npm run build:sidecar && npm run cleanup:tauri:sidecars && node scripts/run-tauri-build.js",
17
+ "tauri:build": "npm run build && npm run build:sidecar && npm run cleanup:tauri:sidecars && node scripts/run-tauri-build.js",
18
+ "tauri:build:mini": "npm run build:mini && npm run build:sidecar && npm run cleanup:tauri:sidecars && node scripts/run-tauri-build.js",
19
19
  "verify:android:env": "node scripts/verify-tauri-android-prereqs.js",
20
20
  "verify:capacitor:device": "node scripts/verify-capacitor-device-acceptance.js",
21
21
  "capture:capacitor:evidence": "node scripts/capture-capacitor-device-evidence.js",
@@ -33,9 +33,9 @@
33
33
  "build:mini": "node scripts/copy-reader-runtime-assets.js && tsc && node scripts/bundle_path_core.js && node scripts/copy-assets.js --mini && node scripts/sync-wasm-parity-artifact.js",
34
34
  "prepare:godot:bin": "node scripts/ensure-godot-sidecar.js",
35
35
  "verify:tauri:bin": "node scripts/validate-tauri-sidecars.js",
36
- "build:sidecar": "node scripts/copy-reader-runtime-assets.js && tsc && node scripts/build-sidecar.js && npm run prepare:godot:bin && npm run verify:tauri:bin",
37
- "build:sidecar:all": "node scripts/copy-reader-runtime-assets.js && tsc && node scripts/build-sidecar.js --all && npm run prepare:godot:bin && node scripts/validate-tauri-sidecars.js --all",
38
- "ensure:sidecar:dev": "node scripts/ensure-sidecar-ready.js",
36
+ "build:sidecar": "node scripts/copy-reader-runtime-assets.js && tsc && node scripts/build-sidecar.js && npm run prepare:godot:bin && npm run verify:tauri:bin",
37
+ "build:sidecar:all": "node scripts/copy-reader-runtime-assets.js && tsc && node scripts/build-sidecar.js --all && npm run prepare:godot:bin && node scripts/validate-tauri-sidecars.js --all",
38
+ "ensure:sidecar:dev": "node scripts/ensure-sidecar-ready.js",
39
39
  "pathmode:dev": "node -r ts-node/register src/server.ts --pathmode",
40
40
  "pathmode:test": "jest --testPathPatterns=Path",
41
41
  "smoke:sidecar:relaunch": "node scripts/smoke-sidecar-relaunch.js",
@@ -51,28 +51,31 @@
51
51
  "benchmark:wasm:parity:history:release": "node scripts/benchmark-wasm-parity.js --require-wasm-adapter 1 --history-window 90 --minimum-history-samples 5 --history-strict-samples 15 --history-maturity-fail-tier enforced --history-performance-fail-mode always --history-max-records 3000 --history-max-age-days 180 --max-candidate-to-history-graph-p95-ratio 1.25 --max-candidate-to-history-layout-p95-ratio 1.25 --max-candidate-to-history-graph-p99-ratio 1.25 --max-candidate-to-history-layout-p99-ratio 1.25",
52
52
  "calibrate:graphmetrics:tiering": "node scripts/calibrate-graphmetrics-tiering.js",
53
53
  "test:wasm:parity:gates": "npm run verify:wasm:parity:strict && npm run benchmark:wasm:parity:strict:perf",
54
- "verify:detox:pipeline": "node scripts/verify-detox-pipeline.js",
55
- "verify:fixrisk:issues": "node scripts/verify-fixrisk-issues.js",
56
- "verify:fixrisk:issues:strict": "node scripts/verify-fixrisk-issues.js --strict-pending",
57
- "verify:fixrisk:issues:strict:evidence": "node scripts/verify-fixrisk-issues.js --strict-pending --require-evidence-root",
58
- "verify:pathbridge:strict": "node scripts/verify-pathbridge-strict-schema.js",
59
- "generate:sbom": "node scripts/generate-sbom.js",
60
- "generate:sbom:attestation": "node scripts/generate-sbom-attestation.js",
61
- "verify:sbom": "node scripts/verify-sbom-policy.js",
62
- "verify:sbom:attestation": "node scripts/verify-sbom-attestation.js",
63
- "test:e2e:detox": "node scripts/run-detox-e2e.js",
64
- "test:e2e:detox:run": "node scripts/run-detox-e2e.js --run",
65
- "verify:privacy:manifest": "node scripts/verify-privacy-manifest.js",
66
- "verify:sidecar:signatures": "node scripts/verify-sidecar-signatures.js",
67
- "ops:fixrisk:close": "node scripts/run-fixrisk-ops-closure.js",
68
- "ops:fixrisk:close:dry": "node scripts/run-fixrisk-ops-closure.js --dry-run",
69
- "test:mobile:contracts": "jest src/mobile.pipeline.test.ts src/runtime.capabilities.test.ts src/source_manager.loadflow.test.ts src/capacitor.runtime.contract.test.ts src/android.pathmode.contract.test.ts src/android.pathmode.smoke.contract.test.ts src/graph.accessibility.contract.test.ts src/detox.pipeline.contract.test.ts src/privacy.manifest.contract.test.ts --runInBand && npm run verify:detox:pipeline && npm run verify:privacy:manifest",
70
- "test:migration": "jest src/core/Graph.test.ts src/core/PathEngine.test.ts src/core/TreeLayout.test.ts src/backend/algorithms/CycleDetection.test.ts src/backend/algorithms/TopologicalSort.test.ts src/backend/algorithms/WasmParityHistory.test.ts src/utils/RuntimePaths.test.ts src/server.migration.test.ts src/pkg.sidecar.contract.test.ts src/pkg.snapshot.safety.contract.test.ts src/mobile.pipeline.test.ts src/capacitor.device.utils.contract.test.ts src/capacitor.evidence.contract.test.ts src/runtime.capabilities.test.ts src/runtime.heap.policy.contract.test.ts src/runtime.spool.policy.contract.test.ts src/runtime.transport.adapter.contract.test.ts src/storage.provider.contract.test.ts src/storage.provider.capacitor.content.contract.test.ts src/storage.provider.capacitor.worker.contract.test.ts src/wasm.parity.runtime.contract.test.ts src/wasm.parity.runtime.functional.test.ts src/wasm.parity.output.equivalence.contract.test.ts src/wasm.parity.benchmark.contract.test.ts src/wasm.parity.benchmark.guards.contract.test.ts src/wasm.parity.history.gate.contract.test.ts src/wasm.parity.artifact.probe.contract.test.ts src/wasm.parity.artifact.provisioning.contract.test.ts src/source_manager.loadflow.test.ts src/capacitor.runtime.contract.test.ts src/welcome.loadflow.test.ts src/pathmode.history.contract.test.ts src/android.pathmode.contract.test.ts src/android.pathmode.smoke.contract.test.ts src/pathbridge.handshake.contract.test.ts src/pathbridge.strict.policy.contract.test.ts src/graph.accessibility.contract.test.ts src/detox.pipeline.contract.test.ts src/privacy.manifest.contract.test.ts src/server.port.fallback.contract.test.ts src/sidecar.signature.contract.test.ts src/sbom.policy.contract.test.ts src/sbom.attestation.policy.contract.test.ts src/sidecar.relaunch.contract.test.ts src/tauri.test.runner.contract.test.ts --runInBand",
71
- "test:tauri": "node scripts/run-tauri-tests.js",
72
- "test:gates": "npm run test:migration && npm run test:wasm:parity:gates && npm run test:tauri && npm run verify:android:env && npm run verify:detox:pipeline && npm run verify:privacy:manifest && npm run verify:pathbridge:strict && npm run verify:sbom -- --contract-only && npm run verify:sbom:attestation -- --contract-only && npm run verify:sidecar:signatures -- --contract-only",
73
- "prepublishOnly": "npm run build:mini",
54
+ "verify:detox:pipeline": "node scripts/verify-detox-pipeline.js",
55
+ "verify:fixrisk:issues": "node scripts/verify-fixrisk-issues.js",
56
+ "verify:fixrisk:issues:strict": "node scripts/verify-fixrisk-issues.js --strict-pending",
57
+ "verify:fixrisk:issues:strict:evidence": "node scripts/verify-fixrisk-issues.js --strict-pending --require-evidence-root",
58
+ "verify:pathbridge:strict": "node scripts/verify-pathbridge-strict-schema.js",
59
+ "generate:sbom": "node scripts/generate-sbom.js",
60
+ "generate:sbom:attestation": "node scripts/generate-sbom-attestation.js",
61
+ "verify:sbom": "node scripts/verify-sbom-policy.js",
62
+ "verify:sbom:attestation": "node scripts/verify-sbom-attestation.js",
63
+ "test:e2e:detox": "node scripts/run-detox-e2e.js",
64
+ "test:e2e:detox:run": "node scripts/run-detox-e2e.js --run",
65
+ "verify:privacy:manifest": "node scripts/verify-privacy-manifest.js",
66
+ "verify:sidecar:signatures": "node scripts/verify-sidecar-signatures.js",
67
+ "ops:fixrisk:close": "node scripts/run-fixrisk-ops-closure.js",
68
+ "ops:fixrisk:close:dry": "node scripts/run-fixrisk-ops-closure.js --dry-run",
69
+ "test:mobile:contracts": "jest src/mobile.pipeline.test.ts src/runtime.capabilities.test.ts src/source_manager.loadflow.test.ts src/capacitor.runtime.contract.test.ts src/android.pathmode.contract.test.ts src/android.pathmode.smoke.contract.test.ts src/graph.accessibility.contract.test.ts src/detox.pipeline.contract.test.ts src/privacy.manifest.contract.test.ts --runInBand && npm run verify:detox:pipeline && npm run verify:privacy:manifest",
70
+ "test:migration": "jest src/core/Graph.test.ts src/core/PathEngine.test.ts src/core/TreeLayout.test.ts src/backend/algorithms/CycleDetection.test.ts src/backend/algorithms/TopologicalSort.test.ts src/backend/algorithms/WasmParityHistory.test.ts src/utils/RuntimePaths.test.ts src/server.migration.test.ts src/pkg.sidecar.contract.test.ts src/pkg.snapshot.safety.contract.test.ts src/mobile.pipeline.test.ts src/capacitor.device.utils.contract.test.ts src/capacitor.evidence.contract.test.ts src/runtime.capabilities.test.ts src/runtime.heap.policy.contract.test.ts src/runtime.spool.policy.contract.test.ts src/runtime.transport.adapter.contract.test.ts src/storage.provider.contract.test.ts src/storage.provider.capacitor.content.contract.test.ts src/storage.provider.capacitor.worker.contract.test.ts src/wasm.parity.runtime.contract.test.ts src/wasm.parity.runtime.functional.test.ts src/wasm.parity.output.equivalence.contract.test.ts src/wasm.parity.benchmark.contract.test.ts src/wasm.parity.benchmark.guards.contract.test.ts src/wasm.parity.history.gate.contract.test.ts src/wasm.parity.artifact.probe.contract.test.ts src/wasm.parity.artifact.provisioning.contract.test.ts src/source_manager.loadflow.test.ts src/capacitor.runtime.contract.test.ts src/welcome.loadflow.test.ts src/pathmode.history.contract.test.ts src/android.pathmode.contract.test.ts src/android.pathmode.smoke.contract.test.ts src/pathbridge.handshake.contract.test.ts src/pathbridge.strict.policy.contract.test.ts src/graph.accessibility.contract.test.ts src/detox.pipeline.contract.test.ts src/privacy.manifest.contract.test.ts src/server.port.fallback.contract.test.ts src/sidecar.signature.contract.test.ts src/sbom.policy.contract.test.ts src/sbom.attestation.policy.contract.test.ts src/sidecar.relaunch.contract.test.ts src/tauri.test.runner.contract.test.ts --runInBand",
71
+ "test:tauri": "node scripts/run-tauri-tests.js",
72
+ "test:gates": "npm run test:migration && npm run test:wasm:parity:gates && npm run test:tauri && npm run verify:android:env && npm run verify:detox:pipeline && npm run verify:privacy:manifest && npm run verify:pathbridge:strict && npm run verify:sbom -- --contract-only && npm run verify:sbom:attestation -- --contract-only && npm run verify:sidecar:signatures -- --contract-only",
73
+ "prepublishOnly": "npm run build:mini",
74
74
  "test": "jest",
75
- "cleanup:tauri:sidecars": "node scripts/cleanup-tauri-sidecars.js"
75
+ "cleanup:tauri:sidecars": "node scripts/cleanup-tauri-sidecars.js",
76
+ "docs:diataxis:check": "node scripts/verify-diataxis-map.js",
77
+ "docs:site:build": "node scripts/run-mkdocs.js build --config-file mkdocs.yml",
78
+ "docs:site:serve": "node scripts/run-mkdocs.js serve --config-file mkdocs.yml"
76
79
  },
77
80
  "pkg": {
78
81
  "scripts": [
@@ -84,12 +87,12 @@
84
87
  "graph_data.json"
85
88
  ]
86
89
  },
87
- "files": [
88
- "dist/src",
89
- "dist/amdgpu",
90
- "LICENSE",
91
- "README.md"
92
- ],
90
+ "files": [
91
+ "dist/src",
92
+ "dist/amdgpu",
93
+ "LICENSE",
94
+ "README.md"
95
+ ],
93
96
  "keywords": [
94
97
  "knowledge-graph",
95
98
  "visualization",