@remnic/core 9.54.4 → 9.54.6

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 (648) hide show
  1. package/dist/access-admin-ops-surface.d.ts +24 -23
  2. package/dist/access-admin-ops-surface.js +55 -50
  3. package/dist/access-authorization-probe.d.ts +25 -22
  4. package/dist/access-authorization-probe.js +58 -51
  5. package/dist/access-boundary.d.ts +26 -23
  6. package/dist/access-boundary.js +59 -52
  7. package/dist/access-cli.js +115 -107
  8. package/dist/access-cli.js.map +1 -1
  9. package/dist/access-coding-context-resolution.d.ts +1 -1
  10. package/dist/access-extraction-force-flush.d.ts +19 -18
  11. package/dist/access-extraction-force-flush.js +55 -50
  12. package/dist/access-health-types.d.ts +1 -1
  13. package/dist/access-http-lcm-compaction.d.ts +22 -21
  14. package/dist/access-http-lifecycle-flush.d.ts +22 -21
  15. package/dist/access-http-offline-stream.d.ts +19 -18
  16. package/dist/access-http-query.js +55 -50
  17. package/dist/access-http.d.ts +55 -36
  18. package/dist/access-http.js +76 -65
  19. package/dist/access-identity-continuity-surface.d.ts +9 -9
  20. package/dist/access-identity-continuity-surface.js +55 -50
  21. package/dist/access-lcm-surface.d.ts +21 -20
  22. package/dist/access-lcm-surface.js +55 -50
  23. package/dist/access-mcp.d.ts +22 -21
  24. package/dist/access-mcp.js +63 -57
  25. package/dist/access-memory-search-fanout.d.ts +22 -2
  26. package/dist/access-memory-search-fanout.js +1 -1
  27. package/dist/{access-namespace-preflight-C7n4tgWC.d.ts → access-namespace-preflight-CCNn_6NE.d.ts} +1 -1
  28. package/dist/access-namespace-preflight.d.ts +3 -3
  29. package/dist/access-namespace-preflight.js +55 -50
  30. package/dist/access-observe-write-surface.d.ts +22 -21
  31. package/dist/access-observe-write-surface.js +55 -50
  32. package/dist/access-offline-manifest.d.ts +10 -9
  33. package/dist/access-offline-manifest.js +12 -10
  34. package/dist/access-operations-batch.js +57 -52
  35. package/dist/access-operations.d.ts +23 -22
  36. package/dist/access-operations.js +61 -55
  37. package/dist/access-recall-concurrency.d.ts +21 -20
  38. package/dist/access-recall-concurrency.js +55 -50
  39. package/dist/access-recall-response.d.ts +19 -18
  40. package/dist/access-recall-response.js +55 -50
  41. package/dist/access-recall-surface.d.ts +19 -18
  42. package/dist/access-recall-surface.js +55 -50
  43. package/dist/access-schema.d.ts +2 -2
  44. package/dist/access-schema.js +5 -4
  45. package/dist/{access-service-CXvbxLK-.d.ts → access-service-DkSdXK4S.d.ts} +1314 -83
  46. package/dist/access-service-helpers.d.ts +22 -21
  47. package/dist/access-service.d.ts +31 -30
  48. package/dist/access-service.js +55 -50
  49. package/dist/access-surface-catalog.d.ts +22 -21
  50. package/dist/access-surface-catalog.js +209 -2
  51. package/dist/access-surface-catalog.js.map +1 -1
  52. package/dist/access-wearables-meetings-surface.d.ts +3 -3
  53. package/dist/action-confidence.d.ts +1 -1
  54. package/dist/active-memory-bridge.d.ts +14 -1
  55. package/dist/active-memory-bridge.js +6 -1
  56. package/dist/active-recall.d.ts +1 -1
  57. package/dist/active-recall.js +4 -2
  58. package/dist/active-recall.js.map +1 -1
  59. package/dist/adapters/index.js +7 -7
  60. package/dist/adapters/registry.js +3 -3
  61. package/dist/admin-console/public/what-helps-me/app.js +1594 -0
  62. package/dist/admin-console/public/what-helps-me/index.html +339 -0
  63. package/dist/admin-console/public/what-helps-me/model.js +685 -0
  64. package/dist/admin-console/public/what-helps-me/what-helps-me.css +1296 -0
  65. package/dist/ambient-provenance.d.ts +1 -1
  66. package/dist/artifact-search.d.ts +1 -1
  67. package/dist/{auto-sync-TDHLZPVP.js → auto-sync-NR3GPCOA.js} +4 -4
  68. package/dist/behavior-learner.d.ts +1 -1
  69. package/dist/behavior-signals.d.ts +1 -1
  70. package/dist/bootstrap.d.ts +9 -9
  71. package/dist/briefing.d.ts +2 -2
  72. package/dist/briefing.js +33 -12
  73. package/dist/buffer-surprise-report.d.ts +1 -1
  74. package/dist/buffer-turn-helpers.d.ts +1 -1
  75. package/dist/buffer.d.ts +2 -2
  76. package/dist/buffer.js +3 -3
  77. package/dist/bulk-import/index.d.ts +3 -3
  78. package/dist/calibration.d.ts +1 -1
  79. package/dist/calibration.js +1 -1
  80. package/dist/capabilities.d.ts +1 -1
  81. package/dist/{catalog-BOOxl-I2.d.ts → catalog-DGFf8mD9.d.ts} +1 -1
  82. package/dist/causal-behavior.d.ts +1 -1
  83. package/dist/causal-consolidation.d.ts +1 -1
  84. package/dist/causal-consolidation.js +34 -13
  85. package/dist/causal-consolidation.js.map +1 -1
  86. package/dist/causal-trajectory-graph.d.ts +1 -1
  87. package/dist/{chunk-ZYLC7NFO.js → chunk-26ZXBTYN.js} +4 -4
  88. package/dist/chunk-26ZXBTYN.js.map +1 -0
  89. package/dist/{chunk-LLJMAULE.js → chunk-2FKEIPIT.js} +5 -5
  90. package/dist/{chunk-3CSX3ETE.js → chunk-2LWQSV6A.js} +23 -11
  91. package/dist/chunk-2LWQSV6A.js.map +1 -0
  92. package/dist/{chunk-JBPKEARU.js → chunk-2QSZNTDO.js} +7 -7
  93. package/dist/chunk-37DZZFFM.js +502 -0
  94. package/dist/chunk-37DZZFFM.js.map +1 -0
  95. package/dist/chunk-3HSRWJTC.js +9 -0
  96. package/dist/chunk-3HSRWJTC.js.map +1 -0
  97. package/dist/{chunk-XUAMCLY4.js → chunk-3TIZPSJC.js} +17 -13
  98. package/dist/chunk-3TIZPSJC.js.map +1 -0
  99. package/dist/{chunk-PDDTI7IC.js → chunk-43SEBY2M.js} +1 -1
  100. package/dist/{chunk-HWRD23DW.js → chunk-46SZBVDS.js} +3 -3
  101. package/dist/{chunk-6HPHEFXT.js → chunk-5FD7JMTX.js} +2 -2
  102. package/dist/{chunk-7BZOBA5J.js → chunk-5NB726GV.js} +70 -16
  103. package/dist/chunk-5NB726GV.js.map +1 -0
  104. package/dist/chunk-62OKGIJA.js +39 -0
  105. package/dist/chunk-62OKGIJA.js.map +1 -0
  106. package/dist/{chunk-IBKR2TFR.js → chunk-6CYHUWCO.js} +2 -2
  107. package/dist/{chunk-YN6ASZUG.js → chunk-6QPI2ITJ.js} +5 -5
  108. package/dist/{chunk-UU3G6GUP.js → chunk-76JJSXK7.js} +1 -1
  109. package/dist/chunk-76JJSXK7.js.map +1 -0
  110. package/dist/{chunk-PX2LSHYM.js → chunk-7BD6O7ZV.js} +189 -4
  111. package/dist/chunk-7BD6O7ZV.js.map +1 -0
  112. package/dist/chunk-7TBXNYLO.js +346 -0
  113. package/dist/chunk-7TBXNYLO.js.map +1 -0
  114. package/dist/{chunk-NH5IR2WM.js → chunk-BRR5EUTF.js} +6 -2
  115. package/dist/chunk-BRR5EUTF.js.map +1 -0
  116. package/dist/{chunk-NIS256NW.js → chunk-CIAI24TQ.js} +3 -3
  117. package/dist/{chunk-T2JHZWH6.js → chunk-D3LBIUWV.js} +11703 -7573
  118. package/dist/chunk-D3LBIUWV.js.map +1 -0
  119. package/dist/{chunk-7FGL6CGA.js → chunk-DVYRD4HY.js} +2 -2
  120. package/dist/{chunk-G7Z6EENO.js → chunk-EKTP7MNS.js} +4 -4
  121. package/dist/{chunk-S75M5ZRK.js → chunk-F2JRDPON.js} +16 -1
  122. package/dist/chunk-F2JRDPON.js.map +1 -0
  123. package/dist/{chunk-RZ4XGKOJ.js → chunk-FFRTNF3L.js} +8 -8
  124. package/dist/{chunk-VKXWK44Q.js → chunk-FONWBCVY.js} +3 -3
  125. package/dist/{chunk-DZD576ZU.js → chunk-FPLM2TKD.js} +2 -2
  126. package/dist/{chunk-ZPJ4QSGP.js → chunk-FTPONF6B.js} +2 -2
  127. package/dist/{chunk-LJS7KUPV.js → chunk-FWX7W5PM.js} +2 -2
  128. package/dist/{chunk-UTRO5UJU.js → chunk-G6DFSS6A.js} +5 -1
  129. package/dist/chunk-G6DFSS6A.js.map +1 -0
  130. package/dist/{chunk-6LAPLZOS.js → chunk-GJ776BC2.js} +8 -3
  131. package/dist/chunk-GJ776BC2.js.map +1 -0
  132. package/dist/{chunk-G4T7L557.js → chunk-GN3WRFJY.js} +6 -6
  133. package/dist/{chunk-QT2FRYI4.js → chunk-H7NAZ3UB.js} +2 -2
  134. package/dist/{chunk-APPFNGX6.js → chunk-HBUFQRIM.js} +2 -2
  135. package/dist/{chunk-UU7TRFNE.js → chunk-HGJ6NJ4E.js} +2 -2
  136. package/dist/{chunk-WB7JSQCP.js → chunk-HIEWDJ2O.js} +5 -2
  137. package/dist/{chunk-WB7JSQCP.js.map → chunk-HIEWDJ2O.js.map} +1 -1
  138. package/dist/{chunk-UES4BHA2.js → chunk-I3MSGFGD.js} +17 -6
  139. package/dist/chunk-I3MSGFGD.js.map +1 -0
  140. package/dist/{chunk-C4ZNGVK3.js → chunk-IKP5OYQQ.js} +2 -2
  141. package/dist/{chunk-KQAFEZQX.js → chunk-IPLYGWQF.js} +5 -5
  142. package/dist/{chunk-RDJRFDJY.js → chunk-IQ4NVWEF.js} +2 -2
  143. package/dist/{chunk-CGVHPCP2.js → chunk-JYG4OLWH.js} +2 -2
  144. package/dist/{chunk-WOPA3SGI.js → chunk-K26NWEOC.js} +8 -8
  145. package/dist/{chunk-K4QS7LXU.js → chunk-K2UGPTYY.js} +485 -44
  146. package/dist/chunk-K2UGPTYY.js.map +1 -0
  147. package/dist/{chunk-L6CQ7WIW.js → chunk-KFBOPMF6.js} +11 -11
  148. package/dist/chunk-KFBOPMF6.js.map +1 -0
  149. package/dist/{chunk-G7A5HLUS.js → chunk-KJH6W2I7.js} +2 -2
  150. package/dist/{chunk-IXEBUYO4.js → chunk-KM55SMLG.js} +11 -2
  151. package/dist/{chunk-IXEBUYO4.js.map → chunk-KM55SMLG.js.map} +1 -1
  152. package/dist/{chunk-HOIRB77A.js → chunk-L6Z5OAS4.js} +2 -2
  153. package/dist/chunk-LC2BDILZ.js +57 -0
  154. package/dist/chunk-LC2BDILZ.js.map +1 -0
  155. package/dist/{chunk-227UFSEQ.js → chunk-LEPOP46W.js} +4 -4
  156. package/dist/{chunk-6R4G7N4S.js → chunk-M4YHAODQ.js} +388 -748
  157. package/dist/chunk-M4YHAODQ.js.map +1 -0
  158. package/dist/{chunk-3JLPGJ43.js → chunk-MIH3CJPN.js} +5 -5
  159. package/dist/{chunk-QHFV6VNU.js → chunk-MK5VEWCR.js} +2 -2
  160. package/dist/chunk-NISZQW72.js +140 -0
  161. package/dist/chunk-NISZQW72.js.map +1 -0
  162. package/dist/{chunk-GUKBW4HL.js → chunk-NQMSEB5D.js} +6 -6
  163. package/dist/{chunk-G2LUMAUF.js → chunk-OCIP6YMD.js} +12 -5
  164. package/dist/chunk-OCIP6YMD.js.map +1 -0
  165. package/dist/{chunk-47P3RYZP.js → chunk-PG4QBPVG.js} +21 -11
  166. package/dist/chunk-PG4QBPVG.js.map +1 -0
  167. package/dist/{chunk-WRJVB3C2.js → chunk-PO7NXBG2.js} +2 -2
  168. package/dist/{chunk-QWGE2PUH.js → chunk-PT54WISF.js} +190 -56
  169. package/dist/chunk-PT54WISF.js.map +1 -0
  170. package/dist/{chunk-AZM4CCAC.js → chunk-QZLDRQ4I.js} +8 -8
  171. package/dist/{chunk-THPPCISA.js → chunk-RBII7637.js} +5 -5
  172. package/dist/{chunk-JDZPNCMA.js → chunk-RUNORG2P.js} +6 -3
  173. package/dist/chunk-RUNORG2P.js.map +1 -0
  174. package/dist/{chunk-JV5YUBIZ.js → chunk-RZQ6W2W4.js} +50 -44
  175. package/dist/chunk-RZQ6W2W4.js.map +1 -0
  176. package/dist/{chunk-RUMNY5DT.js → chunk-SMNYAMJD.js} +6 -2
  177. package/dist/chunk-SMNYAMJD.js.map +1 -0
  178. package/dist/{chunk-MKQBXQKU.js → chunk-TB6CBZ5V.js} +22 -15
  179. package/dist/chunk-TB6CBZ5V.js.map +1 -0
  180. package/dist/{chunk-VMMQDEN5.js → chunk-TO7ZTWJW.js} +2 -2
  181. package/dist/{chunk-K2TDK7GG.js → chunk-TWTUGCTY.js} +2 -2
  182. package/dist/{chunk-HLYR2TQL.js → chunk-UCTS3DDH.js} +2 -2
  183. package/dist/{chunk-JYIJNNN3.js → chunk-UG6VXIOK.js} +2 -2
  184. package/dist/{chunk-WM65LU67.js → chunk-UKYS3NU5.js} +2 -2
  185. package/dist/{chunk-WDBX6VER.js → chunk-UPLZNJKN.js} +5 -5
  186. package/dist/{chunk-C23BYTJV.js → chunk-UW5NPZJ2.js} +40 -12
  187. package/dist/chunk-UW5NPZJ2.js.map +1 -0
  188. package/dist/{chunk-2GFK22OZ.js → chunk-V6ERBR7U.js} +2 -2
  189. package/dist/{chunk-CYF6WVE7.js → chunk-W5ZKPI34.js} +139 -158
  190. package/dist/chunk-W5ZKPI34.js.map +1 -0
  191. package/dist/chunk-XX4HPFFM.js +370 -0
  192. package/dist/chunk-XX4HPFFM.js.map +1 -0
  193. package/dist/{chunk-ZMYDYNI4.js → chunk-XZHE2DEN.js} +4 -4
  194. package/dist/chunk-YXQEKCZM.js +223 -0
  195. package/dist/chunk-YXQEKCZM.js.map +1 -0
  196. package/dist/{chunk-GYPQL6XN.js → chunk-Z4N7SVVF.js} +35 -47
  197. package/dist/chunk-Z4N7SVVF.js.map +1 -0
  198. package/dist/{chunk-MAXVH44E.js → chunk-Z54EM5YZ.js} +7 -7
  199. package/dist/{chunk-YGLY6QFV.js → chunk-ZRZVLB7O.js} +2 -2
  200. package/dist/{chunk-4IIFL7GC.js → chunk-ZUKIUXHQ.js} +10 -10
  201. package/dist/{cli-DVUFIa5E.d.ts → cli-Bze8S0Qs.d.ts} +5 -5
  202. package/dist/cli.d.ts +22 -21
  203. package/dist/cli.js +90 -80
  204. package/dist/coding/pre-action-gate.d.ts +1 -1
  205. package/dist/compounding/engine.d.ts +2 -2
  206. package/dist/compounding/engine.js +33 -12
  207. package/dist/compounding/preference-consolidator.d.ts +1 -1
  208. package/dist/compression-optimizer.d.ts +1 -1
  209. package/dist/config.d.ts +1 -1
  210. package/dist/config.js +4 -2
  211. package/dist/connectors/codex-materialize-runner.d.ts +1 -1
  212. package/dist/connectors/codex-materialize-runner.js +33 -12
  213. package/dist/connectors/codex-materialize.d.ts +1 -1
  214. package/dist/connectors/index.d.ts +1 -1
  215. package/dist/connectors/index.js +32 -12
  216. package/dist/consolidation-provenance-check.d.ts +2 -2
  217. package/dist/consolidation-undo.d.ts +2 -2
  218. package/dist/contradiction/index.d.ts +2 -2
  219. package/dist/contradiction/index.js +3 -1
  220. package/dist/contradiction-scan-JDTBXRGW.js +20 -0
  221. package/dist/converge-config.d.ts +1 -1
  222. package/dist/convergence-refresh.d.ts +2 -2
  223. package/dist/convergence-refresh.js +35 -14
  224. package/dist/conversation-index/backend.d.ts +1 -1
  225. package/dist/conversation-index/backend.js +2 -2
  226. package/dist/conversation-index/chunker.d.ts +1 -1
  227. package/dist/conversation-index/faiss-adapter.d.ts +1 -1
  228. package/dist/conversation-index/indexer.d.ts +1 -1
  229. package/dist/conversation-index/search.d.ts +1 -1
  230. package/dist/corpus-watermark.d.ts +1 -1
  231. package/dist/corpus-watermark.js +25 -24
  232. package/dist/day-summary.d.ts +1 -1
  233. package/dist/delinearize.d.ts +1 -1
  234. package/dist/dependency-propagation-config.d.ts +1 -1
  235. package/dist/{dependency-propagation-delivery-XzF76C9n.d.ts → dependency-propagation-delivery-CzEfB31P.d.ts} +1 -1
  236. package/dist/direct-answer-wiring.d.ts +1 -1
  237. package/dist/direct-answer.d.ts +1 -1
  238. package/dist/embedding-fallback.d.ts +1 -1
  239. package/dist/enrichment/index.d.ts +1 -1
  240. package/dist/entity-retrieval.d.ts +2 -2
  241. package/dist/entity-retrieval.js +33 -12
  242. package/dist/entity-schema.d.ts +1 -1
  243. package/dist/explicit-capture.d.ts +9 -9
  244. package/dist/explicit-capture.js +7 -4
  245. package/dist/external-wiki-access.d.ts +22 -21
  246. package/dist/external-wiki-access.js +56 -51
  247. package/dist/external-wiki-collection-registration.d.ts +1 -1
  248. package/dist/external-wiki-collection.d.ts +1 -1
  249. package/dist/external-wiki-mcp-tools.d.ts +22 -21
  250. package/dist/extraction-error-classification.d.ts +1 -1
  251. package/dist/extraction-faithfulness.d.ts +1 -1
  252. package/dist/extraction-faithfulness.js +2 -2
  253. package/dist/extraction-judge-telemetry.d.ts +1 -1
  254. package/dist/extraction-judge-training.d.ts +1 -1
  255. package/dist/extraction-judge.d.ts +1 -1
  256. package/dist/extraction-judge.js +2 -2
  257. package/dist/extraction-liveness.d.ts +1 -1
  258. package/dist/extraction-normalization.d.ts +1 -1
  259. package/dist/extraction-prompt.d.ts +1 -1
  260. package/dist/extraction-source-grounding-rules.d.ts +1 -1
  261. package/dist/extraction-source-grounding.d.ts +1 -1
  262. package/dist/extraction.d.ts +1 -1
  263. package/dist/extraction.js +35 -19
  264. package/dist/fallback-llm.d.ts +14 -1
  265. package/dist/fallback-llm.js +1 -1
  266. package/dist/{first-start-migration-626C5554.js → first-start-migration-CRZCZAJN.js} +11 -3
  267. package/dist/first-start-migration-CRZCZAJN.js.map +1 -0
  268. package/dist/graph-dashboard-diff.d.ts +1 -1
  269. package/dist/graph-dashboard-key.d.ts +1 -1
  270. package/dist/graph-dashboard-parser.d.ts +1 -1
  271. package/dist/graph-edge-reinforcement.d.ts +1 -1
  272. package/dist/graph-path-reconstruction.d.ts +1 -1
  273. package/dist/graph-path-scoring.d.ts +1 -1
  274. package/dist/graph-snapshot.d.ts +1 -1
  275. package/dist/graph.d.ts +1 -1
  276. package/dist/harmonic-retrieval.js +1 -1
  277. package/dist/importance.d.ts +1 -1
  278. package/dist/importers/index.d.ts +1 -1
  279. package/dist/in-flight-reads.d.ts +1 -1
  280. package/dist/index.d.ts +314 -308
  281. package/dist/index.js +304 -133
  282. package/dist/inline-script.d.ts +3 -0
  283. package/dist/inline-script.js +8 -0
  284. package/dist/inline-script.js.map +1 -0
  285. package/dist/intent.d.ts +1 -1
  286. package/dist/lcm/engine.d.ts +1 -1
  287. package/dist/lcm/engine.js +3 -3
  288. package/dist/lcm/index.d.ts +1 -1
  289. package/dist/lcm/index.js +6 -6
  290. package/dist/lcm/tools.d.ts +1 -1
  291. package/dist/lifecycle.d.ts +1 -1
  292. package/dist/live-connectors-runner.d.ts +1 -1
  293. package/dist/local-llm.d.ts +2 -1
  294. package/dist/local-llm.js +2 -2
  295. package/dist/local-model-endpoint.d.ts +1 -1
  296. package/dist/maintenance/memory-governance.d.ts +1 -1
  297. package/dist/maintenance/memory-governance.js +33 -12
  298. package/dist/maintenance/rebuild-memory-lifecycle-ledger.d.ts +2 -2
  299. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +33 -12
  300. package/dist/maintenance/rebuild-memory-projection.d.ts +2 -2
  301. package/dist/maintenance/rebuild-memory-projection.js +34 -13
  302. package/dist/{maintenance-BJzNIKBE.d.ts → maintenance-C8aRWfhq.d.ts} +3 -3
  303. package/dist/mcp-memory-inspector-app.d.ts +22 -21
  304. package/dist/mcp-read-only-tools.js +1 -1
  305. package/dist/memory-action-policy.d.ts +1 -1
  306. package/dist/memory-cache.d.ts +1 -1
  307. package/dist/memory-lifecycle-ledger-utils.d.ts +1 -1
  308. package/dist/memory-projection-mutations.js +2 -2
  309. package/dist/memory-projection-store.d.ts +6 -2
  310. package/dist/memory-projection-store.js +1 -1
  311. package/dist/memory-provenance.d.ts +1 -1
  312. package/dist/memory-snapshot.d.ts +1 -1
  313. package/dist/memory-worth-outcomes.d.ts +2 -2
  314. package/dist/models-json.d.ts +1 -1
  315. package/dist/namespaces/migrate.d.ts +3 -3
  316. package/dist/namespaces/migrate.js +24 -23
  317. package/dist/namespaces/principal.d.ts +1 -1
  318. package/dist/namespaces/search.d.ts +1 -1
  319. package/dist/namespaces/search.js +8 -8
  320. package/dist/namespaces/storage.d.ts +3 -3
  321. package/dist/namespaces/storage.js +33 -12
  322. package/dist/native-knowledge.d.ts +1 -1
  323. package/dist/offline-sync-file-io.d.ts +20 -0
  324. package/dist/offline-sync-file-io.js +18 -0
  325. package/dist/offline-sync-file-io.js.map +1 -0
  326. package/dist/offline-sync-impression-drain.d.ts +1 -1
  327. package/dist/offline-sync-impression-drain.js +5 -5
  328. package/dist/offline-sync.d.ts +8 -6
  329. package/dist/offline-sync.js +2 -1
  330. package/dist/operator-doctor-corpus.d.ts +1 -1
  331. package/dist/operator-doctor-corpus.js +26 -25
  332. package/dist/operator-doctor-replica.d.ts +1 -1
  333. package/dist/operator-toolkit.d.ts +3 -3
  334. package/dist/operator-toolkit.js +39 -36
  335. package/dist/orchestration/compression-guideline-coordinator.d.ts +2 -2
  336. package/dist/orchestration/maintenance.d.ts +4 -4
  337. package/dist/orchestration/maintenance.js +38 -18
  338. package/dist/{orchestrator-BiXgBJBQ.d.ts → orchestrator-D85CzDDY.d.ts} +11 -8
  339. package/dist/orchestrator.d.ts +9 -9
  340. package/dist/orchestrator.js +117 -109
  341. package/dist/patterns-cli.d.ts +1 -1
  342. package/dist/{pipeline-sz_dcuLg.d.ts → pipeline-DPcUifCO.d.ts} +1 -1
  343. package/dist/policy-runtime.d.ts +1 -1
  344. package/dist/proactive-contention.d.ts +1 -1
  345. package/dist/provenance.d.ts +1 -1
  346. package/dist/public-http-D-SyWNj6.d.ts +25 -0
  347. package/dist/{purge-533FZLS4.js → purge-GB7PZHT2.js} +2 -2
  348. package/dist/{qmd-eAWObK4M.d.ts → qmd-dFCHp1r0.d.ts} +1 -1
  349. package/dist/qmd-preflight.d.ts +2 -2
  350. package/dist/qmd-recall-cache.d.ts +1 -1
  351. package/dist/qmd.d.ts +2 -2
  352. package/dist/recall-concurrency-config.d.ts +1 -1
  353. package/dist/recall-disclosure-escalation.d.ts +1 -1
  354. package/dist/recall-explain-renderer.d.ts +1 -1
  355. package/dist/recall-explain-renderer.js +3 -3
  356. package/dist/recall-memory-map.d.ts +1 -1
  357. package/dist/recall-planner-llm.d.ts +1 -1
  358. package/dist/recall-planner-llm.js +1 -1
  359. package/dist/recall-state.d.ts +1 -1
  360. package/dist/recall-tag-filter.d.ts +1 -1
  361. package/dist/recall-timings.d.ts +1 -1
  362. package/dist/recall-xray-cli.d.ts +1 -1
  363. package/dist/recall-xray-cli.js +4 -4
  364. package/dist/recall-xray-renderer.d.ts +1 -1
  365. package/dist/recall-xray-renderer.js +3 -3
  366. package/dist/recall-xray.d.ts +1 -1
  367. package/dist/recall-xray.js +2 -2
  368. package/dist/reconcile/cursor.d.ts +2 -1
  369. package/dist/reconcile/manifest.d.ts +2 -1
  370. package/dist/reconcile/manifest.js +6 -5
  371. package/dist/reconcile/plan.d.ts +2 -1
  372. package/dist/reconcile/plan.js +3 -2
  373. package/dist/replay/normalizers/chatgpt.d.ts +1 -1
  374. package/dist/replay/normalizers/claude.d.ts +1 -1
  375. package/dist/replay/normalizers/openclaw.d.ts +1 -1
  376. package/dist/replay/normalizers/shared.d.ts +1 -1
  377. package/dist/replay/runner.d.ts +1 -1
  378. package/dist/replay/types.d.ts +1 -1
  379. package/dist/replica-divergence.d.ts +1 -1
  380. package/dist/replica-peers-config.d.ts +1 -1
  381. package/dist/resolve-auth-token.d.ts +1 -1
  382. package/dist/resume-bundles.js +5 -3
  383. package/dist/retrieval-agents.d.ts +2 -2
  384. package/dist/retrieval-tiers.d.ts +1 -1
  385. package/dist/routing/engine.d.ts +1 -1
  386. package/dist/routing/store.d.ts +1 -1
  387. package/dist/salvage-envelope.d.ts +1 -1
  388. package/dist/schemas.d.ts +38 -38
  389. package/dist/{scope-profiles-BBQsXuyj.d.ts → scope-profiles-DICK7GtN.d.ts} +1 -1
  390. package/dist/search/embed-helper.d.ts +1 -1
  391. package/dist/search/factory.d.ts +1 -1
  392. package/dist/search/factory.js +6 -6
  393. package/dist/search/index.d.ts +1 -1
  394. package/dist/search/index.js +12 -12
  395. package/dist/search/lancedb-backend.d.ts +1 -1
  396. package/dist/search/meilisearch-backend.d.ts +1 -1
  397. package/dist/search/noop-backend.d.ts +1 -1
  398. package/dist/search/orama-backend.d.ts +1 -1
  399. package/dist/search/port.d.ts +1 -1
  400. package/dist/search/remote-backend.d.ts +1 -1
  401. package/dist/{semantic-consolidation-HD8RkzVA.d.ts → semantic-consolidation-BP7NAX0N.d.ts} +1 -1
  402. package/dist/semantic-consolidation.d.ts +2 -2
  403. package/dist/semantic-consolidation.js +34 -13
  404. package/dist/semantic-rule-promotion.js +33 -12
  405. package/dist/semantic-rule-verifier.d.ts +1 -1
  406. package/dist/semantic-rule-verifier.js +33 -12
  407. package/dist/{service-DIrtHHEg.d.ts → service-DT-pJyda.d.ts} +660 -660
  408. package/dist/session-observer-bands.d.ts +1 -1
  409. package/dist/session-observer-state.d.ts +1 -1
  410. package/dist/shared-context/manager.d.ts +1 -1
  411. package/dist/signal.d.ts +1 -1
  412. package/dist/source-agent-qualifier.d.ts +1 -1
  413. package/dist/source-agent-qualifier.js +31 -15
  414. package/dist/{storage-DqCzXi56.d.ts → storage-qOnaJ1Lw.d.ts} +49 -13
  415. package/dist/storage.d.ts +2 -2
  416. package/dist/storage.js +33 -12
  417. package/dist/summarizer.d.ts +1 -1
  418. package/dist/summarizer.js +5 -5
  419. package/dist/summary-snapshot.d.ts +1 -1
  420. package/dist/support-passport/index.d.ts +518 -0
  421. package/dist/support-passport/index.js +357 -0
  422. package/dist/support-passport/index.js.map +1 -0
  423. package/dist/temporal-supersession.d.ts +2 -2
  424. package/dist/temporal-timeline-recall.d.ts +1 -1
  425. package/dist/temporal-validity.d.ts +1 -1
  426. package/dist/threading.d.ts +1 -1
  427. package/dist/tier-migration.d.ts +2 -2
  428. package/dist/tier-routing.d.ts +2 -2
  429. package/dist/tier-routing.js +7 -1
  430. package/dist/{tier-stats-2JCZYEF7.js → tier-stats-EXYSKSVN.js} +8 -2
  431. package/dist/{tier-stats-2JCZYEF7.js.map → tier-stats-EXYSKSVN.js.map} +1 -1
  432. package/dist/topics.d.ts +1 -1
  433. package/dist/transcript.d.ts +1 -1
  434. package/dist/transfer/autodetect.js +1 -1
  435. package/dist/transfer/backup.js +1 -1
  436. package/dist/transfer/capsule-export.js +2 -2
  437. package/dist/transfer/types.d.ts +36 -36
  438. package/dist/trust-score-stage.d.ts +1 -1
  439. package/dist/trust-score.d.ts +1 -1
  440. package/dist/{types-d_j4vC6N.d.ts → types-ed-SvS2t.d.ts} +8 -1
  441. package/dist/types.d.ts +1 -1
  442. package/dist/types.js +1 -1
  443. package/dist/utility-runtime.d.ts +2 -2
  444. package/dist/verified-recall.js +33 -12
  445. package/dist/write-envelope.d.ts +1 -1
  446. package/dist/write-rate-limiter.d.ts +11 -5
  447. package/dist/write-rate-limiter.js +1 -1
  448. package/package.json +12 -2
  449. package/scripts/build-with-heap.mjs +11 -1
  450. package/src/access-authorization-probe.ts +20 -2
  451. package/src/access-boundary.ts +32 -0
  452. package/src/access-http.test.ts +1971 -643
  453. package/src/access-http.ts +81 -74
  454. package/src/access-mcp.ts +3 -1
  455. package/src/access-memory-search-fanout.ts +24 -1
  456. package/src/access-offline-manifest.ts +5 -0
  457. package/src/access-operations.ts +2 -0
  458. package/src/access-service-namespace.test.ts +14 -0
  459. package/src/access-service-observe-scope.test.ts +2 -0
  460. package/src/access-service-offline-file-content.test.ts +211 -0
  461. package/src/access-service.ts +628 -884
  462. package/src/access-surface-catalog.test.ts +40 -10
  463. package/src/access-surface-catalog.ts +15 -0
  464. package/src/active-memory-bridge.test.ts +151 -0
  465. package/src/active-memory-bridge.ts +95 -21
  466. package/src/briefing.test.ts +38 -0
  467. package/src/briefing.ts +3 -3
  468. package/src/cli.ts +6 -1
  469. package/src/config.ts +18 -9
  470. package/src/connectors/codex-materialize-runner.ts +2 -0
  471. package/src/contradiction/contradiction-scan.ts +2 -0
  472. package/src/contradiction/contradiction.test.ts +42 -0
  473. package/src/correction/correction-access-wiring.ts +8 -2
  474. package/src/correction/correction-storage-integration.test.ts +7 -0
  475. package/src/explicit-capture.test.ts +24 -0
  476. package/src/explicit-capture.ts +2 -0
  477. package/src/fallback-llm.test.ts +54 -0
  478. package/src/fallback-llm.ts +54 -9
  479. package/src/index.ts +3 -0
  480. package/src/inline-script.ts +6 -0
  481. package/src/local-llm-headers-timeout.test.ts +42 -0
  482. package/src/local-llm.ts +8 -8
  483. package/src/maintenance/first-start-migration.ts +2 -1
  484. package/src/maintenance/memory-governance.test.ts +25 -0
  485. package/src/maintenance/memory-governance.ts +4 -1
  486. package/src/maintenance/pattern-reinforcement.test.ts +20 -0
  487. package/src/maintenance/pattern-reinforcement.ts +2 -1
  488. package/src/maintenance/rebuild-memory-projection.ts +8 -8
  489. package/src/mcp-read-only-tools.ts +3 -0
  490. package/src/memory-projection-store.ts +25 -27
  491. package/src/offline-sync-file-io.ts +68 -0
  492. package/src/offline-sync.ts +43 -57
  493. package/src/orchestration/consolidation-run.ts +5 -4
  494. package/src/orchestration/contradiction-linking-coordinator.ts +7 -0
  495. package/src/orchestration/contradiction-localization.integration.test.ts +115 -0
  496. package/src/orchestration/lifecycle-policy-coordinator.ts +2 -1
  497. package/src/orchestration/qmd-result-resolver.test.ts +215 -14
  498. package/src/orchestration/qmd-result-resolver.ts +142 -82
  499. package/src/orchestration/recall-search-pipeline.test.ts +41 -0
  500. package/src/orchestration/recall-search-pipeline.ts +12 -0
  501. package/src/orchestration/semantic-consolidation-coordinator.ts +2 -1
  502. package/src/orchestration/tier-migration-coordinator.ts +23 -56
  503. package/src/orchestration/workspace-ops.test.ts +37 -0
  504. package/src/orchestration/workspace-ops.ts +5 -29
  505. package/src/orchestrator.ts +7 -8
  506. package/src/relay/mission-access.test.ts +11 -11
  507. package/src/relay/mission-ui.test.ts +3 -1
  508. package/src/storage/supersession-audit.ts +7 -1
  509. package/src/storage/supersession-side-effects.ts +17 -5
  510. package/src/storage/tombstone-blocked-capture-sync.ts +31 -0
  511. package/src/storage-contract/failure-semantics.test.ts +102 -0
  512. package/src/storage.ts +18 -19
  513. package/src/support-passport/access-http-base.ts +143 -0
  514. package/src/support-passport/access-http.ts +172 -0
  515. package/src/support-passport/access-operations.ts +272 -0
  516. package/src/support-passport/access-service-base.test.ts +193 -0
  517. package/src/support-passport/access-service-base.ts +176 -0
  518. package/src/support-passport/access-surface.test.ts +283 -0
  519. package/src/support-passport/access-surface.ts +286 -0
  520. package/src/support-passport/card-projection.test.ts +137 -0
  521. package/src/support-passport/card-projection.ts +348 -0
  522. package/src/support-passport/card-replacement.ts +126 -0
  523. package/src/support-passport/card-service-commit-forwarding.test.ts +160 -0
  524. package/src/support-passport/card-service.test.ts +2964 -0
  525. package/src/support-passport/card-service.ts +1184 -0
  526. package/src/support-passport/card-state.ts +158 -0
  527. package/src/support-passport/config-model-routing.test.ts +37 -0
  528. package/src/support-passport/config.test.ts +34 -0
  529. package/src/support-passport/config.ts +41 -0
  530. package/src/support-passport/contracts.test.ts +156 -0
  531. package/src/support-passport/contracts.ts +149 -0
  532. package/src/support-passport/errors.ts +30 -0
  533. package/src/support-passport/generated-batch.test.ts +185 -0
  534. package/src/support-passport/generated-batch.ts +338 -0
  535. package/src/support-passport/grant-contracts.ts +183 -0
  536. package/src/support-passport/grant-generated-batch-scope.test.ts +201 -0
  537. package/src/support-passport/grant-service-cache.test.ts +148 -0
  538. package/src/support-passport/grant-service-cancellation.test.ts +83 -0
  539. package/src/support-passport/grant-service-marker-cache.test.ts +62 -0
  540. package/src/support-passport/grant-service.test.ts +3496 -0
  541. package/src/support-passport/grant-service.ts +513 -0
  542. package/src/support-passport/grant-store-commit-hook.test.ts +123 -0
  543. package/src/support-passport/grant-store-order.test.ts +60 -0
  544. package/src/support-passport/grant-store-path-safety.test.ts +30 -0
  545. package/src/support-passport/grant-store-recovery.test.ts +448 -0
  546. package/src/support-passport/grant-store.ts +906 -0
  547. package/src/support-passport/index.ts +17 -0
  548. package/src/support-passport/mcp-tools.test.ts +222 -0
  549. package/src/support-passport/mcp-tools.ts +187 -0
  550. package/src/support-passport/model-adapter.test.ts +840 -0
  551. package/src/support-passport/model-adapter.ts +602 -0
  552. package/src/support-passport/model-audit-pinned-write.test.ts +59 -0
  553. package/src/support-passport/model-audit.ts +152 -0
  554. package/src/support-passport/model-bridge.test.ts +456 -0
  555. package/src/support-passport/model-bridge.ts +439 -0
  556. package/src/support-passport/model-contracts.ts +124 -0
  557. package/src/support-passport/model-service.test.ts +1731 -0
  558. package/src/support-passport/model-service.ts +399 -0
  559. package/src/support-passport/offline-sync-guard.ts +230 -0
  560. package/src/support-passport/owner-http.test.ts +671 -0
  561. package/src/support-passport/owner-lock.ts +70 -0
  562. package/src/support-passport/private-file.test.ts +99 -0
  563. package/src/support-passport/private-file.ts +498 -0
  564. package/src/support-passport/public-http.test.ts +476 -0
  565. package/src/support-passport/public-http.ts +601 -0
  566. package/src/support-passport/ui.test.ts +181 -0
  567. package/src/tier-routing.ts +28 -17
  568. package/src/training-export/converter.test.ts +24 -0
  569. package/src/training-export/converter.ts +2 -0
  570. package/src/types.ts +8 -1
  571. package/src/wearables/service.test.ts +13 -0
  572. package/src/wearables/service.ts +2 -0
  573. package/src/write-rate-limiter.test.ts +30 -6
  574. package/src/write-rate-limiter.ts +29 -18
  575. package/dist/chunk-3CSX3ETE.js.map +0 -1
  576. package/dist/chunk-47P3RYZP.js.map +0 -1
  577. package/dist/chunk-6LAPLZOS.js.map +0 -1
  578. package/dist/chunk-6R4G7N4S.js.map +0 -1
  579. package/dist/chunk-7BZOBA5J.js.map +0 -1
  580. package/dist/chunk-C23BYTJV.js.map +0 -1
  581. package/dist/chunk-CYF6WVE7.js.map +0 -1
  582. package/dist/chunk-G2LUMAUF.js.map +0 -1
  583. package/dist/chunk-GYPQL6XN.js.map +0 -1
  584. package/dist/chunk-JDZPNCMA.js.map +0 -1
  585. package/dist/chunk-JV5YUBIZ.js.map +0 -1
  586. package/dist/chunk-K4QS7LXU.js.map +0 -1
  587. package/dist/chunk-L6CQ7WIW.js.map +0 -1
  588. package/dist/chunk-MKQBXQKU.js.map +0 -1
  589. package/dist/chunk-NH5IR2WM.js.map +0 -1
  590. package/dist/chunk-PX2LSHYM.js.map +0 -1
  591. package/dist/chunk-QWGE2PUH.js.map +0 -1
  592. package/dist/chunk-RUMNY5DT.js.map +0 -1
  593. package/dist/chunk-S75M5ZRK.js.map +0 -1
  594. package/dist/chunk-T2JHZWH6.js.map +0 -1
  595. package/dist/chunk-UES4BHA2.js.map +0 -1
  596. package/dist/chunk-UTRO5UJU.js.map +0 -1
  597. package/dist/chunk-UU3G6GUP.js.map +0 -1
  598. package/dist/chunk-XUAMCLY4.js.map +0 -1
  599. package/dist/chunk-ZYLC7NFO.js.map +0 -1
  600. package/dist/contradiction-scan-HYLIRMJ2.js +0 -14
  601. package/dist/first-start-migration-626C5554.js.map +0 -1
  602. /package/dist/{auto-sync-TDHLZPVP.js.map → auto-sync-NR3GPCOA.js.map} +0 -0
  603. /package/dist/{chunk-LLJMAULE.js.map → chunk-2FKEIPIT.js.map} +0 -0
  604. /package/dist/{chunk-JBPKEARU.js.map → chunk-2QSZNTDO.js.map} +0 -0
  605. /package/dist/{chunk-PDDTI7IC.js.map → chunk-43SEBY2M.js.map} +0 -0
  606. /package/dist/{chunk-HWRD23DW.js.map → chunk-46SZBVDS.js.map} +0 -0
  607. /package/dist/{chunk-6HPHEFXT.js.map → chunk-5FD7JMTX.js.map} +0 -0
  608. /package/dist/{chunk-IBKR2TFR.js.map → chunk-6CYHUWCO.js.map} +0 -0
  609. /package/dist/{chunk-YN6ASZUG.js.map → chunk-6QPI2ITJ.js.map} +0 -0
  610. /package/dist/{chunk-NIS256NW.js.map → chunk-CIAI24TQ.js.map} +0 -0
  611. /package/dist/{chunk-7FGL6CGA.js.map → chunk-DVYRD4HY.js.map} +0 -0
  612. /package/dist/{chunk-G7Z6EENO.js.map → chunk-EKTP7MNS.js.map} +0 -0
  613. /package/dist/{chunk-RZ4XGKOJ.js.map → chunk-FFRTNF3L.js.map} +0 -0
  614. /package/dist/{chunk-VKXWK44Q.js.map → chunk-FONWBCVY.js.map} +0 -0
  615. /package/dist/{chunk-DZD576ZU.js.map → chunk-FPLM2TKD.js.map} +0 -0
  616. /package/dist/{chunk-ZPJ4QSGP.js.map → chunk-FTPONF6B.js.map} +0 -0
  617. /package/dist/{chunk-LJS7KUPV.js.map → chunk-FWX7W5PM.js.map} +0 -0
  618. /package/dist/{chunk-G4T7L557.js.map → chunk-GN3WRFJY.js.map} +0 -0
  619. /package/dist/{chunk-QT2FRYI4.js.map → chunk-H7NAZ3UB.js.map} +0 -0
  620. /package/dist/{chunk-APPFNGX6.js.map → chunk-HBUFQRIM.js.map} +0 -0
  621. /package/dist/{chunk-UU7TRFNE.js.map → chunk-HGJ6NJ4E.js.map} +0 -0
  622. /package/dist/{chunk-C4ZNGVK3.js.map → chunk-IKP5OYQQ.js.map} +0 -0
  623. /package/dist/{chunk-KQAFEZQX.js.map → chunk-IPLYGWQF.js.map} +0 -0
  624. /package/dist/{chunk-RDJRFDJY.js.map → chunk-IQ4NVWEF.js.map} +0 -0
  625. /package/dist/{chunk-CGVHPCP2.js.map → chunk-JYG4OLWH.js.map} +0 -0
  626. /package/dist/{chunk-WOPA3SGI.js.map → chunk-K26NWEOC.js.map} +0 -0
  627. /package/dist/{chunk-G7A5HLUS.js.map → chunk-KJH6W2I7.js.map} +0 -0
  628. /package/dist/{chunk-HOIRB77A.js.map → chunk-L6Z5OAS4.js.map} +0 -0
  629. /package/dist/{chunk-227UFSEQ.js.map → chunk-LEPOP46W.js.map} +0 -0
  630. /package/dist/{chunk-3JLPGJ43.js.map → chunk-MIH3CJPN.js.map} +0 -0
  631. /package/dist/{chunk-QHFV6VNU.js.map → chunk-MK5VEWCR.js.map} +0 -0
  632. /package/dist/{chunk-GUKBW4HL.js.map → chunk-NQMSEB5D.js.map} +0 -0
  633. /package/dist/{chunk-WRJVB3C2.js.map → chunk-PO7NXBG2.js.map} +0 -0
  634. /package/dist/{chunk-AZM4CCAC.js.map → chunk-QZLDRQ4I.js.map} +0 -0
  635. /package/dist/{chunk-THPPCISA.js.map → chunk-RBII7637.js.map} +0 -0
  636. /package/dist/{chunk-VMMQDEN5.js.map → chunk-TO7ZTWJW.js.map} +0 -0
  637. /package/dist/{chunk-K2TDK7GG.js.map → chunk-TWTUGCTY.js.map} +0 -0
  638. /package/dist/{chunk-HLYR2TQL.js.map → chunk-UCTS3DDH.js.map} +0 -0
  639. /package/dist/{chunk-JYIJNNN3.js.map → chunk-UG6VXIOK.js.map} +0 -0
  640. /package/dist/{chunk-WM65LU67.js.map → chunk-UKYS3NU5.js.map} +0 -0
  641. /package/dist/{chunk-WDBX6VER.js.map → chunk-UPLZNJKN.js.map} +0 -0
  642. /package/dist/{chunk-2GFK22OZ.js.map → chunk-V6ERBR7U.js.map} +0 -0
  643. /package/dist/{chunk-ZMYDYNI4.js.map → chunk-XZHE2DEN.js.map} +0 -0
  644. /package/dist/{chunk-MAXVH44E.js.map → chunk-Z54EM5YZ.js.map} +0 -0
  645. /package/dist/{chunk-YGLY6QFV.js.map → chunk-ZRZVLB7O.js.map} +0 -0
  646. /package/dist/{chunk-4IIFL7GC.js.map → chunk-ZUKIUXHQ.js.map} +0 -0
  647. /package/dist/{contradiction-scan-HYLIRMJ2.js.map → contradiction-scan-JDTBXRGW.js.map} +0 -0
  648. /package/dist/{purge-533FZLS4.js.map → purge-GB7PZHT2.js.map} +0 -0
@@ -0,0 +1,1594 @@
1
+ (function startWhatHelpsMe() {
2
+ const bootstrapHash = window.__REMNIC_WHAT_HELPS_ME_FRAGMENT__;
3
+ const initialHash = typeof bootstrapHash === "string" ? bootstrapHash : window.location.hash;
4
+ const hasSecretFragment = new URLSearchParams(initialHash.slice(1)).has("secret");
5
+ if (hasSecretFragment) {
6
+ window.history.replaceState(null, "", `${window.location.pathname}${window.location.search}`);
7
+ }
8
+ const model = window.WhatHelpsMeModel;
9
+ if (!model) throw new Error("What Helps Me model did not load.");
10
+
11
+ const byId = (id) => document.getElementById(id);
12
+ const params = new URLSearchParams(window.location.search);
13
+ const replayMode = params.get("mode") === "replay";
14
+ const grantId = params.get("grant") ?? "";
15
+ const requestedReplayChannelId = params.get("replayChannel") ?? "";
16
+ const replayChannelId = replayMode
17
+ ? grantId
18
+ ? requestedReplayChannelId
19
+ : crypto.randomUUID()
20
+ : "";
21
+ let initialHelperSecret = model.parseSecret(initialHash);
22
+ const replayStore = replayMode ? model.createReplayStore() : null;
23
+ const replayChannel =
24
+ replayMode && /^[0-9a-f-]{36}$/i.test(replayChannelId) && "BroadcastChannel" in window
25
+ ? new BroadcastChannel(`remnic-what-helps-me-replay-${replayChannelId}`)
26
+ : null;
27
+ const HELPER_REVALIDATION_MS = 30_000;
28
+ const HELPER_REVALIDATION_MAX_MS = 5 * 60_000;
29
+ const HELPER_READ_TIMEOUT_MS = 10_000;
30
+ const HELPER_QUESTION_TIMEOUT_MS = 15 * 60_000;
31
+ const OWNER_READ_TIMEOUT_MS = 30_000;
32
+ const OWNER_WRITE_TIMEOUT_MS = 60_000;
33
+ const OWNER_MODEL_WRITE_TIMEOUT_MS = 15 * 60_000;
34
+ const OWNER_RECONCILIATION_ATTEMPTS = 4;
35
+ const OWNER_RECONCILIATION_DELAY_MS = 250;
36
+ const MAX_VISIBLE_GRANTS = 100;
37
+ const MAX_SHARED_CARDS = 8;
38
+ const PAGE_HIDDEN_ABORT = new Error("The page was hidden.");
39
+ const HELPER_READ_TIMEOUT_ABORT = Object.assign(new Error("The support passport check took too long."), {
40
+ code: "request_timeout",
41
+ });
42
+ const SCROLL_BEHAVIOR = window.matchMedia?.("(prefers-reduced-motion: reduce)").matches ? "auto" : "smooth";
43
+ const TERMINAL_HELPER_ERROR_CODES = new Set([
44
+ "grant_expired",
45
+ "grant_gone",
46
+ "grant_not_found",
47
+ "grant_stale",
48
+ "missing_link",
49
+ "session_ended",
50
+ ]);
51
+ const REPLAY_SYNC_TIMEOUT_MS = 2_000;
52
+
53
+ const state = {
54
+ token: "",
55
+ cards: [],
56
+ grants: [],
57
+ selectedNotes: [],
58
+ guide: null,
59
+ helperSecret: initialHelperSecret,
60
+ helperGrantId: grantId,
61
+ helperServerOffsetMs: null,
62
+ helperExpiryTimer: null,
63
+ helperLoadController: null,
64
+ helperQuestionController: null,
65
+ helperRevalidationController: null,
66
+ helperRevalidationTimer: null,
67
+ helperRevalidationDelayMs: HELPER_REVALIDATION_MS,
68
+ helperViewGeneration: 0,
69
+ helperLifecyclePaused: false,
70
+ ownerServerOffsetMs: null,
71
+ pendingCardMutationIds: new Set(),
72
+ pendingGrantRevocationIds: new Set(),
73
+ cardSavePending: false,
74
+ notePreviewPending: false,
75
+ draftGenerationPending: false,
76
+ replayInvalidatedCardIds: new Set(),
77
+ shareCreationPending: false,
78
+ shareCreationCardIds: null,
79
+ displayedGrant: null,
80
+ displayedGrantTimer: null,
81
+ ownerGrantExpiryTimer: null,
82
+ ownerLifecyclePaused: false,
83
+ ownerRequestControllers: new Set(),
84
+ ownerLoadGeneration: 0,
85
+ ownerSessionGeneration: 0,
86
+ announcementTimer: null,
87
+ toastTimer: null,
88
+ };
89
+ initialHelperSecret = "";
90
+
91
+ function element(tag, className, text) {
92
+ const node = document.createElement(tag);
93
+ if (className) node.className = className;
94
+ if (text !== undefined) node.textContent = text;
95
+ return node;
96
+ }
97
+
98
+ function clear(node) {
99
+ node.replaceChildren();
100
+ }
101
+
102
+ function setError(id, message = "") {
103
+ if (message === null) return;
104
+ if (message && (state.ownerLifecyclePaused || state.helperLifecyclePaused)) return;
105
+ byId(id).textContent = message;
106
+ }
107
+
108
+ function ownerNowMs() {
109
+ return Date.now() + (state.ownerServerOffsetMs ?? 0);
110
+ }
111
+
112
+ function ownerSessionIsCurrent(generation) {
113
+ return !state.ownerLifecyclePaused && generation === state.ownerSessionGeneration;
114
+ }
115
+
116
+ function clearDisplayedShareLink() {
117
+ window.clearTimeout(state.displayedGrantTimer);
118
+ state.displayedGrantTimer = null;
119
+ state.displayedGrant = null;
120
+ byId("newLinkPanel").hidden = true;
121
+ byId("shareLinkInput").value = "";
122
+ byId("openLinkButton").removeAttribute("href");
123
+ }
124
+
125
+ function scheduleDisplayedShareLinkExpiry() {
126
+ window.clearTimeout(state.displayedGrantTimer);
127
+ state.displayedGrantTimer = null;
128
+ if (!state.displayedGrant) return;
129
+ const remainingMs = Date.parse(state.displayedGrant.expiresAt) - ownerNowMs();
130
+ if (remainingMs <= 0) {
131
+ clearDisplayedShareLink();
132
+ return;
133
+ }
134
+ state.displayedGrantTimer = window.setTimeout(clearDisplayedShareLink, remainingMs);
135
+ }
136
+
137
+ function reconcileDisplayedShareLink() {
138
+ const displayed = state.displayedGrant;
139
+ if (!displayed) return;
140
+ const grant = state.grants.find((candidate) => candidate.grantId === displayed.grantId);
141
+ const cardsStillCurrent = displayed.cards.every((selected) =>
142
+ state.cards.some(
143
+ (card) => card.cardId === selected.cardId && card.revision === selected.revision && card.status === "active"
144
+ )
145
+ );
146
+ if (!grant || grant.status !== "active" || Date.parse(grant.expiresAt) <= ownerNowMs() || !cardsStillCurrent) {
147
+ clearDisplayedShareLink();
148
+ return;
149
+ }
150
+ scheduleDisplayedShareLinkExpiry();
151
+ }
152
+
153
+ function clearDisplayedShareLinkForCard(cardId) {
154
+ if (state.displayedGrant?.cards.some((card) => card.cardId === cardId)) clearDisplayedShareLink();
155
+ }
156
+
157
+ function announce(message) {
158
+ window.clearTimeout(state.announcementTimer);
159
+ byId("announcer").textContent = "";
160
+ state.announcementTimer = window.setTimeout(() => {
161
+ state.announcementTimer = null;
162
+ byId("announcer").textContent = message;
163
+ }, 10);
164
+ }
165
+
166
+ function toast(message) {
167
+ const node = byId("toast");
168
+ window.clearTimeout(state.toastTimer);
169
+ node.textContent = message;
170
+ node.classList.add("visible");
171
+ state.toastTimer = window.setTimeout(() => node.classList.remove("visible"), 3_600);
172
+ }
173
+
174
+ function errorMessage(error, fallback) {
175
+ if (error === PAGE_HIDDEN_ABORT) return null;
176
+ if (error instanceof Error && error.message.trim()) return error.message;
177
+ return fallback;
178
+ }
179
+
180
+ async function withBusy(button, label, run) {
181
+ const prior = button.textContent;
182
+ button.disabled = true;
183
+ button.textContent = label;
184
+ try {
185
+ return await run();
186
+ } finally {
187
+ button.disabled = false;
188
+ button.textContent = prior;
189
+ }
190
+ }
191
+
192
+ async function fetchJson(path, options = {}) {
193
+ if (options.owner && state.ownerLifecyclePaused) throw PAGE_HIDDEN_ABORT;
194
+ const method = options.method ?? "GET";
195
+ const timeoutMs =
196
+ options.timeoutMs ??
197
+ (options.owner ? (method === "GET" ? OWNER_READ_TIMEOUT_MS : OWNER_WRITE_TIMEOUT_MS) : undefined);
198
+ const controller = Number.isFinite(timeoutMs) ? new AbortController() : null;
199
+ let timedOut = false;
200
+ let timeout;
201
+ let removeCallerAbort = () => {};
202
+ if (controller && options.signal) {
203
+ const abortFromCaller = () => controller.abort(options.signal.reason);
204
+ if (options.signal.aborted) abortFromCaller();
205
+ else options.signal.addEventListener("abort", abortFromCaller, { once: true });
206
+ removeCallerAbort = () => options.signal.removeEventListener("abort", abortFromCaller);
207
+ }
208
+ if (controller) {
209
+ timeout = window.setTimeout(() => {
210
+ timedOut = true;
211
+ controller.abort(new Error("The request took too long."));
212
+ }, timeoutMs);
213
+ }
214
+ if (options.owner && controller) state.ownerRequestControllers.add(controller);
215
+ const headers = new Headers(options.headers ?? {});
216
+ headers.set("accept", "application/json");
217
+ if (options.owner) headers.set("authorization", `Bearer ${state.token}`);
218
+ if (options.secret) headers.set("authorization", `SupportPassport ${options.secret}`);
219
+ let body;
220
+ if (options.body !== undefined) {
221
+ headers.set("content-type", "application/json");
222
+ body = JSON.stringify(options.body);
223
+ }
224
+ try {
225
+ const response = await fetch(path, {
226
+ method,
227
+ headers,
228
+ body,
229
+ cache: "no-store",
230
+ signal: controller?.signal ?? options.signal,
231
+ });
232
+ if ((controller?.signal ?? options.signal)?.reason === PAGE_HIDDEN_ABORT) throw PAGE_HIDDEN_ABORT;
233
+ let payload;
234
+ try {
235
+ payload = await response.json();
236
+ } catch {
237
+ throw new Error(`The server returned invalid JSON with HTTP ${response.status}.`);
238
+ }
239
+ if (!response.ok) {
240
+ const error = new Error(
241
+ typeof payload.error === "string" ? payload.error : `The server returned HTTP ${response.status}.`
242
+ );
243
+ error.code = typeof payload.code === "string" ? payload.code : "request_failed";
244
+ error.status = response.status;
245
+ throw error;
246
+ }
247
+ if (options.owner && state.ownerLifecyclePaused) throw PAGE_HIDDEN_ABORT;
248
+ const serverNowMs = Date.parse(response.headers.get("date") ?? "");
249
+ if (options.owner && Number.isFinite(serverNowMs)) {
250
+ state.ownerServerOffsetMs = serverNowMs - Date.now();
251
+ }
252
+ if (!options.captureServerTime) return payload;
253
+ return {
254
+ payload,
255
+ serverOffsetMs: Number.isFinite(serverNowMs) ? serverNowMs - Date.now() : null,
256
+ };
257
+ } catch (error) {
258
+ if (
259
+ (controller?.signal ?? options.signal)?.reason === PAGE_HIDDEN_ABORT ||
260
+ (options.owner && state.ownerLifecyclePaused) ||
261
+ (options.secret && state.helperLifecyclePaused)
262
+ ) {
263
+ throw PAGE_HIDDEN_ABORT;
264
+ }
265
+ if (!timedOut) throw error;
266
+ const timeoutError = new Error(options.timeoutMessage ?? "The owner request took too long. Try again.");
267
+ timeoutError.code = "request_timeout";
268
+ throw timeoutError;
269
+ } finally {
270
+ if (timeout) window.clearTimeout(timeout);
271
+ if (options.owner && controller) state.ownerRequestControllers.delete(controller);
272
+ removeCallerAbort();
273
+ }
274
+ }
275
+
276
+ const liveApi = {
277
+ listCards: () => fetchJson("/engram/v1/support-passport/cards", { owner: true }),
278
+ listGrants: () => fetchJson("/engram/v1/support-passport/grants", { owner: true }),
279
+ fetchMemory: (memoryId) =>
280
+ fetchJson(`/engram/v1/support-passport/memories/${encodeURIComponent(memoryId)}`, { owner: true }),
281
+ createManualDraft: (input) =>
282
+ fetchJson("/engram/v1/support-passport/drafts", { owner: true, method: "POST", body: input }),
283
+ generateDrafts: (input) =>
284
+ fetchJson("/engram/v1/support-passport/drafts/generate", {
285
+ owner: true,
286
+ method: "POST",
287
+ body: input,
288
+ timeoutMs: OWNER_MODEL_WRITE_TIMEOUT_MS,
289
+ timeoutMessage: "Drafting timed out.",
290
+ }),
291
+ replaceCard: (cardId, input) =>
292
+ fetchJson(`/engram/v1/support-passport/cards/${encodeURIComponent(cardId)}`, {
293
+ owner: true,
294
+ method: "PUT",
295
+ body: input,
296
+ }),
297
+ mutateCard: (cardId, input, action) =>
298
+ fetchJson(`/engram/v1/support-passport/cards/${encodeURIComponent(cardId)}/${action}`, {
299
+ owner: true,
300
+ method: "POST",
301
+ body: input,
302
+ }),
303
+ createGrant: (input) =>
304
+ fetchJson("/engram/v1/support-passport/grants", { owner: true, method: "POST", body: input }),
305
+ revokeGrant: (id, input) =>
306
+ fetchJson(`/engram/v1/support-passport/grants/${encodeURIComponent(id)}/revoke`, {
307
+ owner: true,
308
+ method: "POST",
309
+ body: input,
310
+ }),
311
+ readGrant: (id, helperSecret, signal) =>
312
+ fetchJson(`/engram/v1/support-passport/public/grants/${encodeURIComponent(id)}`, {
313
+ secret: helperSecret,
314
+ signal,
315
+ captureServerTime: true,
316
+ }),
317
+ askGrant: (id, helperSecret, question, signal) =>
318
+ fetchJson(`/engram/v1/support-passport/public/grants/${encodeURIComponent(id)}/ask`, {
319
+ secret: helperSecret,
320
+ method: "POST",
321
+ body: { question },
322
+ signal,
323
+ }),
324
+ };
325
+
326
+ const replayApi = replayStore && {
327
+ ...replayStore,
328
+ async fetchMemory(memoryId) {
329
+ const note = replayStore.notes.find((candidate) => candidate.memoryId === memoryId);
330
+ if (!note) return { found: false };
331
+ return { found: true, memory: { id: note.memoryId, content: note.content, revision: note.revision } };
332
+ },
333
+ };
334
+ const api = replayMode ? replayApi : liveApi;
335
+
336
+ function showOwner() {
337
+ byId("connectPanel").hidden = true;
338
+ byId("helperView")?.remove();
339
+ byId("lockedView")?.remove();
340
+ byId("ownerView").hidden = false;
341
+ byId("viewMarkerText").textContent = replayMode ? "Owner replay" : "Owner view";
342
+ }
343
+
344
+ async function requestReplaySharedGuide() {
345
+ if (!replayChannel) return Promise.reject(new Error("The replay share state is unavailable."));
346
+ const requestId = crypto.randomUUID();
347
+ const proof = await model.replaySecretProof(state.helperSecret, requestId, state.helperGrantId);
348
+ return new Promise((resolve, reject) => {
349
+ const finish = (callback, value) => {
350
+ window.clearTimeout(timeout);
351
+ replayChannel.removeEventListener("message", receive);
352
+ callback(value);
353
+ };
354
+ const receive = (event) => {
355
+ if (event.data?.type === "grant-state" && event.data.requestId === requestId) {
356
+ if (event.data.snapshot) {
357
+ finish(resolve, event.data.snapshot);
358
+ return;
359
+ }
360
+ const error = new Error("The replay share state is unavailable.");
361
+ error.code = event.data.errorCode ?? "grant_not_found";
362
+ finish(reject, error);
363
+ }
364
+ };
365
+ replayChannel.addEventListener("message", receive);
366
+ const timeout = window.setTimeout(
367
+ () => finish(reject, new Error("The replay share state is unavailable.")),
368
+ REPLAY_SYNC_TIMEOUT_MS
369
+ );
370
+ try {
371
+ replayChannel.postMessage({
372
+ type: "grant-request",
373
+ requestId,
374
+ grantId: state.helperGrantId,
375
+ proof,
376
+ });
377
+ } catch (error) {
378
+ finish(reject, error);
379
+ }
380
+ });
381
+ }
382
+
383
+ function bindReplayOwnerBridge() {
384
+ if (!replayChannel || !replayStore) return;
385
+ replayChannel.addEventListener("message", async (event) => {
386
+ const request = event.data;
387
+ if (
388
+ request?.type !== "grant-request" ||
389
+ !/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(request.requestId) ||
390
+ !/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(request.grantId) ||
391
+ !/^[a-f0-9]{64}$/.test(request.proof)
392
+ ) {
393
+ return;
394
+ }
395
+ const result = await replayStore.exportSharedGuide(request.grantId, request.requestId, request.proof);
396
+ replayChannel.postMessage({
397
+ type: "grant-state",
398
+ requestId: request.requestId,
399
+ ...(result.errorCode ? { errorCode: result.errorCode } : { snapshot: result }),
400
+ });
401
+ });
402
+ window.addEventListener("pagehide", (event) => {
403
+ if (!event.persisted) replayChannel.close();
404
+ });
405
+ }
406
+
407
+ function renderSelectedNotes() {
408
+ const list = byId("noteList");
409
+ clear(list);
410
+ byId("notesEmpty").hidden = state.selectedNotes.length > 0;
411
+ byId("noteCount").textContent = `${state.selectedNotes.length} selected`;
412
+ for (const note of state.selectedNotes) {
413
+ const item = element("li", "note-item");
414
+ const copy = element("div");
415
+ copy.append(element("code", "", note.memoryId), element("p", "", note.content));
416
+ const remove = element("button", "button button-quiet", "Remove");
417
+ remove.type = "button";
418
+ remove.disabled = state.draftGenerationPending;
419
+ remove.setAttribute("aria-label", `Remove selected note ${note.memoryId}`);
420
+ remove.addEventListener("click", () => {
421
+ state.selectedNotes = state.selectedNotes.filter((candidate) => candidate.memoryId !== note.memoryId);
422
+ byId("consentInput").checked = false;
423
+ renderSelectedNotes();
424
+ announce("Selected note removed. Consent cleared.");
425
+ });
426
+ item.append(copy, remove);
427
+ list.append(item);
428
+ }
429
+ }
430
+
431
+ function statusLabel(card) {
432
+ return card.status === "active" ? "Approved" : "Draft";
433
+ }
434
+
435
+ function cardButton(label, className, handler) {
436
+ const button = element("button", `button ${className}`, label);
437
+ button.type = "button";
438
+ button.addEventListener("click", handler);
439
+ return button;
440
+ }
441
+
442
+ async function mutateCard(card, action, button) {
443
+ if (state.shareCreationPending && state.shareCreationCardIds?.has(card.cardId)) return;
444
+ const ownerSessionGeneration = state.ownerSessionGeneration;
445
+ setError("generateError");
446
+ const verbs = { approve: "Approving…", reject: "Rejecting…", withdraw: "Stopping…" };
447
+ const priorLabel = button.textContent;
448
+ state.pendingCardMutationIds.add(card.cardId);
449
+ for (const actionButton of button.closest(".card-actions")?.querySelectorAll("button") ?? []) {
450
+ actionButton.disabled = true;
451
+ }
452
+ button.textContent = verbs[action];
453
+ updateShareCardChoices();
454
+ try {
455
+ let ownerStateFresh = false;
456
+ let invalidatedCardIds = [];
457
+ try {
458
+ const result = await api.mutateCard(
459
+ card.cardId,
460
+ {
461
+ expectedRevision: card.revision,
462
+ reasonCode:
463
+ action === "approve"
464
+ ? "owner-approved-ui"
465
+ : action === "reject"
466
+ ? "owner-rejected-ui"
467
+ : "owner-withdrew-ui",
468
+ },
469
+ action
470
+ );
471
+ if (replayMode && Array.isArray(result?.invalidatedCardIds)) {
472
+ invalidatedCardIds = result.invalidatedCardIds.filter((cardId) => typeof cardId === "string");
473
+ }
474
+ } catch (error) {
475
+ if (error?.code !== "request_timeout") {
476
+ setError("generateError", errorMessage(error, "The support card did not change."));
477
+ return;
478
+ }
479
+ const reconciled = await reconcileOwnerState(() => {
480
+ const current = state.cards.find((candidate) => candidate.cardId === card.cardId);
481
+ if (action === "approve") {
482
+ return current?.status === "active" && current.revision !== card.revision ? current : null;
483
+ }
484
+ return current ? null : card;
485
+ });
486
+ if (reconciled.cancelled) return;
487
+ if (!reconciled.matched) {
488
+ setError(
489
+ "generateError",
490
+ reconciled.refreshed
491
+ ? "The request stopped without a confirmed change. Review the current guide before trying again."
492
+ : "The request stopped without a confirmed change. Refresh the guide before trying again."
493
+ );
494
+ return;
495
+ }
496
+ ownerStateFresh = true;
497
+ }
498
+ if (invalidatedCardIds.length > 0) {
499
+ replayChannel?.postMessage({ type: "cards-stale", cardIds: invalidatedCardIds });
500
+ }
501
+ const message =
502
+ action === "approve"
503
+ ? `Approved ${card.title}. It can now be shared.`
504
+ : action === "reject"
505
+ ? `Rejected ${card.title}. It stays private.`
506
+ : `Withdrew ${card.title}. Existing links will lock.`;
507
+ if (action === "withdraw") clearDisplayedShareLinkForCard(card.cardId);
508
+ toast(message);
509
+ announce(message);
510
+ try {
511
+ if (!ownerStateFresh) await loadOwnerState();
512
+ } catch (error) {
513
+ if (error === PAGE_HIDDEN_ABORT) return;
514
+ const warning = `${message} The card list did not refresh. Refresh the guide before another change.`;
515
+ setError("generateError", warning);
516
+ announce(warning);
517
+ }
518
+ } finally {
519
+ if (ownerSessionIsCurrent(ownerSessionGeneration)) {
520
+ state.pendingCardMutationIds.delete(card.cardId);
521
+ button.disabled = false;
522
+ button.textContent = priorLabel;
523
+ renderCards();
524
+ updateShareCardChoices();
525
+ }
526
+ }
527
+ }
528
+
529
+ function renderCards() {
530
+ const list = byId("cardList");
531
+ clear(list);
532
+ byId("cardsEmpty").hidden = state.cards.length > 0;
533
+ const drafts = state.cards.filter((card) => card.status === "pending_review").length;
534
+ const approved = state.cards.filter((card) => card.status === "active").length;
535
+ byId("draftCount").textContent = String(drafts);
536
+ byId("approvedCount").textContent = String(approved);
537
+
538
+ for (const card of state.cards) {
539
+ const article = element("article", "support-card");
540
+ article.dataset.cardId = card.cardId;
541
+ article.dataset.category = card.category;
542
+ const top = element("div", "card-topline");
543
+ const status = element(
544
+ "span",
545
+ `status-pill ${card.status === "active" ? "approved" : "draft"}`,
546
+ statusLabel(card)
547
+ );
548
+ const category = element("span", "category-pill", model.CATEGORY_LABELS[card.category]);
549
+ top.append(status, category);
550
+ const title = element("h3", "", card.title);
551
+ const statement = element("blockquote", "", card.statement);
552
+ const dates = element("div", "card-dates");
553
+ dates.append(
554
+ element("span", "", `Updated ${model.formatDate(card.updatedAt)}`),
555
+ element("span", "", `Review by ${model.formatDate(card.reviewBy)}`)
556
+ );
557
+ const actions = element("div", "card-actions");
558
+ actions.append(cardButton("Edit", "button-quiet", () => openCardDialog(card)));
559
+ if (card.status === "pending_review") {
560
+ const approve = cardButton("Approve", "button-primary", (event) =>
561
+ mutateCard(card, "approve", event.currentTarget)
562
+ );
563
+ const reject = cardButton("Reject", "button-danger", (event) =>
564
+ mutateCard(card, "reject", event.currentTarget)
565
+ );
566
+ actions.append(approve, reject);
567
+ } else {
568
+ actions.append(
569
+ cardButton("Withdraw", "button-danger", (event) => mutateCard(card, "withdraw", event.currentTarget))
570
+ );
571
+ }
572
+ if (
573
+ state.pendingCardMutationIds.has(card.cardId) ||
574
+ (state.shareCreationPending && state.shareCreationCardIds?.has(card.cardId))
575
+ ) {
576
+ for (const button of actions.querySelectorAll("button")) button.disabled = true;
577
+ }
578
+ article.append(top, title, statement, dates, actions);
579
+ list.append(article);
580
+ }
581
+ }
582
+
583
+ function renderShareCards() {
584
+ const list = byId("shareCardList");
585
+ clear(list);
586
+ const approved = state.cards.filter((card) => card.status === "active");
587
+ byId("shareCardsEmpty").hidden = approved.length > 0;
588
+ for (const card of approved) {
589
+ const label = element("label", "card-choice");
590
+ const input = document.createElement("input");
591
+ input.type = "checkbox";
592
+ input.name = "shareCard";
593
+ input.value = card.cardId;
594
+ input.dataset.revision = card.revision;
595
+ input.checked = state.shareCreationCardIds?.has(card.cardId) ?? false;
596
+ input.addEventListener("change", updateShareCardChoices);
597
+ const copy = element("span");
598
+ copy.append(element("strong", "", card.title), element("small", "", card.statement));
599
+ label.append(input, copy);
600
+ list.append(label);
601
+ }
602
+ updateShareCardChoices();
603
+ }
604
+
605
+ function updateShareCardChoices() {
606
+ const choices = [...document.querySelectorAll('input[name="shareCard"]')];
607
+ const selectedMutationPending = choices.some(
608
+ (input) => input.checked && state.pendingCardMutationIds.has(input.value)
609
+ );
610
+ const submit = byId("shareForm").querySelector('button[type="submit"]');
611
+ submit.disabled = state.shareCreationPending || selectedMutationPending;
612
+ if (state.shareCreationPending) {
613
+ for (const input of choices) input.disabled = true;
614
+ return;
615
+ }
616
+ const selectedCount = choices.filter((input) => input.checked).length;
617
+ for (const input of choices) input.disabled = !input.checked && selectedCount >= MAX_SHARED_CARDS;
618
+ }
619
+
620
+ function setShareCreationPending(pending, selectedInputs = []) {
621
+ state.shareCreationPending = pending;
622
+ state.shareCreationCardIds = pending ? new Set(selectedInputs.map((input) => input.value)) : null;
623
+ for (const input of document.querySelectorAll('input[name="duration"], #customTimeInput')) {
624
+ input.disabled = pending;
625
+ }
626
+ renderCards();
627
+ updateShareCardChoices();
628
+ }
629
+
630
+ function delay(milliseconds) {
631
+ return new Promise((resolve) => window.setTimeout(resolve, milliseconds));
632
+ }
633
+
634
+ async function reconcileOwnerState(match) {
635
+ const ownerSessionGeneration = state.ownerSessionGeneration;
636
+ if (!ownerSessionIsCurrent(ownerSessionGeneration)) return { matched: false, refreshed: false, cancelled: true };
637
+ let refreshed = false;
638
+ for (let attempt = 0; attempt < OWNER_RECONCILIATION_ATTEMPTS; attempt += 1) {
639
+ if (!ownerSessionIsCurrent(ownerSessionGeneration)) return { matched: false, refreshed, cancelled: true };
640
+ try {
641
+ await loadOwnerState();
642
+ if (!ownerSessionIsCurrent(ownerSessionGeneration)) return { matched: false, refreshed, cancelled: true };
643
+ refreshed = true;
644
+ const value = match();
645
+ if (value) return { matched: true, value };
646
+ } catch (error) {
647
+ if (error === PAGE_HIDDEN_ABORT) return { matched: false, refreshed, cancelled: true };
648
+ }
649
+ if (attempt + 1 < OWNER_RECONCILIATION_ATTEMPTS) {
650
+ await delay(OWNER_RECONCILIATION_DELAY_MS);
651
+ }
652
+ }
653
+ return { matched: false, refreshed };
654
+ }
655
+
656
+ function renderGrants() {
657
+ window.clearTimeout(state.ownerGrantExpiryTimer);
658
+ state.ownerGrantExpiryTimer = null;
659
+ const list = byId("grantList");
660
+ clear(list);
661
+ byId("grantsEmpty").hidden = state.grants.length > 0;
662
+ for (const grant of state.grants) {
663
+ const grantStatus =
664
+ grant.status === "active" && Date.parse(grant.expiresAt) <= ownerNowMs() ? "expired" : grant.status;
665
+ const article = element("article", "grant-card");
666
+ article.dataset.grantId = grant.grantId;
667
+ const stateText =
668
+ grantStatus === "active" ? "Live share" : grantStatus === "revoked" ? "Sharing stopped" : "Share time ended";
669
+ article.append(
670
+ element("p", "", stateText),
671
+ element(
672
+ "div",
673
+ "grant-meta",
674
+ `${grant.cards.length} card${grant.cards.length === 1 ? "" : "s"} · Ends ${model.formatDate(grant.expiresAt)}`
675
+ )
676
+ );
677
+ if (grantStatus === "active") {
678
+ const stop = cardButton("Stop sharing", "button-danger", async (event) => {
679
+ const ownerSessionGeneration = state.ownerSessionGeneration;
680
+ const button = event.currentTarget;
681
+ const priorLabel = button.textContent;
682
+ let ownerStateFresh = false;
683
+ state.pendingGrantRevocationIds.add(grant.grantId);
684
+ button.disabled = true;
685
+ button.textContent = "Stopping sharing…";
686
+ try {
687
+ try {
688
+ await api.revokeGrant(grant.grantId, { expectedVersion: grant.stateVersion });
689
+ } catch (error) {
690
+ if (error?.code !== "request_timeout") {
691
+ setError("shareError", errorMessage(error, "The share link did not stop."));
692
+ return;
693
+ }
694
+ const reconciled = await reconcileOwnerState(() => {
695
+ const current = state.grants.find((candidate) => candidate.grantId === grant.grantId);
696
+ return !current || current.status !== "active" ? grant : null;
697
+ });
698
+ if (reconciled.cancelled) return;
699
+ if (!reconciled.matched) {
700
+ setError(
701
+ "shareError",
702
+ "The server did not confirm that sharing stopped. Refresh the guide and check this link."
703
+ );
704
+ return;
705
+ }
706
+ ownerStateFresh = true;
707
+ }
708
+ if (state.displayedGrant?.grantId === grant.grantId) clearDisplayedShareLink();
709
+ replayChannel?.postMessage({ type: "grant-revoked", grantId: grant.grantId });
710
+ const message = "Sharing stopped. The helper link is now locked.";
711
+ toast(message);
712
+ announce(message);
713
+ try {
714
+ if (!ownerStateFresh) await loadOwnerState();
715
+ } catch (error) {
716
+ if (error === PAGE_HIDDEN_ABORT) return;
717
+ const warning = `${message} The share list did not refresh. Refresh the guide before another change.`;
718
+ setError("shareError", warning);
719
+ announce(warning);
720
+ }
721
+ } finally {
722
+ if (ownerSessionIsCurrent(ownerSessionGeneration)) {
723
+ state.pendingGrantRevocationIds.delete(grant.grantId);
724
+ button.disabled = false;
725
+ button.textContent = priorLabel;
726
+ renderGrants();
727
+ }
728
+ }
729
+ });
730
+ stop.disabled = state.pendingGrantRevocationIds.has(grant.grantId);
731
+ article.append(stop);
732
+ }
733
+ list.append(article);
734
+ }
735
+ const nextExpiry = state.grants
736
+ .filter((grant) => grant.status === "active")
737
+ .map((grant) => Date.parse(grant.expiresAt) - ownerNowMs())
738
+ .filter((remainingMs) => remainingMs > 0)
739
+ .sort((left, right) => left - right)[0];
740
+ if (Number.isFinite(nextExpiry)) {
741
+ state.ownerGrantExpiryTimer = window.setTimeout(() => {
742
+ reconcileDisplayedShareLink();
743
+ renderGrants();
744
+ }, nextExpiry);
745
+ }
746
+ }
747
+
748
+ async function loadOwnerState() {
749
+ const generation = ++state.ownerLoadGeneration;
750
+ const [cardPayload, grantPayload] = await Promise.all([api.listCards(), api.listGrants()]);
751
+ const cards = model.parseCardList(cardPayload);
752
+ const grants = model.parseGrantList(grantPayload).slice(0, MAX_VISIBLE_GRANTS);
753
+ if (generation !== state.ownerLoadGeneration || state.ownerLifecyclePaused) return false;
754
+ state.cards = cards;
755
+ state.grants = grants;
756
+ reconcileDisplayedShareLink();
757
+ renderCards();
758
+ renderShareCards();
759
+ renderGrants();
760
+ return true;
761
+ }
762
+
763
+ function toLocalInputValue(date) {
764
+ const offset = date.getTimezoneOffset() * 60_000;
765
+ return new Date(date.getTime() - offset).toISOString().slice(0, 16);
766
+ }
767
+
768
+ function openCardDialog(card = null) {
769
+ if (state.cardSavePending) return;
770
+ byId("cardDialogTitle").textContent = card ? "Edit this support card" : "Write a support card";
771
+ byId("cardSaveButton").textContent = "Save draft";
772
+ byId("cardIdInput").value = card?.cardId ?? "";
773
+ byId("cardRevisionInput").value = card?.revision ?? "";
774
+ byId("cardTitleInput").value = card?.title ?? "";
775
+ byId("cardStatementInput").value = card?.statement ?? "";
776
+ byId("cardCategoryInput").value = card?.category ?? "communication";
777
+ byId("cardReviewInput").value = toLocalInputValue(
778
+ card ? new Date(card.reviewBy) : new Date(Date.now() + 180 * 24 * 60 * 60_000)
779
+ );
780
+ setError("cardError");
781
+ byId("cardDialog").showModal();
782
+ byId("cardTitleInput").focus();
783
+ }
784
+
785
+ function closeCardDialog() {
786
+ if (state.cardSavePending) return;
787
+ byId("cardDialog").close();
788
+ }
789
+
790
+ function setCardSavePending(pending) {
791
+ state.cardSavePending = pending;
792
+ byId("cardForm").setAttribute("aria-busy", String(pending));
793
+ for (const control of byId("cardForm").querySelectorAll("input, textarea, select, button")) {
794
+ control.disabled = pending;
795
+ }
796
+ byId("newCardButton").disabled = pending;
797
+ }
798
+
799
+ async function saveCard(event) {
800
+ event.preventDefault();
801
+ if (state.cardSavePending) return;
802
+ const ownerSessionGeneration = state.ownerSessionGeneration;
803
+ setError("cardError");
804
+ const cardId = byId("cardIdInput").value;
805
+ const expectedRevision = byId("cardRevisionInput").value;
806
+ const reviewValue = byId("cardReviewInput").value;
807
+ const reviewDate = new Date(reviewValue);
808
+ const existingCard = state.cards.find((card) => card.cardId === cardId);
809
+ const keepsExistingReminder = existingCard && reviewValue === toLocalInputValue(new Date(existingCard.reviewBy));
810
+ if (!Number.isFinite(reviewDate.getTime()) || (reviewDate.getTime() <= Date.now() && !keepsExistingReminder)) {
811
+ setError("cardError", "Choose a future review reminder.");
812
+ return;
813
+ }
814
+ const input = {
815
+ title: byId("cardTitleInput").value.trim(),
816
+ statement: byId("cardStatementInput").value.trim(),
817
+ category: byId("cardCategoryInput").value,
818
+ reviewBy: keepsExistingReminder ? existingCard.reviewBy : reviewDate.toISOString(),
819
+ ...(cardId ? { expectedRevision } : {}),
820
+ };
821
+ const button = byId("cardSaveButton");
822
+ const priorLabel = button.textContent;
823
+ setCardSavePending(true);
824
+ button.textContent = "Saving draft…";
825
+ try {
826
+ try {
827
+ await (cardId ? api.replaceCard(cardId, input) : api.createManualDraft(input));
828
+ } catch (error) {
829
+ if (error?.code !== "request_timeout") {
830
+ setError("cardError", errorMessage(error, "The draft did not save."));
831
+ return;
832
+ }
833
+ const reconciled = await reconcileOwnerState(() => null);
834
+ if (reconciled.cancelled) return;
835
+ let message;
836
+ if (cardId) {
837
+ message = reconciled.refreshed
838
+ ? "The edit timed out. Review the current guide before trying again."
839
+ : "The server did not confirm the edit. Refresh the guide before trying again.";
840
+ } else {
841
+ message = reconciled.refreshed
842
+ ? "The request timed out. Review the current guide to see whether the draft saved before trying again."
843
+ : "The server did not confirm whether the draft saved. Refresh the guide before saving it again.";
844
+ }
845
+ setError("cardError", message);
846
+ return;
847
+ }
848
+ byId("cardDialog").close();
849
+ const message = "Draft saved. Review and approve it before sharing.";
850
+ toast(message);
851
+ announce(message);
852
+ try {
853
+ await loadOwnerState();
854
+ } catch (error) {
855
+ if (error === PAGE_HIDDEN_ABORT) return;
856
+ const warning = "The draft was saved, but the card list did not refresh. Refresh the guide before editing it.";
857
+ setError("generateError", warning);
858
+ announce(warning);
859
+ }
860
+ } finally {
861
+ if (ownerSessionIsCurrent(ownerSessionGeneration)) {
862
+ button.textContent = priorLabel;
863
+ setCardSavePending(false);
864
+ }
865
+ }
866
+ }
867
+
868
+ async function addMemory(event) {
869
+ event.preventDefault();
870
+ if (state.draftGenerationPending || state.notePreviewPending) return;
871
+ const ownerSessionGeneration = state.ownerSessionGeneration;
872
+ setError("memoryError");
873
+ const memoryInput = byId("memoryIdInput");
874
+ const submittedValue = memoryInput.value;
875
+ const memoryId = submittedValue.trim();
876
+ if (state.selectedNotes.some((note) => note.memoryId === memoryId)) {
877
+ setError("memoryError", "That note is already selected.");
878
+ return;
879
+ }
880
+ if (state.selectedNotes.length >= 20) {
881
+ setError("memoryError", "Select no more than 20 notes.");
882
+ return;
883
+ }
884
+ const button = event.currentTarget.querySelector("button");
885
+ const priorLabel = button.textContent;
886
+ setNotePreviewPending(true);
887
+ button.textContent = "Adding note…";
888
+ try {
889
+ const preview = model.parseMemoryPreview(await api.fetchMemory(memoryId));
890
+ if (!preview.found) {
891
+ throw new Error("That memory was not found in your Remnic scope.");
892
+ }
893
+ if (preview.memory.id !== memoryId) throw new Error("The selected note response is invalid.");
894
+ state.selectedNotes = [
895
+ ...state.selectedNotes,
896
+ {
897
+ memoryId,
898
+ content: preview.memory.content,
899
+ revision: preview.memory.revision,
900
+ },
901
+ ];
902
+ if (memoryInput.value === submittedValue) memoryInput.value = "";
903
+ byId("consentInput").checked = false;
904
+ renderSelectedNotes();
905
+ announce("Selected note added. Review it before consent.");
906
+ } catch (error) {
907
+ setError("memoryError", errorMessage(error, "The selected note did not load."));
908
+ } finally {
909
+ if (ownerSessionIsCurrent(ownerSessionGeneration)) {
910
+ button.textContent = priorLabel;
911
+ setNotePreviewPending(false);
912
+ }
913
+ }
914
+ }
915
+
916
+ async function generateDrafts() {
917
+ if (state.draftGenerationPending) return;
918
+ const ownerSessionGeneration = state.ownerSessionGeneration;
919
+ setError("generateError");
920
+ if (state.selectedNotes.length === 0) {
921
+ setError("generateError", "Select at least one note first.");
922
+ return;
923
+ }
924
+ if (state.notePreviewPending) {
925
+ setError("generateError", "Wait for the selected note to finish loading before drafting.");
926
+ return;
927
+ }
928
+ if (!byId("consentInput").checked) {
929
+ setError("generateError", "Select the consent box before any model call.");
930
+ byId("consentInput").focus();
931
+ return;
932
+ }
933
+ const submittedNotes = state.selectedNotes.map((note) => ({ ...note }));
934
+ const button = byId("generateButton");
935
+ const priorLabel = button.textContent;
936
+ setDraftGenerationPending(true);
937
+ button.textContent = "Drafting cards…";
938
+ try {
939
+ try {
940
+ await api.generateDrafts({
941
+ sourceMemoryIds: submittedNotes.map((note) => note.memoryId),
942
+ sourceMemoryRevisions: submittedNotes.map((note) => ({
943
+ memoryId: note.memoryId,
944
+ revision: note.revision,
945
+ })),
946
+ consent: true,
947
+ });
948
+ } catch (error) {
949
+ if (error?.code !== "request_timeout") {
950
+ setError("generateError", errorMessage(error, "The configured model did not return valid drafts."));
951
+ return;
952
+ }
953
+ byId("consentInput").checked = false;
954
+ let refreshed = false;
955
+ try {
956
+ await loadOwnerState();
957
+ refreshed = true;
958
+ } catch (error) {
959
+ if (error === PAGE_HIDDEN_ABORT) return;
960
+ }
961
+ setError(
962
+ "generateError",
963
+ refreshed
964
+ ? "Drafting timed out. Review the current guide before deciding whether to draft again."
965
+ : "Drafting timed out. Refresh the guide before deciding whether to draft again."
966
+ );
967
+ return;
968
+ }
969
+ byId("consentInput").checked = false;
970
+ const message = "Drafts ready. Review each card before approval.";
971
+ toast(message);
972
+ announce(message);
973
+ try {
974
+ await loadOwnerState();
975
+ byId("reviewCards").scrollIntoView({ behavior: SCROLL_BEHAVIOR, block: "start" });
976
+ } catch (error) {
977
+ if (error === PAGE_HIDDEN_ABORT) return;
978
+ const warning = `${message} The card list did not refresh. Refresh the guide before another change.`;
979
+ setError("generateError", warning);
980
+ announce(warning);
981
+ }
982
+ } finally {
983
+ if (ownerSessionIsCurrent(ownerSessionGeneration)) {
984
+ button.textContent = priorLabel;
985
+ setDraftGenerationPending(false);
986
+ }
987
+ }
988
+ }
989
+
990
+ function setDraftGenerationPending(pending) {
991
+ state.draftGenerationPending = pending;
992
+ syncDraftControls();
993
+ }
994
+
995
+ function setNotePreviewPending(pending) {
996
+ state.notePreviewPending = pending;
997
+ syncDraftControls();
998
+ }
999
+
1000
+ function syncDraftControls() {
1001
+ byId("memoryIdInput").disabled = state.draftGenerationPending;
1002
+ byId("memoryForm").querySelector('button[type="submit"]').disabled =
1003
+ state.draftGenerationPending || state.notePreviewPending;
1004
+ byId("consentInput").disabled = state.draftGenerationPending || state.notePreviewPending;
1005
+ byId("generateButton").disabled = state.draftGenerationPending || state.notePreviewPending;
1006
+ renderSelectedNotes();
1007
+ }
1008
+
1009
+ async function createShare(event) {
1010
+ event.preventDefault();
1011
+ if (state.shareCreationPending) return;
1012
+ const ownerSessionGeneration = state.ownerSessionGeneration;
1013
+ setError("shareError");
1014
+ const selectedInputs = [...document.querySelectorAll('input[name="shareCard"]:checked')];
1015
+ const cardIds = selectedInputs.map((input) => input.value);
1016
+ if (cardIds.length === 0) {
1017
+ setError("shareError", "Select at least one approved support card.");
1018
+ return;
1019
+ }
1020
+ if (cardIds.some((cardId) => state.pendingCardMutationIds.has(cardId))) {
1021
+ setError("shareError", "Wait for the selected support card to finish changing before sharing it.");
1022
+ return;
1023
+ }
1024
+ if (cardIds.length > MAX_SHARED_CARDS) {
1025
+ setError("shareError", "Select no more than eight approved support cards.");
1026
+ return;
1027
+ }
1028
+ const cardRevisions = selectedInputs.map((input) => ({
1029
+ cardId: input.value,
1030
+ revision: input.dataset.revision ?? "",
1031
+ }));
1032
+ if (cardRevisions.some((card) => !card.revision)) {
1033
+ setError("shareError", "A selected support card changed. Refresh the guide and select it again.");
1034
+ return;
1035
+ }
1036
+ const choice = document.querySelector('input[name="duration"]:checked')?.value ?? "2h";
1037
+ let expiry;
1038
+ try {
1039
+ expiry = model.expiryForChoice(choice, byId("customTimeInput").value, ownerNowMs());
1040
+ } catch (error) {
1041
+ setError("shareError", errorMessage(error, "Choose a valid share time."));
1042
+ return;
1043
+ }
1044
+ clearDisplayedShareLink();
1045
+ const button = event.currentTarget.querySelector('button[type="submit"]');
1046
+ const priorLabel = button.textContent;
1047
+ let clearSelection = false;
1048
+ setShareCreationPending(true, selectedInputs);
1049
+ button.disabled = true;
1050
+ button.textContent = "Creating link…";
1051
+ try {
1052
+ let created;
1053
+ try {
1054
+ created = model.parseCreatedGrant(await api.createGrant({ cardIds, cardRevisions, ...expiry }));
1055
+ } catch (error) {
1056
+ if (error?.code !== "request_timeout") {
1057
+ setError("shareError", errorMessage(error, "The share link was not created."));
1058
+ return;
1059
+ }
1060
+ let refreshed = false;
1061
+ try {
1062
+ await loadOwnerState();
1063
+ refreshed = true;
1064
+ } catch (error) {
1065
+ if (error === PAGE_HIDDEN_ABORT) return;
1066
+ }
1067
+ setError(
1068
+ "shareError",
1069
+ refreshed
1070
+ ? "The server did not confirm whether it created a link. Review the live share list and stop any link you do not recognize before creating another."
1071
+ : "The server did not confirm whether it created a link. Refresh the guide and review live shares before creating another."
1072
+ );
1073
+ return;
1074
+ }
1075
+ const url = model.buildShareUrl(
1076
+ window.location.href,
1077
+ created.grantId,
1078
+ created.secret,
1079
+ replayMode,
1080
+ replayChannelId
1081
+ );
1082
+ clearSelection = true;
1083
+ state.displayedGrant = {
1084
+ grantId: created.grantId,
1085
+ expiresAt: created.expiresAt,
1086
+ cards: cardRevisions.map((card) => ({ ...card })),
1087
+ };
1088
+ byId("shareLinkInput").value = url;
1089
+ byId("openLinkButton").href = url;
1090
+ byId("newLinkPanel").hidden = false;
1091
+ scheduleDisplayedShareLinkExpiry();
1092
+ const message = `Share link created. It ends ${model.formatDate(created.expiresAt)}.`;
1093
+ toast(message);
1094
+ announce(message);
1095
+ byId("newLinkPanel").scrollIntoView({ behavior: SCROLL_BEHAVIOR, block: "nearest" });
1096
+ try {
1097
+ await loadOwnerState();
1098
+ } catch (error) {
1099
+ if (error === PAGE_HIDDEN_ABORT) return;
1100
+ setError("shareError", "The share link was created, but the share list did not refresh. Use the link above.");
1101
+ }
1102
+ } finally {
1103
+ if (ownerSessionIsCurrent(ownerSessionGeneration)) {
1104
+ setShareCreationPending(false);
1105
+ if (clearSelection) {
1106
+ for (const input of document.querySelectorAll('input[name="shareCard"]')) input.checked = false;
1107
+ updateShareCardChoices();
1108
+ }
1109
+ button.disabled = false;
1110
+ button.textContent = priorLabel;
1111
+ }
1112
+ }
1113
+ }
1114
+
1115
+ async function copyShareLink() {
1116
+ const value = byId("shareLinkInput").value;
1117
+ try {
1118
+ await navigator.clipboard.writeText(value);
1119
+ toast("Share link copied.");
1120
+ announce("Share link copied.");
1121
+ } catch {
1122
+ byId("shareLinkInput").focus();
1123
+ byId("shareLinkInput").select();
1124
+ setError("shareError", "Copy the selected share link manually.");
1125
+ }
1126
+ }
1127
+
1128
+ async function connectOwner(event) {
1129
+ event.preventDefault();
1130
+ setError("connectError");
1131
+ state.ownerLifecyclePaused = false;
1132
+ const ownerSessionGeneration = state.ownerSessionGeneration;
1133
+ state.token = byId("tokenInput").value.trim();
1134
+ if (!state.token) return;
1135
+ const button = event.currentTarget.querySelector("button");
1136
+ try {
1137
+ await withBusy(button, "Opening guide…", loadOwnerState);
1138
+ if (!ownerSessionIsCurrent(ownerSessionGeneration)) return;
1139
+ showOwner();
1140
+ announce("Owner guide loaded.");
1141
+ } catch (error) {
1142
+ if (!ownerSessionIsCurrent(ownerSessionGeneration)) return;
1143
+ state.token = "";
1144
+ setError("connectError", errorMessage(error, "The owner guide did not load."));
1145
+ }
1146
+ }
1147
+
1148
+ function clearHelperSensitiveView() {
1149
+ byId("questionForm").reset();
1150
+ byId("questionInput").disabled = false;
1151
+ byId("questionCount").textContent = "0";
1152
+ byId("answerCopy").textContent = "";
1153
+ byId("answerPanel").hidden = true;
1154
+ clear(byId("citationList"));
1155
+ setError("questionError");
1156
+ }
1157
+
1158
+ function showLocked(error) {
1159
+ if (state.helperLifecyclePaused && error?.code !== "session_ended") return;
1160
+ window.clearTimeout(state.helperExpiryTimer);
1161
+ state.helperExpiryTimer = null;
1162
+ window.clearTimeout(state.helperRevalidationTimer);
1163
+ state.helperRevalidationTimer = null;
1164
+ state.helperLoadController?.abort();
1165
+ state.helperLoadController = null;
1166
+ state.helperQuestionController?.abort();
1167
+ state.helperQuestionController = null;
1168
+ state.helperRevalidationController?.abort();
1169
+ state.helperRevalidationController = null;
1170
+ state.helperViewGeneration += 1;
1171
+ const lastGuide = state.guide;
1172
+ state.guide = null;
1173
+ byId("connectPanel")?.remove();
1174
+ byId("ownerView")?.remove();
1175
+ byId("helperView").hidden = true;
1176
+ byId("helperLoading").hidden = false;
1177
+ clear(byId("guideGroups"));
1178
+ clearHelperSensitiveView();
1179
+ const helperNow = Date.now() + (state.helperServerOffsetMs ?? 0);
1180
+ const lock = model.lockState(error, lastGuide, helperNow);
1181
+ const retryable =
1182
+ Boolean(state.helperGrantId && state.helperSecret) && !TERMINAL_HELPER_ERROR_CODES.has(error?.code);
1183
+ byId("lockedEyebrow").textContent = lock.eyebrow;
1184
+ byId("lockedTitle").textContent = lock.title;
1185
+ byId("lockedDetail").textContent = lock.detail;
1186
+ byId("retryHelperButton").hidden = !retryable;
1187
+ byId("lockedView").hidden = false;
1188
+ byId("viewMarkerText").textContent = "Locked helper view";
1189
+ document.title = "Share link locked · What Helps Me";
1190
+ byId("lockedTitle").focus?.();
1191
+ announce(lock.title);
1192
+ }
1193
+
1194
+ async function readHelperGuide(signal) {
1195
+ const result = await api.readGrant(state.helperGrantId, state.helperSecret, signal);
1196
+ if (state.helperLifecyclePaused) throw PAGE_HIDDEN_ABORT;
1197
+ if (replayMode) return model.parsePublicGuide(result);
1198
+ if (Number.isFinite(result.serverOffsetMs)) state.helperServerOffsetMs = result.serverOffsetMs;
1199
+ return model.parsePublicGuide(result.payload);
1200
+ }
1201
+
1202
+ function scheduleHelperExpiry() {
1203
+ window.clearTimeout(state.helperExpiryTimer);
1204
+ state.helperExpiryTimer = null;
1205
+ if (!state.guide || state.helperLifecyclePaused) return;
1206
+ const serverNow = Date.now() + (state.helperServerOffsetMs ?? 0);
1207
+ const remainingMs = Date.parse(state.guide.expiresAt) - serverNow;
1208
+ if (remainingMs <= 0 && !replayMode && state.helperServerOffsetMs === null) return;
1209
+ if (remainingMs <= 0) {
1210
+ const error = new Error("The share link has expired.");
1211
+ error.code = "grant_expired";
1212
+ showLocked(error);
1213
+ return;
1214
+ }
1215
+ state.helperExpiryTimer = window.setTimeout(() => {
1216
+ const error = new Error("The share link has expired.");
1217
+ error.code = "grant_expired";
1218
+ showLocked(error);
1219
+ }, remainingMs);
1220
+ }
1221
+
1222
+ function renderGuide(guide) {
1223
+ byId("helperLoading").hidden = true;
1224
+ byId("helperExpiry").textContent = model.formatDate(guide.expiresAt);
1225
+ byId("helperCardCount").textContent = `${guide.cards.length} card${guide.cards.length === 1 ? "" : "s"}`;
1226
+ const groupsNode = byId("guideGroups");
1227
+ clear(groupsNode);
1228
+ for (const group of model.groupCards(guide.cards)) {
1229
+ const section = element("section", "guide-group");
1230
+ section.append(element("h3", "", group.label));
1231
+ for (const card of group.cards) {
1232
+ const article = element("article", "public-card");
1233
+ article.dataset.category = card.category;
1234
+ article.dataset.cardId = card.cardId;
1235
+ article.append(element("h3", "", card.title), element("p", "", card.statement));
1236
+ section.append(article);
1237
+ }
1238
+ groupsNode.append(section);
1239
+ }
1240
+ scheduleHelperExpiry();
1241
+ }
1242
+
1243
+ function scheduleHelperRevalidation() {
1244
+ window.clearTimeout(state.helperRevalidationTimer);
1245
+ state.helperRevalidationTimer = null;
1246
+ if (replayMode || !state.guide || state.helperLifecyclePaused) return;
1247
+ state.helperRevalidationTimer = window.setTimeout(revalidateHelper, state.helperRevalidationDelayMs);
1248
+ }
1249
+
1250
+ async function revalidateHelper() {
1251
+ state.helperRevalidationTimer = null;
1252
+ if (!state.guide || state.helperLifecyclePaused) return;
1253
+ const helperViewGeneration = state.helperViewGeneration;
1254
+ const controller = new AbortController();
1255
+ state.helperRevalidationController?.abort();
1256
+ state.helperRevalidationController = controller;
1257
+ const timeout = window.setTimeout(() => controller.abort(HELPER_READ_TIMEOUT_ABORT), HELPER_READ_TIMEOUT_MS);
1258
+ try {
1259
+ const guide = await readHelperGuide(controller.signal);
1260
+ if (state.helperLifecyclePaused || helperViewGeneration !== state.helperViewGeneration) return;
1261
+ state.guide = guide;
1262
+ state.helperRevalidationDelayMs = HELPER_REVALIDATION_MS;
1263
+ scheduleHelperExpiry();
1264
+ } catch (error) {
1265
+ if (
1266
+ state.helperLifecyclePaused ||
1267
+ helperViewGeneration !== state.helperViewGeneration ||
1268
+ controller.signal.reason === PAGE_HIDDEN_ABORT ||
1269
+ !state.guide
1270
+ )
1271
+ return;
1272
+ if (controller.signal.aborted) {
1273
+ if (controller.signal.reason !== HELPER_READ_TIMEOUT_ABORT) return;
1274
+ showLocked(HELPER_READ_TIMEOUT_ABORT);
1275
+ return;
1276
+ }
1277
+ if (["grant_expired", "grant_gone", "grant_not_found", "grant_stale"].includes(error?.code)) {
1278
+ showLocked(error);
1279
+ return;
1280
+ }
1281
+ if (error?.code === "rate_limited") {
1282
+ state.helperRevalidationDelayMs = Math.min(state.helperRevalidationDelayMs * 2, HELPER_REVALIDATION_MAX_MS);
1283
+ }
1284
+ } finally {
1285
+ window.clearTimeout(timeout);
1286
+ if (
1287
+ state.helperRevalidationController === controller &&
1288
+ helperViewGeneration === state.helperViewGeneration
1289
+ ) {
1290
+ state.helperRevalidationController = null;
1291
+ scheduleHelperRevalidation();
1292
+ }
1293
+ }
1294
+ }
1295
+
1296
+ async function loadHelper() {
1297
+ const helperViewGeneration = ++state.helperViewGeneration;
1298
+ byId("connectPanel")?.remove();
1299
+ byId("ownerView")?.remove();
1300
+ byId("lockedView").hidden = true;
1301
+ byId("retryHelperButton").hidden = true;
1302
+ byId("helperView").hidden = false;
1303
+ byId("viewMarkerText").textContent = replayMode ? "Helper replay" : "Helper view";
1304
+ document.title = "Shared support passport · What Helps Me";
1305
+ if (!state.helperGrantId || !state.helperSecret) {
1306
+ const error = new Error("The share link is incomplete.");
1307
+ error.code = "missing_link";
1308
+ showLocked(error);
1309
+ return;
1310
+ }
1311
+ const controller = new AbortController();
1312
+ state.helperLoadController?.abort();
1313
+ state.helperLoadController = controller;
1314
+ const timeout = window.setTimeout(() => controller.abort(HELPER_READ_TIMEOUT_ABORT), HELPER_READ_TIMEOUT_MS);
1315
+ try {
1316
+ if (replayMode) {
1317
+ const sharedState = await requestReplaySharedGuide();
1318
+ if (sharedState.cards.some((card) => state.replayInvalidatedCardIds.has(card.cardId))) {
1319
+ const error = new Error("The shared support guide has changed.");
1320
+ error.code = "grant_stale";
1321
+ throw error;
1322
+ }
1323
+ replayStore.seedSharedGuide(state.helperGrantId, state.helperSecret, sharedState);
1324
+ }
1325
+ const guide = await readHelperGuide(controller.signal);
1326
+ if (state.helperLifecyclePaused || helperViewGeneration !== state.helperViewGeneration) return;
1327
+ state.guide = guide;
1328
+ renderGuide(state.guide);
1329
+ if (!state.guide) return;
1330
+ scheduleHelperRevalidation();
1331
+ announce(`Support passport loaded with ${state.guide.cards.length} cards.`);
1332
+ } catch (error) {
1333
+ if (
1334
+ state.helperLifecyclePaused ||
1335
+ helperViewGeneration !== state.helperViewGeneration ||
1336
+ error === PAGE_HIDDEN_ABORT ||
1337
+ controller.signal.reason === PAGE_HIDDEN_ABORT
1338
+ )
1339
+ return;
1340
+ showLocked(controller.signal.reason === HELPER_READ_TIMEOUT_ABORT ? HELPER_READ_TIMEOUT_ABORT : error);
1341
+ } finally {
1342
+ window.clearTimeout(timeout);
1343
+ if (state.helperLoadController === controller) state.helperLoadController = null;
1344
+ }
1345
+ }
1346
+
1347
+ async function askQuestion(event) {
1348
+ event.preventDefault();
1349
+ setError("questionError");
1350
+ const questionInput = byId("questionInput");
1351
+ const question = questionInput.value.trim();
1352
+ if (!question || !state.guide) return;
1353
+ byId("answerPanel").hidden = true;
1354
+ byId("answerCopy").textContent = "";
1355
+ clear(byId("citationList"));
1356
+ const button = event.currentTarget.querySelector("button");
1357
+ const priorLabel = "Ask from this guide";
1358
+ const controller = new AbortController();
1359
+ state.helperQuestionController?.abort();
1360
+ state.helperQuestionController = controller;
1361
+ let timedOut = false;
1362
+ const timeout = window.setTimeout(() => {
1363
+ timedOut = true;
1364
+ controller.abort();
1365
+ }, HELPER_QUESTION_TIMEOUT_MS);
1366
+ questionInput.disabled = true;
1367
+ button.disabled = true;
1368
+ button.textContent = "Checking shared cards…";
1369
+ try {
1370
+ const payload = await api.askGrant(state.helperGrantId, state.helperSecret, question, controller.signal);
1371
+ if (state.helperLifecyclePaused || state.helperQuestionController !== controller) return;
1372
+ const answer = model.parseAnswer(payload, state.guide);
1373
+ byId("answerCopy").textContent = answer.answer;
1374
+ const citations = byId("citationList");
1375
+ clear(citations);
1376
+ for (const cardId of answer.citedCardIds) {
1377
+ const card = state.guide.cards.find((candidate) => candidate.cardId === cardId);
1378
+ citations.append(element("div", "citation", `Support card · ${card?.title ?? cardId}`));
1379
+ }
1380
+ if (answer.citedCardIds.length === 0)
1381
+ citations.append(element("div", "citation", "No support card covers this question."));
1382
+ byId("answerPanel").hidden = false;
1383
+ byId("answerPanel").focus();
1384
+ announce("Answer ready with support card citations.");
1385
+ } catch (error) {
1386
+ if (!state.guide) return;
1387
+ if (state.helperQuestionController !== controller) return;
1388
+ if (state.helperLifecyclePaused || error === PAGE_HIDDEN_ABORT || controller.signal.reason === PAGE_HIDDEN_ABORT)
1389
+ return;
1390
+ if (timedOut) {
1391
+ setError("questionError", "The question took too long. Try again.");
1392
+ return;
1393
+ }
1394
+ if (["grant_expired", "grant_gone", "grant_not_found", "grant_stale"].includes(error?.code)) {
1395
+ showLocked(error);
1396
+ return;
1397
+ }
1398
+ setError("questionError", errorMessage(error, "The question did not complete."));
1399
+ } finally {
1400
+ window.clearTimeout(timeout);
1401
+ if (state.helperQuestionController === controller) {
1402
+ state.helperQuestionController = null;
1403
+ questionInput.disabled = false;
1404
+ button.disabled = false;
1405
+ button.textContent = priorLabel;
1406
+ }
1407
+ }
1408
+ }
1409
+
1410
+ function clearPrefillToken() {
1411
+ try {
1412
+ delete window.__REMNIC_ADMIN_CONSOLE_PREFILL_TOKEN__;
1413
+ } catch {
1414
+ window.__REMNIC_ADMIN_CONSOLE_PREFILL_TOKEN__ = "";
1415
+ }
1416
+ for (const script of document.scripts) {
1417
+ if (script.src || !script.textContent?.includes("__REMNIC_ADMIN_CONSOLE_PREFILL_TOKEN__")) continue;
1418
+ script.textContent = "";
1419
+ script.remove();
1420
+ }
1421
+ }
1422
+
1423
+ function bindOwnerEvents() {
1424
+ byId("memoryForm").addEventListener("submit", addMemory);
1425
+ byId("generateButton").addEventListener("click", generateDrafts);
1426
+ byId("newCardButton").addEventListener("click", () => openCardDialog());
1427
+ byId("cardForm").addEventListener("submit", saveCard);
1428
+ byId("cardDialogClose").addEventListener("click", closeCardDialog);
1429
+ byId("cardCancelButton").addEventListener("click", closeCardDialog);
1430
+ byId("cardDialog").addEventListener("cancel", (event) => {
1431
+ if (state.cardSavePending) event.preventDefault();
1432
+ });
1433
+ byId("shareForm").addEventListener("submit", createShare);
1434
+ byId("copyLinkButton").addEventListener("click", copyShareLink);
1435
+ byId("refreshButton").addEventListener("click", async (event) => {
1436
+ try {
1437
+ await withBusy(event.currentTarget, "…", loadOwnerState);
1438
+ announce("Cards and share links refreshed.");
1439
+ } catch (error) {
1440
+ if (error === PAGE_HIDDEN_ABORT) return;
1441
+ setError("shareError", errorMessage(error, "The guide did not refresh."));
1442
+ }
1443
+ });
1444
+ for (const input of document.querySelectorAll('input[name="duration"]')) {
1445
+ input.addEventListener("change", () => {
1446
+ byId("customTimeField").hidden = input.value !== "custom" || !input.checked;
1447
+ });
1448
+ }
1449
+ window.addEventListener("pagehide", (event) => {
1450
+ if (replayMode && event.persisted) return;
1451
+ clearOwnerSession();
1452
+ });
1453
+ window.addEventListener("pageshow", (event) => {
1454
+ if (!event.persisted || replayMode) return;
1455
+ clearOwnerSession();
1456
+ });
1457
+ }
1458
+
1459
+ function clearOwnerSession() {
1460
+ state.ownerLifecyclePaused = true;
1461
+ state.ownerSessionGeneration += 1;
1462
+ state.ownerLoadGeneration += 1;
1463
+ for (const controller of state.ownerRequestControllers) controller.abort(PAGE_HIDDEN_ABORT);
1464
+ state.ownerRequestControllers.clear();
1465
+ state.ownerServerOffsetMs = null;
1466
+ state.token = "";
1467
+ state.cards = [];
1468
+ state.grants = [];
1469
+ state.selectedNotes = [];
1470
+ state.pendingCardMutationIds.clear();
1471
+ state.pendingGrantRevocationIds.clear();
1472
+ state.cardSavePending = false;
1473
+ state.notePreviewPending = false;
1474
+ state.draftGenerationPending = false;
1475
+ state.shareCreationPending = false;
1476
+ state.shareCreationCardIds = null;
1477
+ clearDisplayedShareLink();
1478
+ window.clearTimeout(state.ownerGrantExpiryTimer);
1479
+ state.ownerGrantExpiryTimer = null;
1480
+ clearPrefillToken();
1481
+ window.clearTimeout(state.announcementTimer);
1482
+ state.announcementTimer = null;
1483
+ window.clearTimeout(state.toastTimer);
1484
+ byId("connectForm").reset();
1485
+ byId("memoryForm").reset();
1486
+ byId("memoryForm").querySelector('button[type="submit"]').textContent = "Add selected note";
1487
+ byId("generateButton").textContent = "Draft my support cards";
1488
+ byId("cardForm").reset();
1489
+ byId("shareForm").reset();
1490
+ byId("shareForm").querySelector('button[type="submit"]').textContent = "Create share link";
1491
+ byId("customTimeField").hidden = true;
1492
+ if (byId("cardDialog").open) byId("cardDialog").close();
1493
+ byId("toast").textContent = "";
1494
+ byId("toast").classList.remove("visible");
1495
+ byId("announcer").textContent = "";
1496
+ for (const id of ["connectError", "memoryError", "generateError", "cardError", "shareError"]) setError(id);
1497
+ setCardSavePending(false);
1498
+ setDraftGenerationPending(false);
1499
+ setShareCreationPending(false);
1500
+ renderSelectedNotes();
1501
+ renderCards();
1502
+ renderShareCards();
1503
+ renderGrants();
1504
+ byId("ownerView").hidden = true;
1505
+ byId("connectPanel").hidden = false;
1506
+ byId("viewMarkerText").textContent = "Owner view";
1507
+ }
1508
+
1509
+ function bindHelperEvents() {
1510
+ byId("retryHelperButton").addEventListener("click", async (event) => {
1511
+ await withBusy(event.currentTarget, "Trying again…", loadHelper);
1512
+ });
1513
+ byId("questionForm").addEventListener("submit", askQuestion);
1514
+ byId("questionInput").addEventListener("input", () => {
1515
+ byId("questionCount").textContent = String(byId("questionInput").value.length);
1516
+ });
1517
+ replayChannel?.addEventListener("message", (event) => {
1518
+ if (event.data?.type === "grant-revoked" && event.data.grantId === state.helperGrantId) {
1519
+ const error = new Error("The share link is no longer active.");
1520
+ error.code = "grant_gone";
1521
+ showLocked(error);
1522
+ return;
1523
+ }
1524
+ if (event.data?.type === "cards-stale" && Array.isArray(event.data.cardIds)) {
1525
+ for (const cardId of event.data.cardIds) state.replayInvalidatedCardIds.add(cardId);
1526
+ }
1527
+ if (
1528
+ event.data?.type === "cards-stale" &&
1529
+ Array.isArray(event.data.cardIds) &&
1530
+ state.guide?.cards.some((card) => state.replayInvalidatedCardIds.has(card.cardId))
1531
+ ) {
1532
+ const error = new Error("The shared support guide has changed.");
1533
+ error.code = "grant_stale";
1534
+ showLocked(error);
1535
+ }
1536
+ });
1537
+ window.addEventListener("pagehide", (event) => {
1538
+ clearHelperSession();
1539
+ if (!event.persisted) replayChannel?.close();
1540
+ });
1541
+ window.addEventListener("pageshow", (event) => {
1542
+ if (!event.persisted) return;
1543
+ clearHelperSession();
1544
+ });
1545
+ }
1546
+
1547
+ function clearHelperSession() {
1548
+ state.helperLifecyclePaused = true;
1549
+ window.clearTimeout(state.announcementTimer);
1550
+ state.announcementTimer = null;
1551
+ state.helperLoadController?.abort(PAGE_HIDDEN_ABORT);
1552
+ state.helperQuestionController?.abort(PAGE_HIDDEN_ABORT);
1553
+ state.helperRevalidationController?.abort(PAGE_HIDDEN_ABORT);
1554
+ state.helperSecret = "";
1555
+ state.helperServerOffsetMs = null;
1556
+ state.helperRevalidationDelayMs = HELPER_REVALIDATION_MS;
1557
+ const error = new Error("The helper session ended when this page was hidden.");
1558
+ error.code = "session_ended";
1559
+ showLocked(error);
1560
+ byId("helperExpiry").textContent = "";
1561
+ byId("helperCardCount").textContent = "";
1562
+ }
1563
+
1564
+ async function init() {
1565
+ if (replayMode) byId("replayBanner").hidden = false;
1566
+ if (grantId || hasSecretFragment) {
1567
+ clearPrefillToken();
1568
+ bindHelperEvents();
1569
+ await loadHelper();
1570
+ return;
1571
+ }
1572
+
1573
+ bindOwnerEvents();
1574
+ const prefillValue = window.__REMNIC_ADMIN_CONSOLE_PREFILL_TOKEN__;
1575
+ const prefill = typeof prefillValue === "string" ? prefillValue.trim() : "";
1576
+ clearPrefillToken();
1577
+ if (replayMode) {
1578
+ state.token = "synthetic-replay";
1579
+ state.selectedNotes = replayStore.notes.map((note) => ({ ...note }));
1580
+ renderSelectedNotes();
1581
+ await loadOwnerState();
1582
+ showOwner();
1583
+ bindReplayOwnerBridge();
1584
+ return;
1585
+ }
1586
+ if (prefill) byId("tokenInput").value = prefill;
1587
+ byId("connectForm").addEventListener("submit", connectOwner);
1588
+ }
1589
+
1590
+ void init().catch((error) => {
1591
+ if (grantId || hasSecretFragment) showLocked(error);
1592
+ else setError("connectError", errorMessage(error, "The support passport did not start."));
1593
+ });
1594
+ })();