nlm-memory 0.4.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.
Files changed (472) hide show
  1. package/.agents/plugins/marketplace.json +20 -0
  2. package/.github/workflows/ci.yml +30 -0
  3. package/LICENSE +151 -0
  4. package/README.md +119 -0
  5. package/dist/cli/classify-parity.d.ts +48 -0
  6. package/dist/cli/classify-parity.js +182 -0
  7. package/dist/cli/classify-parity.js.map +1 -0
  8. package/dist/cli/launchctl-helpers.d.ts +26 -0
  9. package/dist/cli/launchctl-helpers.js +42 -0
  10. package/dist/cli/launchctl-helpers.js.map +1 -0
  11. package/dist/cli/nlm.d.ts +25 -0
  12. package/dist/cli/nlm.js +832 -0
  13. package/dist/cli/nlm.js.map +1 -0
  14. package/dist/core/actions/actions-log.d.ts +40 -0
  15. package/dist/core/actions/actions-log.js +72 -0
  16. package/dist/core/actions/actions-log.js.map +1 -0
  17. package/dist/core/actions/overlay.d.ts +30 -0
  18. package/dist/core/actions/overlay.js +101 -0
  19. package/dist/core/actions/overlay.js.map +1 -0
  20. package/dist/core/adapters/aider.d.ts +33 -0
  21. package/dist/core/adapters/aider.js +167 -0
  22. package/dist/core/adapters/aider.js.map +1 -0
  23. package/dist/core/adapters/claude-code.d.ts +32 -0
  24. package/dist/core/adapters/claude-code.js +270 -0
  25. package/dist/core/adapters/claude-code.js.map +1 -0
  26. package/dist/core/adapters/common.d.ts +20 -0
  27. package/dist/core/adapters/common.js +60 -0
  28. package/dist/core/adapters/common.js.map +1 -0
  29. package/dist/core/adapters/from-source.d.ts +11 -0
  30. package/dist/core/adapters/from-source.js +55 -0
  31. package/dist/core/adapters/from-source.js.map +1 -0
  32. package/dist/core/adapters/hermes-agent.d.ts +34 -0
  33. package/dist/core/adapters/hermes-agent.js +192 -0
  34. package/dist/core/adapters/hermes-agent.js.map +1 -0
  35. package/dist/core/adapters/hermes.d.ts +31 -0
  36. package/dist/core/adapters/hermes.js +247 -0
  37. package/dist/core/adapters/hermes.js.map +1 -0
  38. package/dist/core/adapters/jsonl-generic.d.ts +56 -0
  39. package/dist/core/adapters/jsonl-generic.js +185 -0
  40. package/dist/core/adapters/jsonl-generic.js.map +1 -0
  41. package/dist/core/adapters/opencode.d.ts +36 -0
  42. package/dist/core/adapters/opencode.js +213 -0
  43. package/dist/core/adapters/opencode.js.map +1 -0
  44. package/dist/core/adapters/pi.d.ts +32 -0
  45. package/dist/core/adapters/pi.js +233 -0
  46. package/dist/core/adapters/pi.js.map +1 -0
  47. package/dist/core/classifier/prompt.d.ts +60 -0
  48. package/dist/core/classifier/prompt.js +178 -0
  49. package/dist/core/classifier/prompt.js.map +1 -0
  50. package/dist/core/dataset/build-dataset.d.ts +87 -0
  51. package/dist/core/dataset/build-dataset.js +335 -0
  52. package/dist/core/dataset/build-dataset.js.map +1 -0
  53. package/dist/core/embedding/chunk-body.d.ts +30 -0
  54. package/dist/core/embedding/chunk-body.js +60 -0
  55. package/dist/core/embedding/chunk-body.js.map +1 -0
  56. package/dist/core/embedding/embed-backfill.d.ts +36 -0
  57. package/dist/core/embedding/embed-backfill.js +168 -0
  58. package/dist/core/embedding/embed-backfill.js.map +1 -0
  59. package/dist/core/embedding/embed-normalize.d.ts +28 -0
  60. package/dist/core/embedding/embed-normalize.js +98 -0
  61. package/dist/core/embedding/embed-normalize.js.map +1 -0
  62. package/dist/core/facts/backfill-facts.d.ts +58 -0
  63. package/dist/core/facts/backfill-facts.js +169 -0
  64. package/dist/core/facts/backfill-facts.js.map +1 -0
  65. package/dist/core/facts/extract-facts.d.ts +20 -0
  66. package/dist/core/facts/extract-facts.js +37 -0
  67. package/dist/core/facts/extract-facts.js.map +1 -0
  68. package/dist/core/hook/citation-detect.d.ts +32 -0
  69. package/dist/core/hook/citation-detect.js +105 -0
  70. package/dist/core/hook/citation-detect.js.map +1 -0
  71. package/dist/core/hook/cite-memo.d.ts +20 -0
  72. package/dist/core/hook/cite-memo.js +68 -0
  73. package/dist/core/hook/cite-memo.js.map +1 -0
  74. package/dist/core/hook/claude-settings.d.ts +34 -0
  75. package/dist/core/hook/claude-settings.js +117 -0
  76. package/dist/core/hook/claude-settings.js.map +1 -0
  77. package/dist/core/hook/gate.d.ts +11 -0
  78. package/dist/core/hook/gate.js +19 -0
  79. package/dist/core/hook/gate.js.map +1 -0
  80. package/dist/core/hook/hook-log.d.ts +25 -0
  81. package/dist/core/hook/hook-log.js +28 -0
  82. package/dist/core/hook/hook-log.js.map +1 -0
  83. package/dist/core/hook/memo-sweep.d.ts +55 -0
  84. package/dist/core/hook/memo-sweep.js +134 -0
  85. package/dist/core/hook/memo-sweep.js.map +1 -0
  86. package/dist/core/hook/memo.d.ts +20 -0
  87. package/dist/core/hook/memo.js +66 -0
  88. package/dist/core/hook/memo.js.map +1 -0
  89. package/dist/core/hook/pointer-block.d.ts +14 -0
  90. package/dist/core/hook/pointer-block.js +19 -0
  91. package/dist/core/hook/pointer-block.js.map +1 -0
  92. package/dist/core/hook/select.d.ts +21 -0
  93. package/dist/core/hook/select.js +15 -0
  94. package/dist/core/hook/select.js.map +1 -0
  95. package/dist/core/hook/transcript.d.ts +31 -0
  96. package/dist/core/hook/transcript.js +103 -0
  97. package/dist/core/hook/transcript.js.map +1 -0
  98. package/dist/core/ingest/ingest-session.d.ts +40 -0
  99. package/dist/core/ingest/ingest-session.js +71 -0
  100. package/dist/core/ingest/ingest-session.js.map +1 -0
  101. package/dist/core/providers/provider-models.d.ts +24 -0
  102. package/dist/core/providers/provider-models.js +72 -0
  103. package/dist/core/providers/provider-models.js.map +1 -0
  104. package/dist/core/providers/provider-registry.d.ts +62 -0
  105. package/dist/core/providers/provider-registry.js +143 -0
  106. package/dist/core/providers/provider-registry.js.map +1 -0
  107. package/dist/core/recall/citation-log.d.ts +28 -0
  108. package/dist/core/recall/citation-log.js +90 -0
  109. package/dist/core/recall/citation-log.js.map +1 -0
  110. package/dist/core/recall/filter.d.ts +11 -0
  111. package/dist/core/recall/filter.js +20 -0
  112. package/dist/core/recall/filter.js.map +1 -0
  113. package/dist/core/recall/index.d.ts +6 -0
  114. package/dist/core/recall/index.js +5 -0
  115. package/dist/core/recall/index.js.map +1 -0
  116. package/dist/core/recall/match-fields.d.ts +10 -0
  117. package/dist/core/recall/match-fields.js +37 -0
  118. package/dist/core/recall/match-fields.js.map +1 -0
  119. package/dist/core/recall/query-log.d.ts +36 -0
  120. package/dist/core/recall/query-log.js +112 -0
  121. package/dist/core/recall/query-log.js.map +1 -0
  122. package/dist/core/recall/query-shape.d.ts +22 -0
  123. package/dist/core/recall/query-shape.js +64 -0
  124. package/dist/core/recall/query-shape.js.map +1 -0
  125. package/dist/core/recall/recall-service.d.ts +19 -0
  126. package/dist/core/recall/recall-service.js +252 -0
  127. package/dist/core/recall/recall-service.js.map +1 -0
  128. package/dist/core/recall/recent-log.d.ts +16 -0
  129. package/dist/core/recall/recent-log.js +46 -0
  130. package/dist/core/recall/recent-log.js.map +1 -0
  131. package/dist/core/recall/tokenize.d.ts +7 -0
  132. package/dist/core/recall/tokenize.js +18 -0
  133. package/dist/core/recall/tokenize.js.map +1 -0
  134. package/dist/core/recall/useful-scan.d.ts +52 -0
  135. package/dist/core/recall/useful-scan.js +300 -0
  136. package/dist/core/recall/useful-scan.js.map +1 -0
  137. package/dist/core/recall-facts/fact-query-log.d.ts +42 -0
  138. package/dist/core/recall-facts/fact-query-log.js +115 -0
  139. package/dist/core/recall-facts/fact-query-log.js.map +1 -0
  140. package/dist/core/recall-facts/fact-recall-service.d.ts +34 -0
  141. package/dist/core/recall-facts/fact-recall-service.js +246 -0
  142. package/dist/core/recall-facts/fact-recall-service.js.map +1 -0
  143. package/dist/core/scheduler/scan-once.d.ts +32 -0
  144. package/dist/core/scheduler/scan-once.js +100 -0
  145. package/dist/core/scheduler/scan-once.js.map +1 -0
  146. package/dist/core/scheduler/scheduler.d.ts +59 -0
  147. package/dist/core/scheduler/scheduler.js +158 -0
  148. package/dist/core/scheduler/scheduler.js.map +1 -0
  149. package/dist/core/sources/source-registry.d.ts +68 -0
  150. package/dist/core/sources/source-registry.js +208 -0
  151. package/dist/core/sources/source-registry.js.map +1 -0
  152. package/dist/core/storage/db-restore.d.ts +53 -0
  153. package/dist/core/storage/db-restore.js +113 -0
  154. package/dist/core/storage/db-restore.js.map +1 -0
  155. package/dist/core/storage/live-status.d.ts +15 -0
  156. package/dist/core/storage/live-status.js +43 -0
  157. package/dist/core/storage/live-status.js.map +1 -0
  158. package/dist/core/storage/migrate.d.ts +14 -0
  159. package/dist/core/storage/migrate.js +52 -0
  160. package/dist/core/storage/migrate.js.map +1 -0
  161. package/dist/core/storage/sqlite-fact-store.d.ts +50 -0
  162. package/dist/core/storage/sqlite-fact-store.js +256 -0
  163. package/dist/core/storage/sqlite-fact-store.js.map +1 -0
  164. package/dist/core/storage/sqlite-session-store.d.ts +152 -0
  165. package/dist/core/storage/sqlite-session-store.js +587 -0
  166. package/dist/core/storage/sqlite-session-store.js.map +1 -0
  167. package/dist/hook/pre-compact-hook.d.ts +26 -0
  168. package/dist/hook/pre-compact-hook.js +94 -0
  169. package/dist/hook/pre-compact-hook.js.map +1 -0
  170. package/dist/hook/prompt-recall-hook.d.ts +23 -0
  171. package/dist/hook/prompt-recall-hook.js +141 -0
  172. package/dist/hook/prompt-recall-hook.js.map +1 -0
  173. package/dist/hook/session-end-hook.d.ts +18 -0
  174. package/dist/hook/session-end-hook.js +67 -0
  175. package/dist/hook/session-end-hook.js.map +1 -0
  176. package/dist/hook/session-start-hook.d.ts +25 -0
  177. package/dist/hook/session-start-hook.js +129 -0
  178. package/dist/hook/session-start-hook.js.map +1 -0
  179. package/dist/hook/stop-hook.d.ts +38 -0
  180. package/dist/hook/stop-hook.js +171 -0
  181. package/dist/hook/stop-hook.js.map +1 -0
  182. package/dist/hook/subagent-start-hook.d.ts +30 -0
  183. package/dist/hook/subagent-start-hook.js +108 -0
  184. package/dist/hook/subagent-start-hook.js.map +1 -0
  185. package/dist/http/app.d.ts +65 -0
  186. package/dist/http/app.js +1009 -0
  187. package/dist/http/app.js.map +1 -0
  188. package/dist/install/claude-code.d.ts +57 -0
  189. package/dist/install/claude-code.js +76 -0
  190. package/dist/install/claude-code.js.map +1 -0
  191. package/dist/install/codex.d.ts +82 -0
  192. package/dist/install/codex.js +277 -0
  193. package/dist/install/codex.js.map +1 -0
  194. package/dist/install/hermes-agent.d.ts +35 -0
  195. package/dist/install/hermes-agent.js +48 -0
  196. package/dist/install/hermes-agent.js.map +1 -0
  197. package/dist/install/hermes.d.ts +29 -0
  198. package/dist/install/hermes.js +52 -0
  199. package/dist/install/hermes.js.map +1 -0
  200. package/dist/install/ollama.d.ts +54 -0
  201. package/dist/install/ollama.js +178 -0
  202. package/dist/install/ollama.js.map +1 -0
  203. package/dist/install/setup.d.ts +37 -0
  204. package/dist/install/setup.js +339 -0
  205. package/dist/install/setup.js.map +1 -0
  206. package/dist/llm/classifier-box.d.ts +29 -0
  207. package/dist/llm/classifier-box.js +43 -0
  208. package/dist/llm/classifier-box.js.map +1 -0
  209. package/dist/llm/deepseek-client.d.ts +40 -0
  210. package/dist/llm/deepseek-client.js +114 -0
  211. package/dist/llm/deepseek-client.js.map +1 -0
  212. package/dist/llm/env-autoload.d.ts +8 -0
  213. package/dist/llm/env-autoload.js +54 -0
  214. package/dist/llm/env-autoload.js.map +1 -0
  215. package/dist/llm/ollama-client.d.ts +47 -0
  216. package/dist/llm/ollama-client.js +156 -0
  217. package/dist/llm/ollama-client.js.map +1 -0
  218. package/dist/mcp/server.d.ts +64 -0
  219. package/dist/mcp/server.js +430 -0
  220. package/dist/mcp/server.js.map +1 -0
  221. package/dist/ports/fact-store.d.ts +82 -0
  222. package/dist/ports/fact-store.js +16 -0
  223. package/dist/ports/fact-store.js.map +1 -0
  224. package/dist/ports/llm-client.d.ts +42 -0
  225. package/dist/ports/llm-client.js +14 -0
  226. package/dist/ports/llm-client.js.map +1 -0
  227. package/dist/ports/logger.d.ts +13 -0
  228. package/dist/ports/logger.js +8 -0
  229. package/dist/ports/logger.js.map +1 -0
  230. package/dist/ports/session-store.d.ts +29 -0
  231. package/dist/ports/session-store.js +9 -0
  232. package/dist/ports/session-store.js.map +1 -0
  233. package/dist/ports/transcript-adapter.d.ts +48 -0
  234. package/dist/ports/transcript-adapter.js +15 -0
  235. package/dist/ports/transcript-adapter.js.map +1 -0
  236. package/dist/shared/types.d.ts +129 -0
  237. package/dist/shared/types.js +6 -0
  238. package/dist/shared/types.js.map +1 -0
  239. package/dist/ui/assets/index-BA6IpU8g.css +1 -0
  240. package/dist/ui/assets/index-B_qIVV0k.js +69 -0
  241. package/dist/ui/index.html +13 -0
  242. package/docs/methodology/re-derivation-rate.md +112 -0
  243. package/docs/methodology/useful-hit-rate.md +79 -0
  244. package/docs/plans/2026-05-20-fts5-lexical-recall.md +1088 -0
  245. package/docs/plans/2026-05-20-recall-daemon-wedge-fix.md +662 -0
  246. package/docs/plans/2026-05-20-recall-hook-design.md +131 -0
  247. package/docs/plans/2026-05-20-recall-hook-implementation.md +1222 -0
  248. package/docs/plans/desktop-product.md +69 -0
  249. package/docs/plans/factstore-design.md +236 -0
  250. package/logs/CHANGELOG/CHANGELOG-2026.md +1389 -0
  251. package/logs/CHANGELOG/CHANGELOG.md +320 -0
  252. package/migrations/000_initial_schema.sql +174 -0
  253. package/migrations/001_entity_type_rename.sql +17 -0
  254. package/migrations/002_adapter_state_extend.sql +12 -0
  255. package/migrations/003_session_embeddings.sql +11 -0
  256. package/migrations/004_facts.sql +46 -0
  257. package/migrations/005_sources.sql +31 -0
  258. package/migrations/006_providers.sql +33 -0
  259. package/migrations/007_source_tokens.sql +17 -0
  260. package/migrations/008_fts_rebuild.sql +9 -0
  261. package/migrations/009_session_embedding_chunks.sql +46 -0
  262. package/migrations/010_sources_opencode.sql +30 -0
  263. package/migrations/011_sources_hermes_agent.sql +30 -0
  264. package/migrations/012_sources_aider.sql +30 -0
  265. package/migrations/013_adapter_state_failure_count.sql +12 -0
  266. package/package.json +56 -0
  267. package/plugin/.codex-plugin/plugin.json +22 -0
  268. package/plugin/.mcp.json +8 -0
  269. package/plugin/README.md +51 -0
  270. package/plugin/hooks/hooks.json +25 -0
  271. package/plugin/scripts/prompt-recall-hook.mjs +202 -0
  272. package/plugin/scripts/stop-hook.mjs +306 -0
  273. package/plugin-hermes-agent/README.md +49 -0
  274. package/plugin-hermes-agent/__init__.py +75 -0
  275. package/plugin-hermes-agent/plugin.yaml +15 -0
  276. package/scripts/backfill-citations.mjs +0 -0
  277. package/scripts/build-codex-plugin.mjs +61 -0
  278. package/scripts/deepseek-probe.mjs +67 -0
  279. package/scripts/extract-triples.mjs +207 -0
  280. package/scripts/longmemeval/embedding-cache.ts +77 -0
  281. package/scripts/longmemeval/fetch-dataset.sh +25 -0
  282. package/scripts/longmemeval/run-harness.ts +315 -0
  283. package/scripts/longmemeval/scorer.ts +99 -0
  284. package/scripts/longmemeval/tsconfig.json +9 -0
  285. package/scripts/longmemeval/types.ts +35 -0
  286. package/scripts/nlm-daily-digest.py +239 -0
  287. package/scripts/nlm-daily-digest.sh +28 -0
  288. package/src/cli/classify-parity.ts +257 -0
  289. package/src/cli/launchctl-helpers.ts +49 -0
  290. package/src/cli/nlm.ts +885 -0
  291. package/src/core/actions/actions-log.ts +118 -0
  292. package/src/core/actions/overlay.ts +117 -0
  293. package/src/core/adapters/aider.ts +205 -0
  294. package/src/core/adapters/claude-code.ts +293 -0
  295. package/src/core/adapters/common.ts +54 -0
  296. package/src/core/adapters/from-source.ts +57 -0
  297. package/src/core/adapters/hermes-agent.ts +240 -0
  298. package/src/core/adapters/hermes.ts +277 -0
  299. package/src/core/adapters/jsonl-generic.ts +208 -0
  300. package/src/core/adapters/opencode.ts +281 -0
  301. package/src/core/adapters/pi.ts +264 -0
  302. package/src/core/classifier/prompt.ts +200 -0
  303. package/src/core/dataset/build-dataset.ts +463 -0
  304. package/src/core/embedding/chunk-body.ts +76 -0
  305. package/src/core/embedding/embed-backfill.ts +210 -0
  306. package/src/core/embedding/embed-normalize.ts +135 -0
  307. package/src/core/facts/backfill-facts.ts +254 -0
  308. package/src/core/facts/extract-facts.ts +50 -0
  309. package/src/core/hook/citation-detect.ts +124 -0
  310. package/src/core/hook/cite-memo.ts +68 -0
  311. package/src/core/hook/claude-settings.ts +166 -0
  312. package/src/core/hook/gate.ts +25 -0
  313. package/src/core/hook/hook-log.ts +41 -0
  314. package/src/core/hook/memo-sweep.ts +164 -0
  315. package/src/core/hook/memo.ts +67 -0
  316. package/src/core/hook/pointer-block.ts +26 -0
  317. package/src/core/hook/select.ts +32 -0
  318. package/src/core/hook/transcript.ts +121 -0
  319. package/src/core/ingest/ingest-session.ts +111 -0
  320. package/src/core/providers/provider-models.ts +100 -0
  321. package/src/core/providers/provider-registry.ts +196 -0
  322. package/src/core/recall/citation-log.ts +108 -0
  323. package/src/core/recall/filter.ts +27 -0
  324. package/src/core/recall/index.ts +6 -0
  325. package/src/core/recall/match-fields.ts +40 -0
  326. package/src/core/recall/query-log.ts +149 -0
  327. package/src/core/recall/query-shape.ts +66 -0
  328. package/src/core/recall/recall-service.ts +320 -0
  329. package/src/core/recall/recent-log.ts +59 -0
  330. package/src/core/recall/tokenize.ts +18 -0
  331. package/src/core/recall/useful-scan.ts +336 -0
  332. package/src/core/recall-facts/fact-query-log.ts +150 -0
  333. package/src/core/recall-facts/fact-recall-service.ts +327 -0
  334. package/src/core/scheduler/scan-once.ts +142 -0
  335. package/src/core/scheduler/scheduler.ts +225 -0
  336. package/src/core/sources/source-registry.ts +260 -0
  337. package/src/core/storage/db-restore.ts +133 -0
  338. package/src/core/storage/live-status.ts +45 -0
  339. package/src/core/storage/migrate.ts +72 -0
  340. package/src/core/storage/sqlite-fact-store.ts +304 -0
  341. package/src/core/storage/sqlite-session-store.ts +765 -0
  342. package/src/hook/prompt-recall-hook.ts +174 -0
  343. package/src/hook/session-end-hook.ts +81 -0
  344. package/src/hook/session-start-hook.ts +165 -0
  345. package/src/hook/stop-hook.ts +236 -0
  346. package/src/http/app.ts +1114 -0
  347. package/src/install/claude-code.ts +128 -0
  348. package/src/install/codex.ts +367 -0
  349. package/src/install/hermes-agent.ts +76 -0
  350. package/src/install/hermes.ts +78 -0
  351. package/src/install/ollama.ts +208 -0
  352. package/src/install/setup.ts +368 -0
  353. package/src/llm/classifier-box.ts +64 -0
  354. package/src/llm/deepseek-client.ts +150 -0
  355. package/src/llm/env-autoload.ts +55 -0
  356. package/src/llm/ollama-client.ts +189 -0
  357. package/src/mcp/server.ts +534 -0
  358. package/src/ports/fact-store.ts +102 -0
  359. package/src/ports/llm-client.ts +52 -0
  360. package/src/ports/logger.ts +16 -0
  361. package/src/ports/session-store.ts +45 -0
  362. package/src/ports/transcript-adapter.ts +55 -0
  363. package/src/shared/types.ts +145 -0
  364. package/src/ui/App.tsx +58 -0
  365. package/src/ui/components/PromoteOpenButton.tsx +65 -0
  366. package/src/ui/components/SessionDrawer.tsx +136 -0
  367. package/src/ui/components/SideNav.tsx +162 -0
  368. package/src/ui/components/Skeleton.tsx +107 -0
  369. package/src/ui/index.html +13 -0
  370. package/src/ui/lib/actions.ts +30 -0
  371. package/src/ui/lib/api.ts +92 -0
  372. package/src/ui/lib/dataset.ts +141 -0
  373. package/src/ui/lib/registries.ts +155 -0
  374. package/src/ui/lib/view-settings.ts +41 -0
  375. package/src/ui/main.tsx +15 -0
  376. package/src/ui/pages/Live.tsx +229 -0
  377. package/src/ui/pages/Pulse.tsx +415 -0
  378. package/src/ui/pages/Recall.tsx +190 -0
  379. package/src/ui/pages/River.tsx +308 -0
  380. package/src/ui/pages/Search.tsx +93 -0
  381. package/src/ui/pages/Stub.tsx +9 -0
  382. package/src/ui/pages/Thread.tsx +262 -0
  383. package/src/ui/pages/settings/Classifier.tsx +227 -0
  384. package/src/ui/pages/settings/Data.tsx +190 -0
  385. package/src/ui/pages/settings/Index.tsx +65 -0
  386. package/src/ui/pages/settings/Labels.tsx +224 -0
  387. package/src/ui/pages/settings/Providers.tsx +305 -0
  388. package/src/ui/pages/settings/SettingsSubnav.tsx +28 -0
  389. package/src/ui/pages/settings/Sources.tsx +326 -0
  390. package/src/ui/pages/settings/Views.tsx +96 -0
  391. package/src/ui/styles.css +1766 -0
  392. package/src/ui/tsconfig.json +21 -0
  393. package/src/ui/vite.config.ts +19 -0
  394. package/tests/fixtures/claude_code/short_session.jsonl +2 -0
  395. package/tests/fixtures/claude_code/standard_iso.jsonl +4 -0
  396. package/tests/fixtures/claude_code/tool_heavy.jsonl +8 -0
  397. package/tests/fixtures/claude_code/with_subagent.jsonl +7 -0
  398. package/tests/fixtures/facts.ts +17 -0
  399. package/tests/fixtures/golden-corpus.ts +85 -0
  400. package/tests/fixtures/hermes/paired_request_dump.json +24 -0
  401. package/tests/fixtures/hermes/paired_session.json +23 -0
  402. package/tests/fixtures/hermes/request_dump.json +28 -0
  403. package/tests/fixtures/hermes/session_iso.json +38 -0
  404. package/tests/fixtures/hermes/session_unix.json +38 -0
  405. package/tests/fixtures/hermes/system_only.json +18 -0
  406. package/tests/fixtures/pi/error-connection-abort.jsonl +8 -0
  407. package/tests/fixtures/pi/short-successful.jsonl +5 -0
  408. package/tests/fixtures/pi/with-custom-message.jsonl +6 -0
  409. package/tests/fixtures/sessions.ts +22 -0
  410. package/tests/integration/backfill-facts.test.ts +362 -0
  411. package/tests/integration/citation-explicit.test.ts +111 -0
  412. package/tests/integration/cite-event.test.ts +169 -0
  413. package/tests/integration/cite-memo.test.ts +87 -0
  414. package/tests/integration/db-restore.test.ts +153 -0
  415. package/tests/integration/embed-backfill.test.ts +176 -0
  416. package/tests/integration/fact-supersedence.test.ts +313 -0
  417. package/tests/integration/fts-index.test.ts +60 -0
  418. package/tests/integration/getbyids-sqlite.test.ts +60 -0
  419. package/tests/integration/hermes-agent-hooks.test.ts +248 -0
  420. package/tests/integration/hook-claude-settings.test.ts +205 -0
  421. package/tests/integration/hook-log.test.ts +54 -0
  422. package/tests/integration/hook-memo.test.ts +68 -0
  423. package/tests/integration/hook-pre-compact.test.ts +105 -0
  424. package/tests/integration/hook-subagent-start.test.ts +102 -0
  425. package/tests/integration/http.test.ts +401 -0
  426. package/tests/integration/keyword-search-fts.test.ts +66 -0
  427. package/tests/integration/mcp-recall-logging.test.ts +88 -0
  428. package/tests/integration/mcp.test.ts +248 -0
  429. package/tests/integration/memo-sweep.test.ts +91 -0
  430. package/tests/integration/prompt-recall-hook.test.ts +88 -0
  431. package/tests/integration/provider-registry.test.ts +107 -0
  432. package/tests/integration/recall-golden.test.ts +59 -0
  433. package/tests/integration/recall-sqlite.test.ts +169 -0
  434. package/tests/integration/scheduler.test.ts +391 -0
  435. package/tests/integration/session-end-hook.test.ts +48 -0
  436. package/tests/integration/session-start-hook.test.ts +126 -0
  437. package/tests/integration/source-registry.test.ts +120 -0
  438. package/tests/integration/sqlite-fact-store.test.ts +346 -0
  439. package/tests/integration/stop-hook.test.ts +560 -0
  440. package/tests/integration/wal-checkpoint.test.ts +49 -0
  441. package/tests/unit/cli/launchctl-helpers.test.ts +60 -0
  442. package/tests/unit/core/adapters/aider.test.ts +230 -0
  443. package/tests/unit/core/adapters/claude-code.test.ts +118 -0
  444. package/tests/unit/core/adapters/hermes-agent.test.ts +329 -0
  445. package/tests/unit/core/adapters/hermes.test.ts +81 -0
  446. package/tests/unit/core/adapters/jsonl-generic.test.ts +142 -0
  447. package/tests/unit/core/adapters/opencode.test.ts +354 -0
  448. package/tests/unit/core/adapters/pi.test.ts +110 -0
  449. package/tests/unit/core/classifier/prompt.test.ts +126 -0
  450. package/tests/unit/core/embedding/chunk-body.test.ts +100 -0
  451. package/tests/unit/core/facts/extract-facts.test.ts +117 -0
  452. package/tests/unit/core/filter.test.ts +40 -0
  453. package/tests/unit/core/hook/citation-detect-cite-session.test.ts +96 -0
  454. package/tests/unit/core/hook/citation-detect.test.ts +124 -0
  455. package/tests/unit/core/hook/gate.test.ts +29 -0
  456. package/tests/unit/core/hook/pointer-block.test.ts +22 -0
  457. package/tests/unit/core/hook/select.test.ts +66 -0
  458. package/tests/unit/core/match-fields.test.ts +39 -0
  459. package/tests/unit/core/mcp-cite-session.test.ts +51 -0
  460. package/tests/unit/core/providers/provider-models.test.ts +101 -0
  461. package/tests/unit/core/query-shape.test.ts +92 -0
  462. package/tests/unit/core/recall-facts/fact-recall-service.test.ts +258 -0
  463. package/tests/unit/core/recall-service.test.ts +200 -0
  464. package/tests/unit/core/storage/live-status.test.ts +54 -0
  465. package/tests/unit/core/tokenize.test.ts +32 -0
  466. package/tests/unit/core/useful-scan.test.ts +537 -0
  467. package/tests/unit/llm/embed.test.ts +93 -0
  468. package/tests/unit/llm/ollama-client.test.ts +124 -0
  469. package/tests/unit/scripts/longmemeval-scorer.test.ts +114 -0
  470. package/tsconfig.json +31 -0
  471. package/tsconfig.test.json +11 -0
  472. package/vitest.config.ts +22 -0
@@ -0,0 +1,1766 @@
1
+ /* NLM — Design system v2
2
+ Single source of truth for all tokens, surfaces, and shared components.
3
+ All pages import this via BaseLayout. No :global() needed — .css is always global. */
4
+
5
+ /* ── Tokens ──────────────────────────────────────────────────────────────── */
6
+ :root {
7
+ /* Surface scale — 4 clean steps (dark-gray, not near-black) */
8
+ --surface-0: #161616; /* page background */
9
+ --surface-1: #1d1d1d; /* card / tile */
10
+ --surface-2: #242424; /* card hover / raised input */
11
+ --surface-float: #2c2c2c; /* floating panel (dropdowns, modals) */
12
+
13
+ /* Border scale — 4 levels (bumped to stay visible on lighter surfaces) */
14
+ --border-1: #262626; /* hairline / divider */
15
+ --border-2: #303030; /* card border */
16
+ --border-3: #3d3d3d; /* hover / active border */
17
+ --border-4: #525252; /* focused / bright */
18
+
19
+ /* Text scale — 4 semantic levels */
20
+ --text-1: #e8e8e8; /* primary — body, values */
21
+ --text-2: #b0b0b0; /* secondary — labels, subtitles */
22
+ --text-3: #686868; /* muted — timestamps, section headers */
23
+ --text-4: #383838; /* disabled / placeholder */
24
+
25
+ /* Legacy text aliases — TODO: migrate .astro call sites to --text-1/2/3/4 */
26
+ --text-primary: #e8e8e8;
27
+ --text-dim: #b8b8b8; /* TODO: migrate */
28
+ --text-muted: #888; /* TODO: migrate */
29
+ --text-faint: #777; /* TODO: migrate */
30
+ --text-ghost: #666; /* TODO: migrate */
31
+ --text-dead: #555; /* TODO: migrate */
32
+ --text-void: #444; /* TODO: migrate */
33
+
34
+ /* Signal colors */
35
+ --accent: #e8ff6e;
36
+ --accent-dim: rgba(232, 255, 110, 0.12);
37
+ --accent-glow: rgba(232, 255, 110, 0.06);
38
+ --warn: #ff9933;
39
+ --warn-dim: rgba(255, 153, 51, 0.12);
40
+ --danger: #ff6b35;
41
+ --danger-dim: rgba(255, 107, 53, 0.07);
42
+ --danger-glow: rgba(255, 107, 53, 0.12);
43
+
44
+ /* Typography — type scale */
45
+ --font-mono: 'IBM Plex Mono', 'Fira Code', ui-monospace, monospace;
46
+
47
+ --text-xs: 10px;
48
+ --text-sm: 11px;
49
+ --text-base: 13px;
50
+ --text-md: 15px;
51
+ --text-lg: 18px;
52
+ --text-xl: 22px;
53
+ --text-stat: 36px;
54
+
55
+ --lh-tight: 1.3;
56
+ --lh-base: 1.5;
57
+ --lh-loose: 1.7;
58
+
59
+ /* Spacing */
60
+ --page-x: 28px;
61
+ --section-gap: 24px;
62
+
63
+ /* Shape */
64
+ --r-sm: 3px;
65
+ --r-md: 5px;
66
+ --r-lg: 7px;
67
+
68
+ /* Motion */
69
+ --ease: 0.15s ease;
70
+ --ease-fast: 0.1s ease;
71
+ }
72
+
73
+ /* ── Reset ───────────────────────────────────────────────────────────────── */
74
+ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
75
+
76
+ html {
77
+ background-color: var(--surface-0);
78
+ }
79
+
80
+ body {
81
+ /* Subtle accent radial at top — the one atmospheric element.
82
+ Reads as a ceiling light above a dark instrument panel. */
83
+ background-image: radial-gradient(
84
+ ellipse 100% 40% at 50% -8%,
85
+ rgba(232, 255, 110, 0.032) 0%,
86
+ transparent 100%
87
+ );
88
+ background-attachment: fixed;
89
+ color: var(--text-1);
90
+ font-family: var(--font-mono);
91
+ min-height: 100vh;
92
+ -webkit-font-smoothing: antialiased;
93
+ -moz-osx-font-smoothing: grayscale;
94
+ }
95
+
96
+ /* ── Scrollbar ───────────────────────────────────────────────────────────── */
97
+ ::-webkit-scrollbar { width: 4px; height: 4px; }
98
+ ::-webkit-scrollbar-track { background: var(--surface-1); }
99
+ ::-webkit-scrollbar-thumb { background: var(--border-3); border-radius: 2px; }
100
+
101
+ /* ── Animation ───────────────────────────────────────────────────────────── */
102
+ @keyframes fadeSlideIn {
103
+ from { opacity: 0; transform: translateY(6px); }
104
+ to { opacity: 1; transform: translateY(0); }
105
+ }
106
+
107
+ @media (prefers-reduced-motion: reduce) {
108
+ *, *::before, *::after {
109
+ animation-duration: 0.01ms !important;
110
+ animation-delay: 0ms !important;
111
+ transition-duration: 0.01ms !important;
112
+ }
113
+ }
114
+
115
+ /* ── Skeleton ────────────────────────────────────────────────────────────── */
116
+ .skeleton {
117
+ display: inline-block;
118
+ background: linear-gradient(
119
+ 90deg,
120
+ var(--surface-1) 0%,
121
+ var(--surface-2) 50%,
122
+ var(--surface-1) 100%
123
+ );
124
+ background-size: 200% 100%;
125
+ animation: skeleton-shimmer 1.2s ease-in-out infinite;
126
+ vertical-align: middle;
127
+ }
128
+ @keyframes skeleton-shimmer {
129
+ 0% { background-position: 200% 0; }
130
+ 100% { background-position: -200% 0; }
131
+ }
132
+ @media (prefers-reduced-motion: reduce) {
133
+ .skeleton { animation: none; background: var(--surface-2); }
134
+ }
135
+
136
+ /* ── Card ────────────────────────────────────────────────────────────────── */
137
+ /* Base surface. Combine with .card-lift for interactive tiles. */
138
+ .card {
139
+ background: var(--surface-1);
140
+ border: 1px solid var(--border-2);
141
+ border-radius: var(--r-md);
142
+ }
143
+ .card-lift {
144
+ transition: border-color var(--ease), background var(--ease);
145
+ }
146
+ .card-lift:hover {
147
+ background: var(--surface-2);
148
+ border-color: var(--border-3);
149
+ }
150
+
151
+ /* ── Button ──────────────────────────────────────────────────────────────── */
152
+ /* Canonical button. All interactive controls should use this or a variant. */
153
+ .btn {
154
+ display: inline-flex;
155
+ align-items: center;
156
+ gap: 5px;
157
+ font-family: var(--font-mono);
158
+ font-size: var(--text-sm);
159
+ letter-spacing: 0.06em;
160
+ text-transform: uppercase;
161
+ padding: 5px 12px;
162
+ border-radius: var(--r-sm);
163
+ border: 1px solid var(--border-3);
164
+ background: transparent;
165
+ color: var(--text-2);
166
+ cursor: pointer;
167
+ white-space: nowrap;
168
+ text-decoration: none;
169
+ user-select: none;
170
+ transition: border-color var(--ease), color var(--ease), background var(--ease);
171
+ }
172
+ .btn:hover { border-color: var(--border-4); color: var(--text-1); }
173
+ .btn:active { background: var(--surface-2); }
174
+ .btn.active { background: var(--surface-2); border-color: var(--border-3); color: var(--text-1); }
175
+ .btn-primary { background: var(--accent); border-color: var(--accent); color: #080808; font-weight: 600; }
176
+ .btn-primary:hover { background: #d4eb5e; border-color: #d4eb5e; color: #080808; }
177
+ .btn-accent { color: var(--accent); border-color: rgba(232,255,110,0.25); }
178
+ .btn-accent:hover { background: var(--accent-glow); border-color: rgba(232,255,110,0.4); }
179
+
180
+ .form-row {
181
+ display: flex;
182
+ align-items: center;
183
+ gap: 12px;
184
+ flex-wrap: wrap;
185
+ margin: 12px 0;
186
+ }
187
+ .form-row .form-label { color: var(--text-3); font-size: var(--text-xs); }
188
+ .btn-danger:hover { border-color: var(--danger); color: var(--danger); }
189
+
190
+ /* File input — restyle the native picker button to match .btn; the
191
+ filename text stays native but tuned to the app's muted mono. */
192
+ input[type="file"].file-input {
193
+ font-family: var(--font-mono);
194
+ font-size: var(--text-xs);
195
+ color: var(--text-3);
196
+ max-width: 24rem;
197
+ }
198
+ input[type="file"].file-input::file-selector-button {
199
+ display: inline-flex;
200
+ align-items: center;
201
+ font-family: var(--font-mono);
202
+ font-size: var(--text-sm);
203
+ letter-spacing: 0.06em;
204
+ text-transform: uppercase;
205
+ padding: 5px 12px;
206
+ margin-right: 10px;
207
+ border-radius: var(--r-sm);
208
+ border: 1px solid var(--border-3);
209
+ background: transparent;
210
+ color: var(--text-2);
211
+ cursor: pointer;
212
+ transition: border-color var(--ease), color var(--ease), background var(--ease);
213
+ }
214
+ input[type="file"].file-input::file-selector-button:hover {
215
+ border-color: var(--border-4);
216
+ color: var(--text-1);
217
+ }
218
+ input[type="file"].file-input:disabled {
219
+ cursor: not-allowed;
220
+ }
221
+ input[type="file"].file-input:disabled::file-selector-button {
222
+ opacity: 0.5;
223
+ cursor: not-allowed;
224
+ }
225
+
226
+ /* ── Chip ────────────────────────────────────────────────────────────────── */
227
+ /* Filter / state toggles. Lowercase, smaller presence than .btn. */
228
+ .chip {
229
+ display: inline-flex;
230
+ align-items: center;
231
+ gap: 4px;
232
+ font-family: var(--font-mono);
233
+ font-size: var(--text-sm);
234
+ letter-spacing: 0.06em;
235
+ text-transform: lowercase;
236
+ padding: 3px 9px;
237
+ border-radius: var(--r-sm);
238
+ border: 1px solid var(--border-1);
239
+ background: transparent;
240
+ color: var(--text-3);
241
+ cursor: pointer;
242
+ user-select: none;
243
+ white-space: nowrap;
244
+ transition: border-color var(--ease), color var(--ease), background var(--ease);
245
+ }
246
+ .chip:hover { border-color: var(--border-3); color: var(--text-2); }
247
+ .chip.active { border-color: var(--border-3); color: var(--text-1); background: var(--surface-2); }
248
+
249
+ /* Semantic chip tints — opt-in via data attributes */
250
+ .chip[data-severity="critical"].active { border-color: rgba(255,107,53,0.4); color: var(--danger); background: var(--danger-dim); }
251
+ .chip[data-severity="high"].active { border-color: rgba(204,68,34,0.35); color: #cc4422; background: rgba(204,68,34,0.06); }
252
+ .chip[data-severity="medium"].active { border-color: rgba(255,153,51,0.35); color: var(--warn); background: var(--warn-dim); }
253
+ .chip[data-severity="low"].active { border-color: var(--border-3); color: var(--text-2); background: var(--surface-2); }
254
+ .chip[data-status="active"].active { color: var(--accent); border-color: rgba(232,255,110,0.3); background: var(--accent-glow); }
255
+ .chip[data-status="idle"].active { color: #ffcc80; }
256
+ .chip[data-length].active { color: var(--text-1); }
257
+ .chip[data-marker].active { color: var(--accent); border-color: rgba(232,255,110,0.3); background: var(--accent-glow); }
258
+
259
+ /* ── Floating panel ──────────────────────────────────────────────────────── */
260
+ /* Glass treatment — ONLY for elements that genuinely overlay scrolled content
261
+ (dropdowns, session preview, sticky bulk toolbar, KB legend). */
262
+ .panel-float {
263
+ background: rgba(16, 16, 16, 0.96);
264
+ backdrop-filter: blur(18px);
265
+ -webkit-backdrop-filter: blur(18px);
266
+ border: 1px solid var(--border-3);
267
+ border-radius: var(--r-md);
268
+ box-shadow: 0 12px 40px rgba(0,0,0,0.65), 0 0 0 1px rgba(255,255,255,0.03);
269
+ }
270
+
271
+ /* ── Navigation pills ────────────────────────────────────────────────────── */
272
+ .nav-link {
273
+ display: inline-flex;
274
+ align-items: center;
275
+ background: transparent;
276
+ border: 1px solid transparent;
277
+ color: var(--text-3);
278
+ border-radius: var(--r-sm);
279
+ padding: 4px 10px;
280
+ font-size: var(--text-sm);
281
+ font-family: var(--font-mono);
282
+ letter-spacing: 0.06em;
283
+ text-transform: uppercase;
284
+ text-decoration: none;
285
+ transition: color var(--ease), border-color var(--ease), background var(--ease);
286
+ }
287
+ .nav-link:hover { color: var(--text-2); border-color: var(--border-3); }
288
+ .nav-link.active {
289
+ background: var(--surface-2);
290
+ border-color: var(--border-3);
291
+ color: var(--text-1);
292
+ }
293
+
294
+ /* ── Inputs ──────────────────────────────────────────────────────────────── */
295
+ .search-input {
296
+ background: var(--surface-1);
297
+ border: 1px solid var(--border-2);
298
+ color: var(--text-1);
299
+ font-family: var(--font-mono);
300
+ font-size: var(--text-xs);
301
+ padding: 5px 10px;
302
+ border-radius: var(--r-sm);
303
+ letter-spacing: 0.04em;
304
+ outline: none;
305
+ transition: border-color var(--ease);
306
+ }
307
+ .search-input:focus { border-color: var(--border-3); }
308
+ .search-input::placeholder { color: var(--text-4); letter-spacing: 0.06em; }
309
+
310
+ /* ── Control buttons (River toolbar: zoom, axis, entity pickers) ─────────── */
311
+ .ctrl-btn {
312
+ background: var(--surface-1);
313
+ border: 1px solid var(--border-2);
314
+ color: var(--text-2);
315
+ font-family: var(--font-mono);
316
+ font-size: var(--text-sm);
317
+ letter-spacing: 0.06em;
318
+ text-transform: uppercase;
319
+ padding: 4px 10px;
320
+ border-radius: var(--r-sm);
321
+ cursor: pointer;
322
+ white-space: nowrap;
323
+ transition: border-color var(--ease), color var(--ease), background var(--ease);
324
+ }
325
+ .ctrl-btn:hover { border-color: var(--border-3); color: var(--text-1); }
326
+ .ctrl-btn.active { background: var(--surface-2); border-color: var(--border-3); color: var(--accent); }
327
+ .ctrl-btn[data-active="1"] { color: var(--accent); border-color: rgba(232,255,110,0.25); }
328
+
329
+ .ctrl-label {
330
+ font-size: var(--text-sm);
331
+ color: var(--text-3);
332
+ letter-spacing: 0.06em;
333
+ text-transform: uppercase;
334
+ flex-shrink: 0;
335
+ }
336
+
337
+ /* ── Settings subnav ─────────────────────────────────────────────────────── */
338
+ .subnav {
339
+ padding: 0 28px;
340
+ border-bottom: 1px solid var(--border-1);
341
+ display: flex;
342
+ gap: 0;
343
+ }
344
+ .subnav-link {
345
+ font-size: var(--text-sm);
346
+ font-family: var(--font-mono);
347
+ letter-spacing: 0.06em;
348
+ text-transform: uppercase;
349
+ color: var(--text-3);
350
+ text-decoration: none;
351
+ padding: 10px 14px;
352
+ border-bottom: 2px solid transparent;
353
+ margin-bottom: -1px;
354
+ transition: color var(--ease), border-color var(--ease);
355
+ }
356
+ .subnav-link:hover { color: var(--text-2); }
357
+ .subnav-link.active { color: var(--accent); border-bottom-color: var(--accent); }
358
+
359
+ /* ── App shell ────────────────────────────────────────────────────────────
360
+ Ported from BaseLayout.astro. Side-nav is sticky inside the flex row,
361
+ so the main panel doesn't need a margin offset. */
362
+ .page-shell {
363
+ display: flex;
364
+ min-height: 100vh;
365
+ background: var(--surface-0);
366
+ }
367
+
368
+ .page-main {
369
+ flex: 1;
370
+ min-width: 0;
371
+ display: flex;
372
+ flex-direction: column;
373
+ }
374
+
375
+ /* ── AppHeader (slim top bar) ─────────────────────────────────────────── */
376
+ .app-header {
377
+ position: sticky;
378
+ top: 0;
379
+ z-index: 50;
380
+ height: 52px;
381
+ padding: 0 var(--page-x);
382
+ display: flex;
383
+ align-items: center;
384
+ gap: 20px;
385
+ background: rgba(22, 22, 22, 0.9);
386
+ backdrop-filter: blur(12px);
387
+ -webkit-backdrop-filter: blur(12px);
388
+ border-bottom: 1px solid var(--border-1);
389
+ }
390
+
391
+ .app-header .wordmark-page {
392
+ font-size: var(--text-sm);
393
+ color: var(--text-3);
394
+ letter-spacing: 0.04em;
395
+ text-transform: lowercase;
396
+ }
397
+
398
+ .app-header .header-spacer { flex: 1; }
399
+
400
+ .app-header .status-dot {
401
+ width: 7px;
402
+ height: 7px;
403
+ border-radius: 50%;
404
+ background: var(--accent);
405
+ box-shadow: 0 0 6px var(--accent-dim);
406
+ animation: pulse-dot 2s ease-in-out infinite;
407
+ }
408
+
409
+ @keyframes pulse-dot {
410
+ 0%, 100% { opacity: 1; }
411
+ 50% { opacity: 0.4; }
412
+ }
413
+
414
+ .app-header .header-info {
415
+ font-size: var(--text-xs);
416
+ color: var(--text-3);
417
+ letter-spacing: 0.04em;
418
+ }
419
+
420
+ /* ── SideNav (ported from SideNav.astro <style> block) ─────────────────── */
421
+ .sidenav {
422
+ width: 180px;
423
+ flex-shrink: 0;
424
+ display: flex;
425
+ flex-direction: column;
426
+ background: var(--surface-1);
427
+ border-right: 1px solid var(--border-1);
428
+ position: sticky;
429
+ top: 0;
430
+ height: 100vh;
431
+ overflow: hidden;
432
+ transition: width 150ms ease;
433
+ z-index: 100;
434
+ }
435
+
436
+ .sidenav.collapsed { width: 52px; }
437
+
438
+ .sidenav-header {
439
+ padding: 0 8px 0 16px;
440
+ height: 52px;
441
+ display: flex;
442
+ align-items: center;
443
+ justify-content: space-between;
444
+ border-bottom: 1px solid var(--border-1);
445
+ flex-shrink: 0;
446
+ }
447
+
448
+ .sidenav-wordmark {
449
+ font-size: 11px;
450
+ font-family: var(--font-mono);
451
+ letter-spacing: 0.2em;
452
+ text-transform: uppercase;
453
+ color: var(--accent);
454
+ user-select: none;
455
+ white-space: nowrap;
456
+ overflow: hidden;
457
+ transition: opacity 100ms ease, width 100ms ease;
458
+ }
459
+
460
+ .sidenav.collapsed .sidenav-wordmark { opacity: 0; width: 0; padding: 0; }
461
+
462
+ .sidenav-toggle {
463
+ background: none;
464
+ border: none;
465
+ color: var(--text-3);
466
+ cursor: pointer;
467
+ padding: 5px;
468
+ border-radius: var(--r-sm);
469
+ display: flex;
470
+ align-items: center;
471
+ justify-content: center;
472
+ flex-shrink: 0;
473
+ transition: color var(--ease), background var(--ease);
474
+ }
475
+
476
+ .sidenav-toggle:hover { color: var(--text-1); background: var(--surface-2); }
477
+
478
+ .sidenav.collapsed .sidenav-header { justify-content: center; padding: 0 8px; }
479
+
480
+ .sidenav-items {
481
+ flex: 1;
482
+ padding: 10px 8px;
483
+ display: flex;
484
+ flex-direction: column;
485
+ gap: 2px;
486
+ overflow: hidden;
487
+ }
488
+
489
+ .sidenav-item {
490
+ display: flex;
491
+ align-items: center;
492
+ gap: 9px;
493
+ padding: 8px 10px;
494
+ border-radius: var(--r-sm);
495
+ font-size: 11px;
496
+ font-family: var(--font-mono);
497
+ letter-spacing: 0.06em;
498
+ text-transform: uppercase;
499
+ color: var(--text-3);
500
+ text-decoration: none;
501
+ border-left: 3px solid transparent;
502
+ transition: color var(--ease), background var(--ease), border-color var(--ease);
503
+ user-select: none;
504
+ white-space: nowrap;
505
+ overflow: hidden;
506
+ position: relative;
507
+ }
508
+
509
+ .sidenav-item:hover { color: var(--text-1); background: var(--surface-2); }
510
+ .sidenav-item.active {
511
+ color: var(--text-1);
512
+ border-left-color: var(--accent);
513
+ background: var(--accent-glow);
514
+ }
515
+
516
+ .item-icon { flex-shrink: 0; display: flex; align-items: center; opacity: 0.7; }
517
+ .sidenav-item.active .item-icon { opacity: 1; }
518
+ .item-label { transition: opacity 100ms ease; }
519
+
520
+ .sidenav.collapsed .item-label { display: none; }
521
+ .sidenav.collapsed .sidenav-item {
522
+ gap: 0;
523
+ padding: 8px;
524
+ justify-content: center;
525
+ border-left: 3px solid transparent;
526
+ }
527
+ .sidenav.collapsed .sidenav-item.active { border-left-color: var(--accent); }
528
+
529
+ .sidenav.collapsed .sidenav-item::after {
530
+ content: attr(data-label);
531
+ position: absolute;
532
+ left: calc(100% + 8px);
533
+ top: 50%;
534
+ transform: translateY(-50%);
535
+ background: var(--surface-float);
536
+ color: var(--text-1);
537
+ font-size: 10px;
538
+ font-family: var(--font-mono);
539
+ letter-spacing: 0.06em;
540
+ text-transform: uppercase;
541
+ padding: 4px 8px;
542
+ border-radius: var(--r-sm);
543
+ border: 1px solid var(--border-2);
544
+ white-space: nowrap;
545
+ pointer-events: none;
546
+ opacity: 0;
547
+ transition: opacity 100ms ease;
548
+ z-index: 200;
549
+ }
550
+
551
+ .sidenav.collapsed .sidenav-item:hover::after { opacity: 1; }
552
+
553
+ .sidenav-footer {
554
+ padding: 8px;
555
+ border-top: 1px solid var(--border-1);
556
+ flex-shrink: 0;
557
+ }
558
+
559
+ .sidenav-data { font-size: 10px; }
560
+
561
+ /* ── Live page — three-column board ───────────────────────────────────── */
562
+ .live-page {
563
+ display: flex;
564
+ flex-direction: column;
565
+ flex: 1;
566
+ min-height: 0;
567
+ }
568
+
569
+ .live-status {
570
+ display: flex;
571
+ align-items: center;
572
+ gap: 8px;
573
+ padding: 12px var(--page-x) 4px;
574
+ }
575
+
576
+ .live-status-dot {
577
+ width: 7px;
578
+ height: 7px;
579
+ border-radius: 50%;
580
+ flex-shrink: 0;
581
+ }
582
+ .live-status-live {
583
+ background: var(--accent);
584
+ box-shadow: 0 0 6px var(--accent);
585
+ }
586
+ .live-status-connecting { background: var(--text-3); }
587
+ .live-status-reconnecting {
588
+ background: var(--warn);
589
+ animation: pulse-dot 1.2s ease-in-out infinite;
590
+ }
591
+
592
+ .live-status-label {
593
+ font-family: var(--font-mono);
594
+ font-size: var(--text-sm);
595
+ letter-spacing: 0.06em;
596
+ text-transform: uppercase;
597
+ color: var(--text-2);
598
+ }
599
+
600
+ .live-board {
601
+ padding: 4px var(--page-x) 0;
602
+ display: grid;
603
+ grid-template-columns: repeat(3, 1fr);
604
+ gap: 16px;
605
+ flex: 1;
606
+ min-height: 0;
607
+ }
608
+
609
+ .live-col {
610
+ background: var(--surface-1);
611
+ border: 1px solid var(--border-2);
612
+ border-radius: var(--r-md);
613
+ display: flex;
614
+ flex-direction: column;
615
+ overflow: hidden;
616
+ min-height: 0;
617
+ }
618
+
619
+ .live-col-head {
620
+ padding: 10px 14px;
621
+ border-bottom: 1px solid var(--border-1);
622
+ display: flex;
623
+ align-items: baseline;
624
+ justify-content: space-between;
625
+ }
626
+
627
+ .live-col-title {
628
+ font-size: var(--text-sm);
629
+ font-family: var(--font-mono);
630
+ letter-spacing: 0.06em;
631
+ text-transform: uppercase;
632
+ color: var(--text-2);
633
+ }
634
+
635
+ .live-col-count {
636
+ font-size: var(--text-xs);
637
+ color: var(--text-3);
638
+ font-family: var(--font-mono);
639
+ }
640
+
641
+ .live-col-body { overflow-y: auto; flex: 1; }
642
+
643
+ .live-row {
644
+ padding: 8px 14px;
645
+ border-bottom: 1px solid var(--border-1);
646
+ animation: fadeSlideIn 200ms ease-out;
647
+ }
648
+
649
+ .live-row:last-child { border-bottom: none; }
650
+
651
+ .live-row.clickable {
652
+ cursor: pointer;
653
+ transition: background var(--ease);
654
+ }
655
+ .live-row.clickable:hover { background: var(--surface-2); }
656
+ .live-row.clickable:focus-visible {
657
+ outline: 1px solid rgba(255, 255, 255, 0.5);
658
+ outline-offset: -1px;
659
+ }
660
+
661
+ /* Newly arrived rows flash once so the feed reads as live. */
662
+ .live-row.is-new { animation: fadeSlideIn 200ms ease-out, liveFlash 1200ms ease-out; }
663
+
664
+ @keyframes liveFlash {
665
+ 0% { background: var(--accent-glow); }
666
+ 100% { background: transparent; }
667
+ }
668
+
669
+ .live-row .label { color: var(--text-1); font-size: var(--text-base); line-height: var(--lh-tight); }
670
+ .live-row .body { color: var(--text-3); font-size: var(--text-sm); margin-top: 3px; line-height: var(--lh-tight); }
671
+ .live-row .meta { color: var(--text-3); font-size: var(--text-xs); margin-top: 4px; letter-spacing: 0.04em; }
672
+
673
+ .live-tag {
674
+ display: inline-block;
675
+ font-size: var(--text-xs);
676
+ font-family: var(--font-mono);
677
+ letter-spacing: 0.06em;
678
+ text-transform: uppercase;
679
+ padding: 1px 6px;
680
+ margin-right: 6px;
681
+ border-radius: var(--r-sm);
682
+ background: var(--surface-2);
683
+ color: var(--text-2);
684
+ border: 1px solid var(--border-2);
685
+ }
686
+
687
+ .live-tag[data-kind="decision"] {
688
+ background: var(--accent-glow);
689
+ border-color: rgba(232,255,110,0.25);
690
+ color: var(--accent);
691
+ }
692
+
693
+ .live-tag[data-kind="open"] {
694
+ background: var(--warn-dim);
695
+ border-color: rgba(255,153,51,0.25);
696
+ color: var(--warn);
697
+ }
698
+
699
+ .live-empty {
700
+ padding: 32px 16px;
701
+ text-align: center;
702
+ color: var(--text-3);
703
+ font-size: var(--text-xs);
704
+ letter-spacing: 0.06em;
705
+ text-transform: uppercase;
706
+ }
707
+
708
+ /* ── Stub page (placeholder for unported routes) ───────────────────────── */
709
+ .stub-shell {
710
+ padding: 80px var(--page-x);
711
+ text-align: center;
712
+ color: var(--text-3);
713
+ font-size: var(--text-base);
714
+ line-height: var(--lh-loose);
715
+ }
716
+ .stub-shell .stub-name {
717
+ color: var(--accent);
718
+ font-size: var(--text-md);
719
+ text-transform: lowercase;
720
+ letter-spacing: 0.04em;
721
+ display: block;
722
+ margin-bottom: 12px;
723
+ }
724
+ .stub-shell .stub-hint { color: var(--text-4); font-size: var(--text-sm); }
725
+
726
+ /* ── Page chrome shared across all routes ───────────────────────────────── */
727
+ .page-pad {
728
+ padding: 20px var(--page-x) 60px;
729
+ flex: 1;
730
+ min-width: 0;
731
+ }
732
+
733
+ .page-title {
734
+ font-size: var(--text-lg);
735
+ font-family: var(--font-mono);
736
+ font-weight: 500;
737
+ color: var(--text-1);
738
+ margin-bottom: 16px;
739
+ }
740
+
741
+ .page-header {
742
+ display: flex;
743
+ align-items: center;
744
+ gap: 24px;
745
+ margin-bottom: 24px;
746
+ padding-top: 8px;
747
+ }
748
+
749
+ .subnav + .page-header,
750
+ .subnav + .page-title {
751
+ gap: 28px;
752
+ margin-top: 28px;
753
+ margin-bottom: 28px;
754
+ padding-top: 0;
755
+ }
756
+
757
+ .page-header .page-title { margin-bottom: 0; }
758
+ .page-header .header-spacer,
759
+ .thread-header .header-spacer,
760
+ .river-toolbar .header-spacer { flex: 1; }
761
+
762
+ .section-title {
763
+ font-size: var(--text-sm);
764
+ text-transform: uppercase;
765
+ letter-spacing: 0.06em;
766
+ color: var(--text-3);
767
+ margin: 24px 0 8px;
768
+ font-weight: 500;
769
+ }
770
+
771
+ .muted { color: var(--text-3); }
772
+ .muted.small { font-size: var(--text-xs); letter-spacing: 0.04em; }
773
+ .muted.error { color: var(--danger); }
774
+ .right { text-align: right; }
775
+ .mono { font-family: var(--font-mono); }
776
+ .small { font-size: var(--text-xs); }
777
+
778
+ code, .code-block {
779
+ font-family: var(--font-mono);
780
+ font-size: var(--text-sm);
781
+ background: var(--surface-2);
782
+ border: 1px solid var(--border-2);
783
+ padding: 2px 6px;
784
+ border-radius: var(--r-sm);
785
+ color: var(--text-1);
786
+ }
787
+ .code-block { display: block; padding: 10px 14px; margin: 8px 0; }
788
+
789
+ /* ── Settings ───────────────────────────────────────────────────────────── */
790
+ .settings-grid {
791
+ display: grid;
792
+ grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
793
+ gap: 12px;
794
+ margin-top: 16px;
795
+ }
796
+
797
+ .settings-card {
798
+ padding: 14px;
799
+ text-decoration: none;
800
+ display: flex;
801
+ flex-direction: column;
802
+ gap: 6px;
803
+ color: var(--text-1);
804
+ }
805
+
806
+ .settings-card-title {
807
+ font-size: var(--text-sm);
808
+ letter-spacing: 0.06em;
809
+ text-transform: uppercase;
810
+ color: var(--accent);
811
+ font-weight: 500;
812
+ }
813
+
814
+ .settings-card-body { color: var(--text-2); font-size: var(--text-sm); }
815
+
816
+ .kv-list {
817
+ display: grid;
818
+ grid-template-columns: max-content 1fr;
819
+ gap: 6px 16px;
820
+ margin-top: 12px;
821
+ }
822
+
823
+ .kv-label {
824
+ color: var(--text-3);
825
+ font-size: var(--text-sm);
826
+ letter-spacing: 0.04em;
827
+ }
828
+
829
+ .kv-value { color: var(--text-1); font-size: var(--text-sm); }
830
+ .kv-value.mono { font-family: var(--font-mono); word-break: break-all; }
831
+
832
+ .form-grid {
833
+ display: grid;
834
+ gap: 12px;
835
+ margin-top: 12px;
836
+ max-width: 420px;
837
+ }
838
+
839
+ .form-field {
840
+ display: flex;
841
+ flex-direction: column;
842
+ gap: 4px;
843
+ }
844
+
845
+ .form-label {
846
+ font-size: var(--text-xs);
847
+ color: var(--text-3);
848
+ letter-spacing: 0.06em;
849
+ text-transform: uppercase;
850
+ }
851
+
852
+ .form-input {
853
+ background: var(--surface-1);
854
+ border: 1px solid var(--border-2);
855
+ color: var(--text-1);
856
+ padding: 6px 10px;
857
+ font-family: var(--font-mono);
858
+ font-size: var(--text-sm);
859
+ border-radius: var(--r-sm);
860
+ }
861
+ .form-input:focus { border-color: var(--border-4); outline: none; }
862
+
863
+ /* ── Data table (labels page) ───────────────────────────────────────────── */
864
+ .data-table {
865
+ width: 100%;
866
+ border-collapse: collapse;
867
+ margin-top: 12px;
868
+ font-size: var(--text-sm);
869
+ }
870
+
871
+ .data-table th {
872
+ text-align: left;
873
+ font-weight: 500;
874
+ font-size: var(--text-xs);
875
+ letter-spacing: 0.06em;
876
+ text-transform: uppercase;
877
+ color: var(--text-3);
878
+ padding: 8px 10px;
879
+ border-bottom: 1px solid var(--border-2);
880
+ }
881
+
882
+ .data-table th.right { text-align: right; }
883
+
884
+ .data-table td {
885
+ padding: 7px 10px;
886
+ border-bottom: 1px solid var(--border-1);
887
+ color: var(--text-1);
888
+ vertical-align: middle;
889
+ }
890
+
891
+ .data-table tr:hover td { background: var(--surface-1); }
892
+
893
+ .canonical .dot { margin-right: 8px; vertical-align: middle; }
894
+
895
+ .dot {
896
+ display: inline-block;
897
+ width: 8px;
898
+ height: 8px;
899
+ border-radius: 50%;
900
+ flex-shrink: 0;
901
+ }
902
+ .dot.lg { width: 14px; height: 14px; }
903
+
904
+ .chip-inline {
905
+ display: inline-flex;
906
+ align-items: center;
907
+ gap: 4px;
908
+ padding: 2px 8px;
909
+ border-radius: var(--r-sm);
910
+ border: 1px solid var(--border-2);
911
+ background: var(--surface-2);
912
+ color: var(--text-2);
913
+ font-family: var(--font-mono);
914
+ font-size: var(--text-xs);
915
+ letter-spacing: 0.04em;
916
+ text-transform: lowercase;
917
+ }
918
+
919
+ .chip-inline.status-active { color: var(--accent); border-color: rgba(232,255,110,0.25); background: var(--accent-glow); }
920
+ .chip-inline.status-idle { color: #ffcc80; border-color: rgba(255,204,128,0.25); }
921
+ .chip-inline.status-closed { color: var(--text-2); }
922
+ .chip-inline.status-superseded { color: var(--text-3); text-decoration: line-through; }
923
+ .chip-inline.status-retired { color: var(--danger); border-color: rgba(255,107,53,0.25); background: var(--danger-dim); }
924
+ .chip-inline.status-stale { color: var(--warn); border-color: rgba(255,153,51,0.25); background: var(--warn-dim); }
925
+ .chip-inline.severity-high { color: var(--danger); border-color: rgba(255,107,53,0.35); background: var(--danger-dim); }
926
+ .chip-inline.severity-medium { color: var(--warn); border-color: rgba(255,153,51,0.35); background: var(--warn-dim); }
927
+
928
+ .chip-x {
929
+ background: none;
930
+ border: none;
931
+ color: inherit;
932
+ cursor: pointer;
933
+ font-size: 12px;
934
+ padding: 0 0 0 4px;
935
+ line-height: 1;
936
+ }
937
+
938
+ /* ── Pulse ──────────────────────────────────────────────────────────────── */
939
+ .kpi-row {
940
+ display: grid;
941
+ grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
942
+ gap: 12px;
943
+ margin-bottom: 18px;
944
+ }
945
+
946
+ .kpi {
947
+ background: var(--surface-1);
948
+ border: 1px solid var(--border-2);
949
+ border-radius: var(--r-md);
950
+ padding: 14px 16px;
951
+ display: flex;
952
+ flex-direction: column;
953
+ gap: 4px;
954
+ }
955
+
956
+ .kpi-label {
957
+ font-size: var(--text-xs);
958
+ letter-spacing: 0.06em;
959
+ text-transform: uppercase;
960
+ color: var(--text-3);
961
+ }
962
+
963
+ .kpi-value {
964
+ font-size: var(--text-stat);
965
+ font-family: var(--font-mono);
966
+ color: var(--text-1);
967
+ line-height: 1;
968
+ }
969
+
970
+ .kpi-hint { font-size: var(--text-xs); color: var(--text-3); }
971
+
972
+ .kpi-sparkline { gap: 8px; }
973
+
974
+ .sparkline {
975
+ display: flex;
976
+ align-items: flex-end;
977
+ gap: 4px;
978
+ height: 40px;
979
+ }
980
+
981
+ .spark-bar {
982
+ flex: 1;
983
+ background: var(--accent);
984
+ opacity: 0.7;
985
+ border-radius: 1px;
986
+ min-height: 2px;
987
+ }
988
+
989
+ .pulse-grid {
990
+ display: grid;
991
+ grid-template-columns: 1fr 1fr 1fr;
992
+ grid-template-rows: 1fr 1fr;
993
+ grid-template-areas:
994
+ "coherence recent stale"
995
+ "runtimes recent stale";
996
+ gap: 12px;
997
+ margin-top: 6px;
998
+ /* Bound the grid to viewport-minus-chrome so cards have a real bounding box
999
+ and their bodies scroll instead of stretching the page. */
1000
+ height: calc(100vh - 280px);
1001
+ min-height: 360px;
1002
+ }
1003
+
1004
+ .pulse-area-coherence { grid-area: coherence; }
1005
+ .pulse-area-runtimes { grid-area: runtimes; }
1006
+ .pulse-area-recent { grid-area: recent; }
1007
+ .pulse-area-stale { grid-area: stale; }
1008
+
1009
+ /* Scroll containers (Stale alerts, Recent sessions). Card lays out as a
1010
+ flex column with header fixed and body filling the remainder. */
1011
+ .pulse-scroll-card {
1012
+ display: flex;
1013
+ flex-direction: column;
1014
+ min-height: 0;
1015
+ overflow: hidden;
1016
+ }
1017
+
1018
+ .pulse-scroll-card .card-head { flex-shrink: 0; }
1019
+
1020
+ /* Hover affordance for the major blocks on the Pulse view — KPI tiles
1021
+ and the three pulse-grid cards get a clean white outline so it's
1022
+ obvious they're interactive zones. */
1023
+ .kpi,
1024
+ .pulse-grid > .card {
1025
+ transition: outline-color var(--ease), border-color var(--ease);
1026
+ outline: 1px solid transparent;
1027
+ outline-offset: -1px;
1028
+ }
1029
+
1030
+ .kpi:hover,
1031
+ .pulse-grid > .card:hover {
1032
+ outline-color: rgba(255, 255, 255, 0.55);
1033
+ }
1034
+
1035
+ .pulse-scroll-body {
1036
+ flex: 1;
1037
+ min-height: 0;
1038
+ overflow-y: auto;
1039
+ }
1040
+
1041
+ .card-head {
1042
+ display: flex;
1043
+ align-items: baseline;
1044
+ justify-content: space-between;
1045
+ padding: 10px 14px;
1046
+ border-bottom: 1px solid var(--border-1);
1047
+ }
1048
+
1049
+ .card-head h3 {
1050
+ font-size: var(--text-sm);
1051
+ letter-spacing: 0.06em;
1052
+ text-transform: uppercase;
1053
+ color: var(--text-2);
1054
+ font-weight: 500;
1055
+ }
1056
+
1057
+ .bar-stack { padding: 14px; display: flex; flex-direction: column; gap: 10px; }
1058
+ .bar-item { display: grid; grid-template-columns: 80px 1fr auto; gap: 10px; align-items: center; }
1059
+ .bar-label { font-size: var(--text-sm); color: var(--text-2); }
1060
+ .bar-track { height: 8px; background: var(--surface-2); border-radius: 4px; overflow: hidden; }
1061
+ .bar-fill { height: 100%; transition: width 0.3s ease; }
1062
+ .bar-fill.tone-active { background: var(--accent); }
1063
+ .bar-fill.tone-warn { background: var(--warn); }
1064
+ .bar-fill.tone-danger { background: var(--danger); }
1065
+ .bar-value { font-size: var(--text-sm); color: var(--text-1); min-width: 96px; text-align: right; white-space: nowrap; }
1066
+ .bar-pct { margin-left: 2px; }
1067
+
1068
+ /* Recall — adoption + coverage telemetry */
1069
+ .recall-head {
1070
+ display: flex;
1071
+ justify-content: space-between;
1072
+ align-items: flex-start;
1073
+ gap: 24px;
1074
+ margin-bottom: 20px;
1075
+ }
1076
+ .recall-note { max-width: 60ch; margin: 0; line-height: 1.5; }
1077
+ .recall-block { margin-bottom: 28px; }
1078
+ .recall-block-head { margin-bottom: 14px; }
1079
+ .recall-block-head .page-title { margin-bottom: 2px; }
1080
+ .recall-block-head p { margin: 0; max-width: 70ch; }
1081
+ .recall-empty { padding: 18px; font-size: var(--text-sm); }
1082
+ .recall-cards {
1083
+ display: grid;
1084
+ grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
1085
+ gap: 14px;
1086
+ margin-top: 14px;
1087
+ }
1088
+ .recall-bars .bar-item { grid-template-columns: 140px 1fr auto; }
1089
+ .recall-bar-label {
1090
+ overflow: hidden;
1091
+ text-overflow: ellipsis;
1092
+ white-space: nowrap;
1093
+ }
1094
+ .recall-bars .bar-value { min-width: 0; }
1095
+ .recall-bars-empty { padding: 4px 0; }
1096
+
1097
+ @media (max-width: 720px) {
1098
+ .recall-head { flex-direction: column; gap: 12px; }
1099
+ }
1100
+
1101
+ /* Runtimes panel (Pulse) */
1102
+ .runtime-list { list-style: none; padding: 4px 0; margin: 0; }
1103
+ .runtime-row {
1104
+ display: grid;
1105
+ grid-template-columns: 12px 1fr auto auto;
1106
+ align-items: center;
1107
+ gap: 10px;
1108
+ padding: 8px 12px;
1109
+ border-bottom: 1px solid var(--border-1);
1110
+ }
1111
+ .runtime-row:last-child { border-bottom: none; }
1112
+ .runtime-dot {
1113
+ width: 8px; height: 8px; border-radius: 50%;
1114
+ background: var(--text-3);
1115
+ box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.04);
1116
+ }
1117
+ .runtime-dot.runtime-active { background: var(--accent); box-shadow: 0 0 6px rgba(232,255,110,0.6); }
1118
+ .runtime-dot.runtime-idle { background: var(--warn); }
1119
+ .runtime-dot.runtime-dormant { background: var(--text-3); opacity: 0.5; }
1120
+ .runtime-name { color: var(--text-1); font-size: var(--text-sm); }
1121
+ .runtime-counts { white-space: nowrap; }
1122
+ .runtime-counts-sep { margin: 0 6px; opacity: 0.5; }
1123
+ .runtime-counts-prev { opacity: 0.7; }
1124
+ .runtime-age { min-width: 60px; text-align: right; }
1125
+
1126
+ .alert-list { list-style: none; padding: 4px 0; }
1127
+ .alert-row-empty { padding: 16px 14px; }
1128
+
1129
+ .alert-row {
1130
+ display: grid;
1131
+ grid-template-columns: auto auto 1fr;
1132
+ gap: 10px;
1133
+ align-items: center;
1134
+ padding: 7px 14px;
1135
+ border-bottom: 1px solid var(--border-1);
1136
+ }
1137
+
1138
+ .alert-entity { color: var(--text-1); font-size: var(--text-sm); }
1139
+
1140
+ .alert-summary {
1141
+ display: -webkit-box;
1142
+ -webkit-line-clamp: 1;
1143
+ -webkit-box-orient: vertical;
1144
+ overflow: hidden;
1145
+ text-overflow: ellipsis;
1146
+ color: var(--text-3);
1147
+ font-size: var(--text-xs);
1148
+ letter-spacing: 0.04em;
1149
+ min-width: 0;
1150
+ line-height: var(--lh-base);
1151
+ }
1152
+
1153
+ .alert-row.clickable { cursor: pointer; transition: background var(--ease); }
1154
+ .alert-row.clickable:hover { background: var(--surface-2); }
1155
+ .alert-row.clickable:focus-visible { outline: 1px solid var(--border-4); outline-offset: -2px; }
1156
+ .alert-row.clickable:hover .alert-entity { color: var(--accent); }
1157
+
1158
+ .drawer-actions {
1159
+ display: flex;
1160
+ flex-wrap: wrap;
1161
+ gap: 6px;
1162
+ margin: 14px 0 6px;
1163
+ }
1164
+
1165
+ .session-list { list-style: none; }
1166
+ .session-list.compact .session-row { padding: 6px 0; border-bottom: 1px solid var(--border-1); }
1167
+
1168
+ .session-row {
1169
+ display: grid;
1170
+ grid-template-columns: auto 1fr auto;
1171
+ gap: 10px;
1172
+ align-items: center;
1173
+ padding: 8px 14px;
1174
+ border-bottom: 1px solid var(--border-1);
1175
+ }
1176
+
1177
+ .session-row-detail {
1178
+ grid-template-columns: auto 1fr auto;
1179
+ }
1180
+
1181
+ .session-row-main {
1182
+ display: flex;
1183
+ flex-direction: column;
1184
+ gap: 2px;
1185
+ min-width: 0;
1186
+ }
1187
+
1188
+ .session-label {
1189
+ color: var(--text-1);
1190
+ font-size: var(--text-sm);
1191
+ text-decoration: none;
1192
+ white-space: nowrap;
1193
+ overflow: hidden;
1194
+ text-overflow: ellipsis;
1195
+ }
1196
+ a.session-label:hover { color: var(--accent); }
1197
+
1198
+ .session-meta {
1199
+ color: var(--text-3);
1200
+ font-size: var(--text-xs);
1201
+ letter-spacing: 0.04em;
1202
+ }
1203
+
1204
+ @media (max-width: 1100px) {
1205
+ .pulse-grid {
1206
+ grid-template-columns: 1fr 1fr;
1207
+ grid-template-rows: auto;
1208
+ grid-template-areas:
1209
+ "coherence runtimes"
1210
+ "recent recent"
1211
+ "stale stale";
1212
+ height: auto;
1213
+ min-height: 0;
1214
+ }
1215
+ .pulse-scroll-body { max-height: 360px; }
1216
+ }
1217
+
1218
+ @media (max-width: 700px) {
1219
+ .pulse-grid {
1220
+ grid-template-columns: 1fr;
1221
+ grid-template-areas:
1222
+ "coherence"
1223
+ "runtimes"
1224
+ "recent"
1225
+ "stale";
1226
+ }
1227
+ }
1228
+
1229
+ /* ── Search ─────────────────────────────────────────────────────────────── */
1230
+ .search-bar {
1231
+ display: flex;
1232
+ gap: 10px;
1233
+ align-items: center;
1234
+ margin-bottom: 12px;
1235
+ }
1236
+
1237
+ .search-input.search-big {
1238
+ flex: 1;
1239
+ padding: 10px 14px;
1240
+ font-size: var(--text-md);
1241
+ }
1242
+
1243
+ .search-meta { margin: 8px 0; }
1244
+
1245
+ /* ── Thread ─────────────────────────────────────────────────────────────── */
1246
+ .thread-header {
1247
+ display: flex;
1248
+ align-items: center;
1249
+ gap: 14px;
1250
+ margin-bottom: 16px;
1251
+ }
1252
+
1253
+ .thread-header .page-title { margin-bottom: 0; }
1254
+
1255
+ .thread-grid {
1256
+ display: grid;
1257
+ grid-template-columns: 1fr 1fr;
1258
+ gap: 12px;
1259
+ margin-bottom: 18px;
1260
+ }
1261
+
1262
+ .marker-list { list-style: none; max-height: 360px; overflow-y: auto; }
1263
+ .marker-row {
1264
+ display: grid;
1265
+ grid-template-columns: auto 1fr auto;
1266
+ gap: 10px;
1267
+ padding: 7px 14px;
1268
+ border-bottom: 1px solid var(--border-1);
1269
+ align-items: baseline;
1270
+ }
1271
+
1272
+ .marker-text { color: var(--text-1); font-size: var(--text-sm); }
1273
+
1274
+ .entity-grid {
1275
+ list-style: none;
1276
+ display: grid;
1277
+ grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
1278
+ gap: 10px;
1279
+ margin-top: 16px;
1280
+ }
1281
+
1282
+ .entity-card {
1283
+ padding: 10px 12px;
1284
+ text-decoration: none;
1285
+ color: var(--text-1);
1286
+ display: flex;
1287
+ align-items: center;
1288
+ gap: 8px;
1289
+ }
1290
+
1291
+ .entity-name {
1292
+ flex: 1;
1293
+ font-size: var(--text-sm);
1294
+ overflow: hidden;
1295
+ text-overflow: ellipsis;
1296
+ white-space: nowrap;
1297
+ }
1298
+
1299
+ @media (max-width: 800px) {
1300
+ .thread-grid { grid-template-columns: 1fr; }
1301
+ }
1302
+
1303
+ /* ── River ──────────────────────────────────────────────────────────────── */
1304
+ .river-toolbar {
1305
+ display: flex;
1306
+ align-items: center;
1307
+ gap: 12px;
1308
+ margin-bottom: 16px;
1309
+ }
1310
+
1311
+ .river-toolbar .page-title { margin-bottom: 0; font-size: var(--text-lg); }
1312
+
1313
+ .river-grid {
1314
+ --river-label-w: 200px;
1315
+ --river-row-gap: 3px;
1316
+ --river-cell-gap: 2px;
1317
+ --river-cell-h: 16px;
1318
+ padding: 12px;
1319
+ width: 100%;
1320
+ display: flex;
1321
+ flex-direction: column;
1322
+ gap: var(--river-row-gap);
1323
+ }
1324
+
1325
+ /* Density tiers — Settings → Views. Comfortable matches the base values
1326
+ above; compact and spacious retune row gap, cell gap, and cell height. */
1327
+ .river-grid.river-density-compact {
1328
+ --river-row-gap: 1px;
1329
+ --river-cell-gap: 1px;
1330
+ --river-cell-h: 10px;
1331
+ }
1332
+ .river-grid.river-density-spacious {
1333
+ --river-row-gap: 6px;
1334
+ --river-cell-gap: 4px;
1335
+ --river-cell-h: 24px;
1336
+ }
1337
+
1338
+ .river-row {
1339
+ display: grid;
1340
+ grid-template-columns: var(--river-label-w) 1fr;
1341
+ gap: 6px;
1342
+ align-items: center;
1343
+ width: 100%;
1344
+ min-width: 0;
1345
+ }
1346
+
1347
+ .river-row-dates {
1348
+ margin-bottom: 2px;
1349
+ }
1350
+
1351
+ .river-lane-label {
1352
+ display: flex;
1353
+ align-items: center;
1354
+ gap: 6px;
1355
+ padding: 2px 6px;
1356
+ border-radius: var(--r-sm);
1357
+ text-decoration: none;
1358
+ color: var(--text-2);
1359
+ font-size: var(--text-sm);
1360
+ white-space: nowrap;
1361
+ overflow: hidden;
1362
+ width: 100%;
1363
+ min-width: 0;
1364
+ }
1365
+ .river-lane-label:hover { background: var(--surface-2); color: var(--text-1); }
1366
+
1367
+ .river-lane-label--header {
1368
+ padding: 0;
1369
+ background: none;
1370
+ }
1371
+
1372
+ .river-lane-name { flex: 1; overflow: hidden; text-overflow: ellipsis; min-width: 0; }
1373
+
1374
+ .river-cells {
1375
+ display: grid;
1376
+ grid-template-columns: repeat(var(--cells, 1), minmax(0, 1fr));
1377
+ gap: var(--river-cell-gap);
1378
+ width: 100%;
1379
+ min-width: 0;
1380
+ }
1381
+
1382
+ .river-date-cell {
1383
+ font-size: var(--text-xs);
1384
+ color: var(--text-2);
1385
+ letter-spacing: 0.04em;
1386
+ text-align: center;
1387
+ overflow: hidden;
1388
+ text-overflow: ellipsis;
1389
+ white-space: nowrap;
1390
+ }
1391
+
1392
+ .river-cell {
1393
+ height: var(--river-cell-h);
1394
+ border-radius: 2px;
1395
+ background: var(--surface-2);
1396
+ min-width: 0;
1397
+ }
1398
+
1399
+ .river-cell.tier-1 { background: rgba(232,255,110,0.20); }
1400
+ .river-cell.tier-2 { background: rgba(232,255,110,0.40); }
1401
+ .river-cell.tier-3 { background: rgba(232,255,110,0.65); }
1402
+ .river-cell.tier-4 { background: var(--accent); }
1403
+
1404
+ /* ── Inline action buttons (Pulse alerts, Labels rows) ─────────────────── */
1405
+ .alert-row {
1406
+ grid-template-columns: auto auto 1fr auto;
1407
+ }
1408
+
1409
+ .alert-actions {
1410
+ display: flex;
1411
+ gap: 6px;
1412
+ align-items: center;
1413
+ }
1414
+
1415
+ .alert-actions .chip {
1416
+ font-size: var(--text-xs);
1417
+ padding: 2px 8px;
1418
+ }
1419
+
1420
+ .row-actions {
1421
+ display: flex;
1422
+ gap: 6px;
1423
+ }
1424
+
1425
+ .row-actions .chip {
1426
+ font-size: var(--text-xs);
1427
+ padding: 2px 8px;
1428
+ }
1429
+
1430
+ .row-busy { opacity: 0.5; pointer-events: none; transition: opacity 0.15s ease; }
1431
+
1432
+ .form-input-inline {
1433
+ padding: 3px 8px;
1434
+ font-size: var(--text-xs);
1435
+ letter-spacing: 0.04em;
1436
+ }
1437
+
1438
+ /* ── River interactivity ───────────────────────────────────────────────── */
1439
+ .river-grid {
1440
+ position: relative;
1441
+ user-select: none;
1442
+ }
1443
+
1444
+ .river-drag-rect {
1445
+ position: absolute;
1446
+ top: 0;
1447
+ bottom: 0;
1448
+ background: var(--accent-glow);
1449
+ border-left: 1px dashed var(--accent);
1450
+ border-right: 1px dashed var(--accent);
1451
+ pointer-events: none;
1452
+ z-index: 1;
1453
+ }
1454
+
1455
+ .river-lane-label {
1456
+ background: none;
1457
+ border: none;
1458
+ text-align: left;
1459
+ cursor: pointer;
1460
+ font-family: var(--font-mono);
1461
+ }
1462
+
1463
+ .river-cell {
1464
+ transition: transform 0.08s ease, outline-color var(--ease);
1465
+ outline: 1px solid transparent;
1466
+ outline-offset: 1px;
1467
+ position: relative;
1468
+ z-index: 0;
1469
+ }
1470
+
1471
+ .river-cell:hover {
1472
+ transform: scale(1.3);
1473
+ outline-color: rgba(255, 255, 255, 0.7);
1474
+ z-index: 1;
1475
+ }
1476
+
1477
+ .river-hover {
1478
+ position: fixed;
1479
+ display: flex;
1480
+ align-items: center;
1481
+ gap: 8px;
1482
+ background: var(--surface-float);
1483
+ border: 1px solid var(--border-3);
1484
+ border-radius: var(--r-sm);
1485
+ padding: 6px 10px;
1486
+ font-size: var(--text-xs);
1487
+ pointer-events: none;
1488
+ z-index: 200;
1489
+ box-shadow: 0 6px 20px rgba(0, 0, 0, 0.5);
1490
+ }
1491
+
1492
+ .river-hover-name {
1493
+ color: var(--text-1);
1494
+ font-size: var(--text-sm);
1495
+ }
1496
+
1497
+ /* ── Thread drawer ─────────────────────────────────────────────────────── */
1498
+ .session-row.clickable { cursor: pointer; }
1499
+ .session-row.clickable:hover { background: var(--surface-1); }
1500
+
1501
+ .link-button {
1502
+ background: none;
1503
+ border: none;
1504
+ color: var(--text-3);
1505
+ font-family: var(--font-mono);
1506
+ font-size: var(--text-xs);
1507
+ cursor: pointer;
1508
+ padding: 0;
1509
+ letter-spacing: 0.04em;
1510
+ }
1511
+ .link-button:hover { color: var(--accent); }
1512
+
1513
+ .drawer-backdrop {
1514
+ position: fixed;
1515
+ inset: 0;
1516
+ background: rgba(0, 0, 0, 0.55);
1517
+ z-index: 199;
1518
+ animation: fadeIn 0.15s ease;
1519
+ }
1520
+
1521
+ @keyframes fadeIn {
1522
+ from { opacity: 0; }
1523
+ to { opacity: 1; }
1524
+ }
1525
+
1526
+ .session-drawer {
1527
+ position: fixed;
1528
+ top: 0;
1529
+ right: 0;
1530
+ bottom: 0;
1531
+ width: min(560px, 100vw);
1532
+ background: var(--surface-1);
1533
+ border-left: 1px solid var(--border-3);
1534
+ box-shadow: -16px 0 40px rgba(0, 0, 0, 0.55);
1535
+ z-index: 200;
1536
+ display: flex;
1537
+ flex-direction: column;
1538
+ animation: slideInRight 0.18s ease;
1539
+ }
1540
+
1541
+ @keyframes slideInRight {
1542
+ from { transform: translateX(20px); opacity: 0; }
1543
+ to { transform: translateX(0); opacity: 1; }
1544
+ }
1545
+
1546
+ .drawer-head {
1547
+ display: flex;
1548
+ align-items: center;
1549
+ gap: 10px;
1550
+ padding: 14px 18px;
1551
+ border-bottom: 1px solid var(--border-1);
1552
+ }
1553
+
1554
+ .drawer-title {
1555
+ flex: 1;
1556
+ font-size: var(--text-md);
1557
+ font-family: var(--font-mono);
1558
+ font-weight: 500;
1559
+ color: var(--text-1);
1560
+ min-width: 0;
1561
+ overflow: hidden;
1562
+ text-overflow: ellipsis;
1563
+ white-space: nowrap;
1564
+ }
1565
+
1566
+ .drawer-close {
1567
+ background: none;
1568
+ border: none;
1569
+ color: var(--text-3);
1570
+ font-size: 24px;
1571
+ line-height: 1;
1572
+ cursor: pointer;
1573
+ padding: 0 6px;
1574
+ }
1575
+ .drawer-close:hover { color: var(--text-1); }
1576
+
1577
+ .drawer-body {
1578
+ overflow-y: auto;
1579
+ padding: 16px 18px 80px;
1580
+ flex: 1;
1581
+ }
1582
+
1583
+ .drawer-section {
1584
+ font-size: var(--text-xs);
1585
+ letter-spacing: 0.06em;
1586
+ text-transform: uppercase;
1587
+ color: var(--text-3);
1588
+ margin: 18px 0 8px;
1589
+ font-weight: 500;
1590
+ }
1591
+
1592
+ .drawer-list {
1593
+ list-style: none;
1594
+ display: flex;
1595
+ flex-direction: column;
1596
+ gap: 8px;
1597
+ font-size: var(--text-sm);
1598
+ color: var(--text-1);
1599
+ }
1600
+
1601
+ .drawer-list li { display: flex; gap: 8px; align-items: baseline; }
1602
+
1603
+ .drawer-paragraph {
1604
+ font-size: var(--text-sm);
1605
+ color: var(--text-1);
1606
+ line-height: var(--lh-loose);
1607
+ }
1608
+
1609
+ .drawer-body-text {
1610
+ background: var(--surface-0);
1611
+ border: 1px solid var(--border-1);
1612
+ border-radius: var(--r-sm);
1613
+ padding: 10px 12px;
1614
+ font-size: var(--text-xs);
1615
+ color: var(--text-2);
1616
+ white-space: pre-wrap;
1617
+ word-break: break-word;
1618
+ max-height: 360px;
1619
+ overflow-y: auto;
1620
+ line-height: var(--lh-base);
1621
+ }
1622
+
1623
+ .entity-chips {
1624
+ display: flex;
1625
+ flex-wrap: wrap;
1626
+ gap: 6px;
1627
+ }
1628
+
1629
+ /* ── Card head with stacked filters ──────────────────────────────────── */
1630
+ .card-head.card-head-stack {
1631
+ flex-direction: column;
1632
+ align-items: stretch;
1633
+ gap: 8px;
1634
+ padding: 10px 14px 12px;
1635
+ }
1636
+
1637
+ .card-head-row {
1638
+ display: flex;
1639
+ align-items: baseline;
1640
+ justify-content: space-between;
1641
+ }
1642
+
1643
+ .card-filters {
1644
+ display: flex;
1645
+ gap: 12px;
1646
+ flex-wrap: wrap;
1647
+ }
1648
+
1649
+ .filter-group {
1650
+ display: flex;
1651
+ gap: 4px;
1652
+ }
1653
+
1654
+ .filter-group .chip {
1655
+ font-size: var(--text-xs);
1656
+ padding: 2px 8px;
1657
+ }
1658
+
1659
+ /* ── Thread sessions: search + filters + pagination ──────────────────── */
1660
+ .thread-sessions-head {
1661
+ display: flex;
1662
+ align-items: center;
1663
+ gap: 12px;
1664
+ margin: 24px 0 8px;
1665
+ }
1666
+
1667
+ .thread-sessions-title { margin: 0; flex-shrink: 0; }
1668
+
1669
+ .thread-sessions-head .search-input { flex: 1; }
1670
+
1671
+ .thread-filters {
1672
+ display: flex;
1673
+ align-items: center;
1674
+ gap: 12px;
1675
+ flex-wrap: wrap;
1676
+ margin-bottom: 8px;
1677
+ }
1678
+
1679
+ .thread-filters .filter-group { flex-wrap: wrap; }
1680
+
1681
+ .empty-row { padding: 16px 14px; }
1682
+
1683
+ .pagination {
1684
+ display: flex;
1685
+ align-items: center;
1686
+ gap: 16px;
1687
+ flex-wrap: wrap;
1688
+ padding: 14px 0 4px;
1689
+ border-top: 1px solid var(--border-1);
1690
+ margin-top: 4px;
1691
+ }
1692
+
1693
+ .page-size {
1694
+ display: flex;
1695
+ align-items: center;
1696
+ gap: 8px;
1697
+ }
1698
+
1699
+ .page-nav {
1700
+ display: flex;
1701
+ align-items: center;
1702
+ gap: 4px;
1703
+ }
1704
+
1705
+ .page-nav .chip:disabled {
1706
+ opacity: 0.4;
1707
+ cursor: not-allowed;
1708
+ }
1709
+
1710
+ .page-indicator {
1711
+ font-size: var(--text-sm);
1712
+ color: var(--text-2);
1713
+ padding: 0 8px;
1714
+ min-width: 60px;
1715
+ text-align: center;
1716
+ }
1717
+
1718
+ /* ── Promote-open inline control ───────────────────────────────────────── */
1719
+ .marker-row-promotable {
1720
+ display: grid;
1721
+ grid-template-columns: auto 1fr auto auto;
1722
+ gap: 10px;
1723
+ align-items: baseline;
1724
+ }
1725
+
1726
+ .marker-row-promotable .marker-actions {
1727
+ display: flex;
1728
+ gap: 6px;
1729
+ align-items: baseline;
1730
+ }
1731
+
1732
+ .promote-chip {
1733
+ font-size: var(--text-xs);
1734
+ padding: 2px 8px;
1735
+ color: var(--accent);
1736
+ border-color: rgba(232,255,110,0.3);
1737
+ background: var(--accent-glow);
1738
+ }
1739
+ .promote-chip:hover {
1740
+ border-color: rgba(232,255,110,0.55);
1741
+ color: var(--accent);
1742
+ background: var(--accent-dim);
1743
+ }
1744
+
1745
+ .promote-editor {
1746
+ display: flex;
1747
+ gap: 6px;
1748
+ align-items: center;
1749
+ min-width: 280px;
1750
+ }
1751
+
1752
+ .promote-input {
1753
+ flex: 1;
1754
+ min-width: 200px;
1755
+ }
1756
+
1757
+ /* Compact pagination — used inside drawers / narrow panels */
1758
+ .pagination.pagination-compact {
1759
+ gap: 10px;
1760
+ padding: 10px 0 4px;
1761
+ font-size: var(--text-xs);
1762
+ flex-wrap: wrap;
1763
+ }
1764
+ .pagination.pagination-compact .page-indicator { min-width: 40px; padding: 0 4px; font-size: var(--text-xs); }
1765
+ .pagination.pagination-compact .chip { padding: 1px 6px; font-size: var(--text-xs); }
1766
+ .pagination.pagination-compact .form-label { font-size: 9px; }