@ic-reactor/mcp 0.0.0-stage → 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (1041) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.yml +108 -0
  2. package/.github/ISSUE_TEMPLATE/config.yml +8 -0
  3. package/.github/ISSUE_TEMPLATE/feature_request.yml +77 -0
  4. package/.github/actions/setup-rust-wasm/action.yml +25 -0
  5. package/.github/dependabot.yml +107 -0
  6. package/.github/workflows/auth-latest.yml +67 -0
  7. package/.github/workflows/b3pay-site.yml +49 -0
  8. package/.github/workflows/ci.yml +430 -0
  9. package/.github/workflows/codeql.yml +41 -0
  10. package/.github/workflows/dependency-review.yml +68 -0
  11. package/.github/workflows/docs-check.yml +98 -0
  12. package/.github/workflows/docs.yml +265 -0
  13. package/.github/workflows/e2e.yml +90 -0
  14. package/.github/workflows/examples-published.yml +97 -0
  15. package/.github/workflows/release.yml +326 -0
  16. package/.husky/pre-commit +12 -0
  17. package/.prettierignore +109 -0
  18. package/.prettierrc +6 -0
  19. package/AGENTS.md +103 -0
  20. package/CHANGELOG.md +3268 -0
  21. package/CLAUDE.md +93 -0
  22. package/CNAME +1 -0
  23. package/CODE_OF_CONDUCT.md +7 -0
  24. package/CONTRIBUTING.md +433 -0
  25. package/LICENSE +21 -0
  26. package/README.md +2 -2
  27. package/SECURITY.md +74 -0
  28. package/b3pay-module.json +194 -0
  29. package/docs/.remarkrc.json +10 -0
  30. package/docs/README.md +64 -0
  31. package/docs/astro.config.mjs +205 -0
  32. package/docs/favicon.svg +15 -0
  33. package/docs/package.json +39 -0
  34. package/docs/public/favicon.svg +15 -0
  35. package/docs/public/ic-reactor-social-card.png +0 -0
  36. package/docs/scripts/b3pay-static.mjs +22 -0
  37. package/docs/scripts/check-example-docs.mjs +214 -0
  38. package/docs/scripts/check-links.mjs +212 -0
  39. package/docs/scripts/fix-dist-links.js +125 -0
  40. package/docs/scripts/fix-frontmatter.js +71 -0
  41. package/docs/scripts/remove-redundant-h1.cjs +48 -0
  42. package/docs/scripts/rewrite-typedoc-links.js +110 -0
  43. package/docs/scripts/split-index.js +69 -0
  44. package/docs/src/assets/ic-reactor-hero-dark.svg +23 -0
  45. package/docs/src/assets/ic-reactor-hero-light.svg +23 -0
  46. package/docs/src/assets/ic-reactor-lockup-dark.svg +15 -0
  47. package/docs/src/assets/ic-reactor-lockup-light.svg +15 -0
  48. package/docs/src/assets/ic-reactor-mark-dark.svg +14 -0
  49. package/docs/src/assets/ic-reactor-mark-light.svg +14 -0
  50. package/docs/src/assets/layers/candid-core-layer-icon-dark.svg +4 -0
  51. package/docs/src/assets/layers/candid-core-layer-icon-light.svg +4 -0
  52. package/docs/src/assets/layers/candid-profile-layer-icon-dark.svg +4 -0
  53. package/docs/src/assets/layers/candid-profile-layer-icon-light.svg +4 -0
  54. package/docs/src/assets/layers/ic-reactor-layer-icon-dark.svg +4 -0
  55. package/docs/src/assets/layers/ic-reactor-layer-icon-light.svg +4 -0
  56. package/docs/src/components/LibraryCards.astro +172 -0
  57. package/docs/src/components/LibraryIcon.astro +28 -0
  58. package/docs/src/components/SiteTitle.astro +241 -0
  59. package/docs/src/content/docs/404.mdx +19 -0
  60. package/docs/src/content/docs/build/agent-tools.mdx +593 -0
  61. package/docs/src/content/docs/build/icrc1-wallet.mdx +619 -0
  62. package/docs/src/content/docs/build/ssr-per-user.mdx +552 -0
  63. package/docs/src/content/docs/examples/canister-studio.mdx +95 -0
  64. package/docs/src/content/docs/examples/icrc-ledger.mdx +45 -0
  65. package/docs/src/content/docs/examples/index.mdx +38 -0
  66. package/docs/src/content/docs/examples/next-ssr.mdx +73 -0
  67. package/docs/src/content/docs/examples/node-agent-tool.mdx +80 -0
  68. package/docs/src/content/docs/examples/vite-wallet.mdx +71 -0
  69. package/docs/src/content/docs/guides/auth.mdx +1181 -0
  70. package/docs/src/content/docs/guides/client.mdx +551 -0
  71. package/docs/src/content/docs/guides/deploy.mdx +180 -0
  72. package/docs/src/content/docs/guides/errors.mdx +475 -0
  73. package/docs/src/content/docs/guides/getting-started.mdx +620 -0
  74. package/docs/src/content/docs/guides/reads.mdx +586 -0
  75. package/docs/src/content/docs/guides/ssr.mdx +529 -0
  76. package/docs/src/content/docs/guides/testing.mdx +401 -0
  77. package/docs/src/content/docs/guides/values.mdx +261 -0
  78. package/docs/src/content/docs/guides/vite-plugin.mdx +690 -0
  79. package/docs/src/content/docs/guides/writes.mdx +572 -0
  80. package/docs/src/content/docs/ic-reactor/index.mdx +167 -0
  81. package/docs/src/content/docs/index.mdx +65 -0
  82. package/docs/src/content/docs/migrating-from-3.mdx +535 -0
  83. package/docs/src/content/docs/packages/core.mdx +186 -0
  84. package/docs/src/content/docs/packages/mcp.mdx +163 -0
  85. package/docs/src/content/docs/packages/react.mdx +207 -0
  86. package/docs/src/content/docs/packages/vite-plugin.mdx +73 -0
  87. package/docs/src/content/docs/versioning.mdx +104 -0
  88. package/docs/src/content.config.ts +7 -0
  89. package/docs/src/styles/custom.css +189 -0
  90. package/docs/tsconfig.typedoc.json +26 -0
  91. package/docs/typedoc-entry.ts +17 -0
  92. package/docs/typedoc.json +37 -0
  93. package/e2e/Cargo.lock +646 -0
  94. package/e2e/Cargo.toml +3 -0
  95. package/e2e/README.md +101 -0
  96. package/e2e/frontends/build.mjs +94 -0
  97. package/e2e/frontends/index.html +10 -0
  98. package/e2e/global-setup.ts +65 -0
  99. package/e2e/icp.yaml +38 -0
  100. package/e2e/package.json +40 -0
  101. package/e2e/setup.ts +6 -0
  102. package/e2e/src/actor/Cargo.toml +14 -0
  103. package/e2e/src/actor/hello_actor.did +10 -0
  104. package/e2e/src/actor/src/lib.rs +73 -0
  105. package/e2e/src/calls.test.ts +132 -0
  106. package/e2e/src/declarations/hello_actor.envelope.json +175 -0
  107. package/e2e/src/declarations/hello_actor.ts +15 -0
  108. package/e2e/src/errors.test.ts +107 -0
  109. package/e2e/src/frontend-headers.test.ts +253 -0
  110. package/e2e/src/identity.test.ts +61 -0
  111. package/e2e/src/react.test.tsx +59 -0
  112. package/e2e/src/replica.ts +70 -0
  113. package/e2e/src/vite-env.test.ts +108 -0
  114. package/e2e/test.sh +47 -0
  115. package/e2e/tsconfig.json +20 -0
  116. package/e2e/tsconfig.typecheck.json +9 -0
  117. package/e2e/vitest.config.ts +22 -0
  118. package/eslint.config.mjs +100 -0
  119. package/evals/PREREGISTRATION.md +1346 -0
  120. package/evals/README.md +756 -0
  121. package/evals/conditions/thin/condition.json +8 -0
  122. package/evals/conditions/thin/docs/candid-core-cli.README.md +90 -0
  123. package/evals/conditions/thin/docs/candid-core-schema.README.md +339 -0
  124. package/evals/conditions/thin/package.json +25 -0
  125. package/evals/conditions/thin/starter/src/generated/icrc1.ts +57 -0
  126. package/evals/conditions/thin-guide/condition.json +10 -0
  127. package/evals/conditions/thin-guide/docs/llms.txt +292 -0
  128. package/evals/conditions/v3/condition.json +9 -0
  129. package/evals/conditions/v3/docs/ic-reactor-core.llms.txt +232 -0
  130. package/evals/conditions/v3/docs/ic-reactor-react.llms.txt +280 -0
  131. package/evals/conditions/v3/docs/llms-full.txt +1053 -0
  132. package/evals/conditions/v3/package.json +26 -0
  133. package/evals/conditions/v3/starter/src/declarations/icrc1.did.d.ts +95 -0
  134. package/evals/conditions/v3/starter/src/declarations/icrc1.did.js +140 -0
  135. package/evals/conditions/v4/PORTING.md +384 -0
  136. package/evals/conditions/v4/README.md +106 -0
  137. package/evals/conditions/v4/condition.json +5 -0
  138. package/evals/conditions/v4/docs/llms.txt +323 -0
  139. package/evals/conditions/v4/package.json +27 -0
  140. package/evals/conditions/v4/starter/src/generated/icrc1.ts +78 -0
  141. package/evals/conditions/v4-proto/condition.json +6 -0
  142. package/evals/conditions/v4-proto/docs/llms.txt +291 -0
  143. package/evals/conditions/v4-proto/lib/SHIM-NOTES.md +20 -0
  144. package/evals/conditions/v4-proto/lib/package.json +27 -0
  145. package/evals/conditions/v4-proto/lib/src/client.ts +471 -0
  146. package/evals/conditions/v4-proto/lib/src/errors.ts +214 -0
  147. package/evals/conditions/v4-proto/lib/src/index.ts +36 -0
  148. package/evals/conditions/v4-proto/lib/src/principal.ts +175 -0
  149. package/evals/conditions/v4-proto/lib/src/service.ts +69 -0
  150. package/evals/conditions/v4-proto/lib/src/types.ts +174 -0
  151. package/evals/conditions/v4-proto/lib/test/runtime.test.ts +170 -0
  152. package/evals/conditions/v4-proto/lib/test/tsconfig.json +10 -0
  153. package/evals/conditions/v4-proto/lib/test/types.test-d.ts +145 -0
  154. package/evals/conditions/v4-proto/lib/test/vitest.config.mjs +8 -0
  155. package/evals/conditions/v4-proto/lib/tsconfig.json +17 -0
  156. package/evals/conditions/v4-proto/package.json +26 -0
  157. package/evals/conditions/v4-proto/starter/src/generated/icrc1.service.ts +19 -0
  158. package/evals/conditions/v4-proto/starter/src/generated/icrc1.ts +57 -0
  159. package/evals/drive.mjs +1221 -0
  160. package/evals/gate.mjs +161 -0
  161. package/evals/harness/aggregate.mjs +534 -0
  162. package/evals/harness/aggregate.test.mjs +344 -0
  163. package/evals/harness/assemble.mjs +216 -0
  164. package/evals/harness/assemble.test.mjs +143 -0
  165. package/evals/harness/auth.mjs +91 -0
  166. package/evals/harness/check-docs.mjs +157 -0
  167. package/evals/harness/declarations/icrc1.did.d.ts +95 -0
  168. package/evals/harness/declarations/icrc1.did.js +140 -0
  169. package/evals/harness/drive-auth.test.mjs +584 -0
  170. package/evals/harness/drive.test.mjs +509 -0
  171. package/evals/harness/fake-auth.ts +67 -0
  172. package/evals/harness/fake-ledger.ts +178 -0
  173. package/evals/harness/gate-plan.mjs +104 -0
  174. package/evals/harness/gate-plan.test.mjs +243 -0
  175. package/evals/harness/icrc1.did +88 -0
  176. package/evals/harness/judge.mjs +63 -0
  177. package/evals/harness/judge.test.mjs +81 -0
  178. package/evals/harness/leak-scan.mjs +1373 -0
  179. package/evals/harness/leak-scan.test.mjs +1300 -0
  180. package/evals/harness/probes/amounts.ts +14 -0
  181. package/evals/harness/probes/node-tool.probe.ts +48 -0
  182. package/evals/harness/probes/probe.mjs +149 -0
  183. package/evals/harness/probes/react-wallet.probe.ts +96 -0
  184. package/evals/harness/public/support/auth.ts +38 -0
  185. package/evals/harness/public/support/fake-replica.d.ts +28 -0
  186. package/evals/harness/public/support/fake-replica.js +434 -0
  187. package/evals/harness/public/support/ledger.idl.d.ts +95 -0
  188. package/evals/harness/public/support/ledger.idl.js +140 -0
  189. package/evals/harness/public/support/world.ts +179 -0
  190. package/evals/harness/runs.mjs +139 -0
  191. package/evals/harness/sandbox.mjs +88 -0
  192. package/evals/harness/sandbox.test.mjs +134 -0
  193. package/evals/harness/ship.mjs +264 -0
  194. package/evals/harness/ship.test.mjs +552 -0
  195. package/evals/harness/world.ts +291 -0
  196. package/evals/package.json +26 -0
  197. package/evals/pnpm-lock.yaml +1537 -0
  198. package/evals/pnpm-workspace.yaml +12 -0
  199. package/evals/results/2026-09-30T14-22-05-924Z/pilot.json +384 -0
  200. package/evals/results/2026-09-30T14-22-05-924Z/pilot.rescanned.json +404 -0
  201. package/evals/results/2026-09-30T14-22-05-924Z/pilot.rescanned.rescored.json +404 -0
  202. package/evals/results/2026-09-30T14-22-05-924Z/plan.json +240 -0
  203. package/evals/results/2026-09-30T14-22-05-924Z/summary.json +1800 -0
  204. package/evals/results/2026-09-30T14-22-05-924Z/summary.rescanned.json +1843 -0
  205. package/evals/results/2026-09-30T14-22-05-924Z/summary.rescanned.rescored.json +2100 -0
  206. package/evals/results/2026-09-30T15-48-23-343Z/pilot.json +404 -0
  207. package/evals/results/2026-09-30T15-48-23-343Z/pilot.rescored.json +404 -0
  208. package/evals/results/2026-09-30T15-48-23-343Z/plan.json +242 -0
  209. package/evals/results/2026-09-30T15-48-23-343Z/summary.json +1843 -0
  210. package/evals/results/2026-09-30T15-48-23-343Z/summary.rescored.json +2195 -0
  211. package/evals/results/2026-10-03-beta1-gate/pilot.json +162 -0
  212. package/evals/results/2026-10-03-beta1-gate/plan.json +141 -0
  213. package/evals/results/2026-10-03-beta1-gate/summary.json +983 -0
  214. package/evals/results/2026-10-05-ga-gate/contaminated-run.json +55 -0
  215. package/evals/results/2026-10-05-ga-gate/pilot.json +162 -0
  216. package/evals/results/2026-10-05-ga-gate/plan.json +457 -0
  217. package/evals/results/2026-10-05-ga-gate/summary.json +1037 -0
  218. package/evals/results/2026-10-05-ga-gate/summary.rescanned.json +1037 -0
  219. package/evals/results/README.md +31 -0
  220. package/evals/runs-pilot.log +150 -0
  221. package/evals/runs-pilot2.log +150 -0
  222. package/evals/score.mjs +256 -0
  223. package/evals/setup.mjs +547 -0
  224. package/evals/tasks/node-tool/faulty/thin-accepts-exponent/meta.json +5 -0
  225. package/evals/tasks/node-tool/faulty/thin-accepts-exponent/src/index.ts +121 -0
  226. package/evals/tasks/node-tool/faulty/thin-anonymous-transfer/meta.json +5 -0
  227. package/evals/tasks/node-tool/faulty/thin-anonymous-transfer/src/index.ts +116 -0
  228. package/evals/tasks/node-tool/faulty/thin-every-reject-may-have-executed/meta.json +9 -0
  229. package/evals/tasks/node-tool/faulty/thin-every-reject-may-have-executed/src/index.ts +118 -0
  230. package/evals/tasks/node-tool/faulty/thin-fetches-root-key-off-local/meta.json +5 -0
  231. package/evals/tasks/node-tool/faulty/thin-fetches-root-key-off-local/src/index.ts +118 -0
  232. package/evals/tasks/node-tool/faulty/thin-ignores-root-key/meta.json +5 -0
  233. package/evals/tasks/node-tool/faulty/thin-ignores-root-key/src/index.ts +117 -0
  234. package/evals/tasks/node-tool/faulty/thin-misclassifies-reject-codes/meta.json +9 -0
  235. package/evals/tasks/node-tool/faulty/thin-misclassifies-reject-codes/src/index.ts +119 -0
  236. package/evals/tasks/node-tool/faulty/thin-no-nat64-cap/meta.json +9 -0
  237. package/evals/tasks/node-tool/faulty/thin-no-nat64-cap/src/index.ts +118 -0
  238. package/evals/tasks/node-tool/faulty/thin-number-amount/meta.json +10 -0
  239. package/evals/tasks/node-tool/faulty/thin-number-amount/src/index.ts +117 -0
  240. package/evals/tasks/node-tool/faulty/thin-truncates-fraction-digits/meta.json +5 -0
  241. package/evals/tasks/node-tool/faulty/thin-truncates-fraction-digits/src/index.ts +120 -0
  242. package/evals/tasks/node-tool/faulty/thin-unvalidated-recipient/meta.json +5 -0
  243. package/evals/tasks/node-tool/faulty/thin-unvalidated-recipient/src/index.ts +118 -0
  244. package/evals/tasks/node-tool/faulty/v3-retry-call-errors/meta.json +11 -0
  245. package/evals/tasks/node-tool/faulty/v3-retry-call-errors/src/index.ts +149 -0
  246. package/evals/tasks/node-tool/faulty/v4-anonymous-identity-sent/meta.json +5 -0
  247. package/evals/tasks/node-tool/faulty/v4-anonymous-identity-sent/src/index.ts +71 -0
  248. package/evals/tasks/node-tool/faulty/v4-never-may-have-executed/meta.json +9 -0
  249. package/evals/tasks/node-tool/faulty/v4-never-may-have-executed/src/index.ts +84 -0
  250. package/evals/tasks/node-tool/faulty/v4-proto-never-may-have-executed/meta.json +9 -0
  251. package/evals/tasks/node-tool/faulty/v4-proto-never-may-have-executed/src/index.ts +72 -0
  252. package/evals/tasks/node-tool/faulty/v4-proto-refuses-nat64-max/meta.json +5 -0
  253. package/evals/tasks/node-tool/faulty/v4-proto-refuses-nat64-max/src/index.ts +72 -0
  254. package/evals/tasks/node-tool/faulty/v4-refuses-nat64-max/meta.json +5 -0
  255. package/evals/tasks/node-tool/faulty/v4-refuses-nat64-max/src/index.ts +84 -0
  256. package/evals/tasks/node-tool/hidden/node-tool.test.ts +312 -0
  257. package/evals/tasks/node-tool/prompt.md +55 -0
  258. package/evals/tasks/node-tool/prompt.minimal.md +30 -0
  259. package/evals/tasks/node-tool/solutions/thin/reference/src/index.ts +118 -0
  260. package/evals/tasks/node-tool/solutions/thin/reference-module-scope/src/index.ts +145 -0
  261. package/evals/tasks/node-tool/solutions/thin-guide/reference/src/index.ts +118 -0
  262. package/evals/tasks/node-tool/solutions/thin-guide/reference-module-scope/src/index.ts +145 -0
  263. package/evals/tasks/node-tool/solutions/v3/reference/src/index.ts +140 -0
  264. package/evals/tasks/node-tool/solutions/v3/reference-module-scope/src/index.ts +153 -0
  265. package/evals/tasks/node-tool/solutions/v4/reference/src/index.ts +84 -0
  266. package/evals/tasks/node-tool/solutions/v4/reference-module-scope/src/index.ts +105 -0
  267. package/evals/tasks/node-tool/solutions/v4-proto/reference/src/index.ts +72 -0
  268. package/evals/tasks/node-tool/solutions/v4-proto/reference-module-scope/src/index.ts +87 -0
  269. package/evals/tasks/node-tool/starter/src/contract.ts +31 -0
  270. package/evals/tasks/node-tool/starter/src/index.ts +7 -0
  271. package/evals/tasks/node-tool/starter/test/smoke.test.ts +22 -0
  272. package/evals/tasks/node-tool/starter/tsconfig.json +17 -0
  273. package/evals/tasks/node-tool/task.json +63 -0
  274. package/evals/tasks/react-wallet/faulty/thin-accepts-exponent/meta.json +5 -0
  275. package/evals/tasks/react-wallet/faulty/thin-accepts-exponent/src/Wallet.tsx +216 -0
  276. package/evals/tasks/react-wallet/faulty/thin-anonymous-transfer/meta.json +5 -0
  277. package/evals/tasks/react-wallet/faulty/thin-anonymous-transfer/src/Wallet.tsx +212 -0
  278. package/evals/tasks/react-wallet/faulty/thin-both-auth-buttons/meta.json +5 -0
  279. package/evals/tasks/react-wallet/faulty/thin-both-auth-buttons/src/Wallet.tsx +210 -0
  280. package/evals/tasks/react-wallet/faulty/thin-constant-balance-key/meta.json +8 -0
  281. package/evals/tasks/react-wallet/faulty/thin-constant-balance-key/src/Wallet.tsx +213 -0
  282. package/evals/tasks/react-wallet/faulty/thin-no-nat64-cap/meta.json +9 -0
  283. package/evals/tasks/react-wallet/faulty/thin-no-nat64-cap/src/Wallet.tsx +213 -0
  284. package/evals/tasks/react-wallet/faulty/thin-number-balance/meta.json +5 -0
  285. package/evals/tasks/react-wallet/faulty/thin-number-balance/src/Wallet.tsx +213 -0
  286. package/evals/tasks/react-wallet/faulty/thin-transfer-as-query/meta.json +5 -0
  287. package/evals/tasks/react-wallet/faulty/thin-transfer-as-query/src/Wallet.tsx +254 -0
  288. package/evals/tasks/react-wallet/faulty/thin-truncates-fraction-digits/meta.json +5 -0
  289. package/evals/tasks/react-wallet/faulty/thin-truncates-fraction-digits/src/Wallet.tsx +215 -0
  290. package/evals/tasks/react-wallet/faulty/thin-unguarded-balance-effect/meta.json +5 -0
  291. package/evals/tasks/react-wallet/faulty/thin-unguarded-balance-effect/src/Wallet.tsx +214 -0
  292. package/evals/tasks/react-wallet/faulty/v3-http-4xx-unknown/meta.json +5 -0
  293. package/evals/tasks/react-wallet/faulty/v3-http-4xx-unknown/src/Wallet.tsx +224 -0
  294. package/evals/tasks/react-wallet/faulty/v3-mutation-retry-3/meta.json +12 -0
  295. package/evals/tasks/react-wallet/faulty/v3-mutation-retry-3/src/Wallet.tsx +225 -0
  296. package/evals/tasks/react-wallet/faulty/v3-unknown-as-error/meta.json +11 -0
  297. package/evals/tasks/react-wallet/faulty/v3-unknown-as-error/src/Wallet.tsx +182 -0
  298. package/evals/tasks/react-wallet/faulty/v4-every-reject-unknown/meta.json +5 -0
  299. package/evals/tasks/react-wallet/faulty/v4-every-reject-unknown/src/Wallet.tsx +160 -0
  300. package/evals/tasks/react-wallet/faulty/v4-keep-previous-data/meta.json +5 -0
  301. package/evals/tasks/react-wallet/faulty/v4-keep-previous-data/src/Wallet.tsx +166 -0
  302. package/evals/tasks/react-wallet/faulty/v4-proto-every-reject-unknown/meta.json +5 -0
  303. package/evals/tasks/react-wallet/faulty/v4-proto-every-reject-unknown/src/Wallet.tsx +161 -0
  304. package/evals/tasks/react-wallet/faulty/v4-proto-keep-previous-data/meta.json +5 -0
  305. package/evals/tasks/react-wallet/faulty/v4-proto-keep-previous-data/src/Wallet.tsx +163 -0
  306. package/evals/tasks/react-wallet/faulty/v4-proto-retry-spread/meta.json +12 -0
  307. package/evals/tasks/react-wallet/faulty/v4-proto-retry-spread/src/Wallet.tsx +162 -0
  308. package/evals/tasks/react-wallet/faulty/v4-proto-status-not-idle/meta.json +5 -0
  309. package/evals/tasks/react-wallet/faulty/v4-proto-status-not-idle/src/Wallet.tsx +163 -0
  310. package/evals/tasks/react-wallet/faulty/v4-retry-spread/meta.json +12 -0
  311. package/evals/tasks/react-wallet/faulty/v4-retry-spread/src/Wallet.tsx +163 -0
  312. package/evals/tasks/react-wallet/faulty/v4-status-not-idle/meta.json +5 -0
  313. package/evals/tasks/react-wallet/faulty/v4-status-not-idle/src/Wallet.tsx +162 -0
  314. package/evals/tasks/react-wallet/hidden/react-wallet.test.ts +536 -0
  315. package/evals/tasks/react-wallet/prompt.md +88 -0
  316. package/evals/tasks/react-wallet/prompt.minimal.md +51 -0
  317. package/evals/tasks/react-wallet/solutions/thin/reference/src/Wallet.tsx +213 -0
  318. package/evals/tasks/react-wallet/solutions/thin/reference-module-scope/src/Wallet.tsx +227 -0
  319. package/evals/tasks/react-wallet/solutions/thin-guide/reference/src/Wallet.tsx +213 -0
  320. package/evals/tasks/react-wallet/solutions/thin-guide/reference-module-scope/src/Wallet.tsx +227 -0
  321. package/evals/tasks/react-wallet/solutions/v3/reference/src/Wallet.tsx +224 -0
  322. package/evals/tasks/react-wallet/solutions/v3/reference-module-scope/src/Wallet.tsx +230 -0
  323. package/evals/tasks/react-wallet/solutions/v4/reference/src/Wallet.tsx +160 -0
  324. package/evals/tasks/react-wallet/solutions/v4/reference-module-scope/src/Wallet.tsx +178 -0
  325. package/evals/tasks/react-wallet/solutions/v4-proto/reference/src/Wallet.tsx +161 -0
  326. package/evals/tasks/react-wallet/solutions/v4-proto/reference-module-scope/src/Wallet.tsx +161 -0
  327. package/evals/tasks/react-wallet/starter/src/Wallet.tsx +12 -0
  328. package/evals/tasks/react-wallet/starter/src/auth.ts +16 -0
  329. package/evals/tasks/react-wallet/starter/src/config.ts +14 -0
  330. package/evals/tasks/react-wallet/starter/src/ii-auth.ts +43 -0
  331. package/evals/tasks/react-wallet/starter/src/main.tsx +15 -0
  332. package/evals/tasks/react-wallet/starter/test/smoke.test.ts +34 -0
  333. package/evals/tasks/react-wallet/starter/tsconfig.json +18 -0
  334. package/evals/tasks/react-wallet/task.json +84 -0
  335. package/examples/canister-studio/.stackblitzrc +4 -0
  336. package/examples/canister-studio/README.md +183 -0
  337. package/examples/canister-studio/did/ckbtc_minter.did +806 -0
  338. package/examples/canister-studio/did/icp_ledger.did +586 -0
  339. package/examples/canister-studio/did/icrc1.did +88 -0
  340. package/examples/canister-studio/package.json +45 -0
  341. package/examples/canister-studio/scripts/smoke.ts +188 -0
  342. package/examples/canister-studio/src/calls/CacheKeyPanel.tsx +104 -0
  343. package/examples/canister-studio/src/calls/ConfirmWrite.tsx +265 -0
  344. package/examples/canister-studio/src/calls/ErrorPanel.test.tsx +113 -0
  345. package/examples/canister-studio/src/calls/ErrorPanel.tsx +214 -0
  346. package/examples/canister-studio/src/calls/MethodRunner.test.tsx +354 -0
  347. package/examples/canister-studio/src/calls/MethodRunner.tsx +57 -0
  348. package/examples/canister-studio/src/calls/ReadRunner.tsx +244 -0
  349. package/examples/canister-studio/src/calls/ResendPanel.tsx +165 -0
  350. package/examples/canister-studio/src/calls/ShareLink.tsx +59 -0
  351. package/examples/canister-studio/src/calls/WriteRunner.test.tsx +678 -0
  352. package/examples/canister-studio/src/calls/WriteRunner.tsx +670 -0
  353. package/examples/canister-studio/src/calls/dedupe.ts +189 -0
  354. package/examples/canister-studio/src/calls/error-info.ts +168 -0
  355. package/examples/canister-studio/src/calls/history.test.ts +284 -0
  356. package/examples/canister-studio/src/calls/history.ts +287 -0
  357. package/examples/canister-studio/src/calls/kinds.ts +68 -0
  358. package/examples/canister-studio/src/calls/prepare.ts +32 -0
  359. package/examples/canister-studio/src/calls/query-options.ts +30 -0
  360. package/examples/canister-studio/src/calls/read.ts +47 -0
  361. package/examples/canister-studio/src/calls/readback.ts +100 -0
  362. package/examples/canister-studio/src/calls/record.ts +123 -0
  363. package/examples/canister-studio/src/calls/safe-calls.test.ts +299 -0
  364. package/examples/canister-studio/src/calls/share.ts +46 -0
  365. package/examples/canister-studio/src/calls/testing/doubles.tsx +108 -0
  366. package/examples/canister-studio/src/calls/testing/fixtures.ts +185 -0
  367. package/examples/canister-studio/src/calls/useCanisterHandle.ts +35 -0
  368. package/examples/canister-studio/src/canisters/ckbtc_minter.envelope.json +4228 -0
  369. package/examples/canister-studio/src/canisters/ckbtc_minter.ts +868 -0
  370. package/examples/canister-studio/src/canisters/icp_ledger.envelope.json +3454 -0
  371. package/examples/canister-studio/src/canisters/icp_ledger.ts +584 -0
  372. package/examples/canister-studio/src/canisters/icrc1.envelope.json +840 -0
  373. package/examples/canister-studio/src/canisters/icrc1.ts +78 -0
  374. package/examples/canister-studio/src/components/ActionButton.tsx +32 -0
  375. package/examples/canister-studio/src/components/AppHeader.tsx +83 -0
  376. package/examples/canister-studio/src/components/AppShell.tsx +29 -0
  377. package/examples/canister-studio/src/components/AuthButton.tsx +79 -0
  378. package/examples/canister-studio/src/components/Badge.tsx +79 -0
  379. package/examples/canister-studio/src/components/CanisterHeader.tsx +200 -0
  380. package/examples/canister-studio/src/components/CanisterJump.tsx +130 -0
  381. package/examples/canister-studio/src/components/Card.tsx +64 -0
  382. package/examples/canister-studio/src/components/Code.tsx +101 -0
  383. package/examples/canister-studio/src/components/CopyButton.tsx +126 -0
  384. package/examples/canister-studio/src/components/Disclosure.tsx +34 -0
  385. package/examples/canister-studio/src/components/EmptyState.tsx +34 -0
  386. package/examples/canister-studio/src/components/MethodList.test.tsx +137 -0
  387. package/examples/canister-studio/src/components/MethodList.tsx +244 -0
  388. package/examples/canister-studio/src/components/Notice.tsx +36 -0
  389. package/examples/canister-studio/src/components/ProvenanceBadge.tsx +44 -0
  390. package/examples/canister-studio/src/components/RouteError.tsx +42 -0
  391. package/examples/canister-studio/src/components/SectionBoundary.tsx +76 -0
  392. package/examples/canister-studio/src/components/Spinner.tsx +33 -0
  393. package/examples/canister-studio/src/components/Tabs.tsx +96 -0
  394. package/examples/canister-studio/src/components/canister/CanisterLoadFailure.test.tsx +56 -0
  395. package/examples/canister-studio/src/components/canister/CanisterLoadFailure.tsx +165 -0
  396. package/examples/canister-studio/src/components/canister/CanisterNotFound.tsx +59 -0
  397. package/examples/canister-studio/src/components/canister/CanisterPending.tsx +33 -0
  398. package/examples/canister-studio/src/components/canister/MethodOverview.tsx +93 -0
  399. package/examples/canister-studio/src/components/canister/MethodWorkspace.test.tsx +201 -0
  400. package/examples/canister-studio/src/components/canister/MethodWorkspace.tsx +187 -0
  401. package/examples/canister-studio/src/components/canister/shared-call.test.ts +186 -0
  402. package/examples/canister-studio/src/components/canister/shared-call.ts +133 -0
  403. package/examples/canister-studio/src/components/canister/shared-link-loader.test.ts +116 -0
  404. package/examples/canister-studio/src/components/canister/use-canister-page.ts +44 -0
  405. package/examples/canister-studio/src/components/canister/workspace-key.test.ts +27 -0
  406. package/examples/canister-studio/src/components/canister/workspace-key.ts +36 -0
  407. package/examples/canister-studio/src/components/canister-id-input.ts +69 -0
  408. package/examples/canister-studio/src/components/canister-names.ts +11 -0
  409. package/examples/canister-studio/src/components/download.ts +34 -0
  410. package/examples/canister-studio/src/components/fallbacks.tsx +33 -0
  411. package/examples/canister-studio/src/components/helpers.test.ts +107 -0
  412. package/examples/canister-studio/src/components/history/HistoryList.tsx +284 -0
  413. package/examples/canister-studio/src/components/home/FeaturedLedger.tsx +149 -0
  414. package/examples/canister-studio/src/components/home/OpenCanister.tsx +99 -0
  415. package/examples/canister-studio/src/components/home/featured.test.tsx +92 -0
  416. package/examples/canister-studio/src/components/home/featured.ts +64 -0
  417. package/examples/canister-studio/src/components/interface/CandidTab.tsx +38 -0
  418. package/examples/canister-studio/src/components/interface/CompatReport.tsx +92 -0
  419. package/examples/canister-studio/src/components/interface/CompatibilityTab.tsx +190 -0
  420. package/examples/canister-studio/src/components/interface/ProjectTab.tsx +196 -0
  421. package/examples/canister-studio/src/components/interface/TypeScriptTab.tsx +114 -0
  422. package/examples/canister-studio/src/components/interface/tools.test.tsx +169 -0
  423. package/examples/canister-studio/src/components/modes.ts +31 -0
  424. package/examples/canister-studio/src/components/playground/MethodPicker.tsx +67 -0
  425. package/examples/canister-studio/src/components/playground/PlaygroundForms.tsx +112 -0
  426. package/examples/canister-studio/src/components/playground/PlaygroundTools.tsx +219 -0
  427. package/examples/canister-studio/src/components/playground/sample.ts +43 -0
  428. package/examples/canister-studio/src/components/playground/target.test.tsx +86 -0
  429. package/examples/canister-studio/src/components/primitives.test.tsx +121 -0
  430. package/examples/canister-studio/src/components/provenance.ts +38 -0
  431. package/examples/canister-studio/src/components/route-render.test.tsx +71 -0
  432. package/examples/canister-studio/src/components/use-task.ts +56 -0
  433. package/examples/canister-studio/src/components/wallet/BalancePanel.tsx +110 -0
  434. package/examples/canister-studio/src/components/wallet/LedgerSummary.tsx +63 -0
  435. package/examples/canister-studio/src/components/wallet/MinterPanel.tsx +255 -0
  436. package/examples/canister-studio/src/components/wallet/SendPanel.test.tsx +297 -0
  437. package/examples/canister-studio/src/components/wallet/SendPanel.tsx +446 -0
  438. package/examples/canister-studio/src/components/wallet/ledgers.test.ts +36 -0
  439. package/examples/canister-studio/src/components/wallet/ledgers.ts +111 -0
  440. package/examples/canister-studio/src/forms/JsonPanel.tsx +157 -0
  441. package/examples/canister-studio/src/forms/MethodForm.test.tsx +369 -0
  442. package/examples/canister-studio/src/forms/MethodForm.tsx +368 -0
  443. package/examples/canister-studio/src/forms/TypedForm.tsx +55 -0
  444. package/examples/canister-studio/src/forms/context.ts +62 -0
  445. package/examples/canister-studio/src/forms/convert.ts +131 -0
  446. package/examples/canister-studio/src/forms/edge.test.ts +252 -0
  447. package/examples/canister-studio/src/forms/engine.test.ts +358 -0
  448. package/examples/canister-studio/src/forms/engine.ts +1492 -0
  449. package/examples/canister-studio/src/forms/fields/BytesField.tsx +210 -0
  450. package/examples/canister-studio/src/forms/fields/Field.tsx +106 -0
  451. package/examples/canister-studio/src/forms/fields/FieldShell.tsx +101 -0
  452. package/examples/canister-studio/src/forms/fields/Group.tsx +74 -0
  453. package/examples/canister-studio/src/forms/fields/ListField.tsx +256 -0
  454. package/examples/canister-studio/src/forms/fields/Segmented.tsx +78 -0
  455. package/examples/canister-studio/src/forms/fields/TimestampField.tsx +108 -0
  456. package/examples/canister-studio/src/forms/fields/UnitsField.tsx +119 -0
  457. package/examples/canister-studio/src/forms/fields/composites.tsx +389 -0
  458. package/examples/canister-studio/src/forms/fields/leaves.tsx +350 -0
  459. package/examples/canister-studio/src/forms/fields.test.tsx +396 -0
  460. package/examples/canister-studio/src/forms/helpers.test.ts +225 -0
  461. package/examples/canister-studio/src/forms/hints.ts +128 -0
  462. package/examples/canister-studio/src/forms/json.ts +119 -0
  463. package/examples/canister-studio/src/forms/parsing.test.ts +217 -0
  464. package/examples/canister-studio/src/forms/useMethodForm.ts +251 -0
  465. package/examples/canister-studio/src/interface/browser.test.ts +23 -0
  466. package/examples/canister-studio/src/interface/browser.ts +153 -0
  467. package/examples/canister-studio/src/interface/cache.ts +68 -0
  468. package/examples/canister-studio/src/interface/cli-cache.test.ts +155 -0
  469. package/examples/canister-studio/src/interface/cli-cache.ts +125 -0
  470. package/examples/canister-studio/src/interface/compile.server.test.ts +198 -0
  471. package/examples/canister-studio/src/interface/compile.server.ts +262 -0
  472. package/examples/canister-studio/src/interface/compile.test.ts +108 -0
  473. package/examples/canister-studio/src/interface/compile.ts +305 -0
  474. package/examples/canister-studio/src/interface/docs.test.ts +75 -0
  475. package/examples/canister-studio/src/interface/docs.ts +100 -0
  476. package/examples/canister-studio/src/interface/functions.test.ts +42 -0
  477. package/examples/canister-studio/src/interface/functions.ts +200 -0
  478. package/examples/canister-studio/src/interface/live-candid.server.ts +91 -0
  479. package/examples/canister-studio/src/interface/live-loader.test.ts +515 -0
  480. package/examples/canister-studio/src/interface/live-loader.ts +364 -0
  481. package/examples/canister-studio/src/interface/load.test.ts +328 -0
  482. package/examples/canister-studio/src/interface/load.ts +249 -0
  483. package/examples/canister-studio/src/interface/read-candid.test.ts +185 -0
  484. package/examples/canister-studio/src/interface/read-candid.ts +233 -0
  485. package/examples/canister-studio/src/interface/server-result.ts +42 -0
  486. package/examples/canister-studio/src/interface/typed.ts +68 -0
  487. package/examples/canister-studio/src/interface/types.ts +121 -0
  488. package/examples/canister-studio/src/lib/format.test.ts +107 -0
  489. package/examples/canister-studio/src/lib/format.ts +139 -0
  490. package/examples/canister-studio/src/lib/hex.test.ts +43 -0
  491. package/examples/canister-studio/src/lib/hex.ts +33 -0
  492. package/examples/canister-studio/src/lib/ids.test.ts +70 -0
  493. package/examples/canister-studio/src/lib/ids.ts +93 -0
  494. package/examples/canister-studio/src/lib/reactor.test.ts +55 -0
  495. package/examples/canister-studio/src/lib/reactor.ts +55 -0
  496. package/examples/canister-studio/src/lib/registry.test.ts +74 -0
  497. package/examples/canister-studio/src/lib/registry.ts +238 -0
  498. package/examples/canister-studio/src/lib/schema.ts +148 -0
  499. package/examples/canister-studio/src/routeTree.gen.ts +227 -0
  500. package/examples/canister-studio/src/router.test.ts +290 -0
  501. package/examples/canister-studio/src/router.tsx +127 -0
  502. package/examples/canister-studio/src/routes/__root.tsx +58 -0
  503. package/examples/canister-studio/src/routes/canister.$canisterId.index.tsx +172 -0
  504. package/examples/canister-studio/src/routes/canister.$canisterId.interface.tsx +98 -0
  505. package/examples/canister-studio/src/routes/canister.$canisterId.tsx +91 -0
  506. package/examples/canister-studio/src/routes/history.tsx +55 -0
  507. package/examples/canister-studio/src/routes/index.tsx +169 -0
  508. package/examples/canister-studio/src/routes/playground.tsx +286 -0
  509. package/examples/canister-studio/src/routes/safety.tsx +57 -0
  510. package/examples/canister-studio/src/routes/wallet.tsx +213 -0
  511. package/examples/canister-studio/src/safety/LabTransferForm.tsx +84 -0
  512. package/examples/canister-studio/src/safety/RequestLog.tsx +79 -0
  513. package/examples/canister-studio/src/safety/SafetyLab.test.tsx +89 -0
  514. package/examples/canister-studio/src/safety/SafetyLab.tsx +140 -0
  515. package/examples/canister-studio/src/safety/ScenarioCard.tsx +262 -0
  516. package/examples/canister-studio/src/safety/lab-run.ts +283 -0
  517. package/examples/canister-studio/src/safety/mock-ledger.test.ts +99 -0
  518. package/examples/canister-studio/src/safety/mock-ledger.ts +263 -0
  519. package/examples/canister-studio/src/safety/scenarios.test.ts +157 -0
  520. package/examples/canister-studio/src/safety/scenarios.ts +924 -0
  521. package/examples/canister-studio/src/styles.css +3453 -0
  522. package/examples/canister-studio/src/styles.test.ts +46 -0
  523. package/examples/canister-studio/src/values/CallPreview.test.tsx +86 -0
  524. package/examples/canister-studio/src/values/CallPreview.tsx +148 -0
  525. package/examples/canister-studio/src/values/ErrValueView.tsx +66 -0
  526. package/examples/canister-studio/src/values/Inspector.tsx +133 -0
  527. package/examples/canister-studio/src/values/ReplyView.test.tsx +341 -0
  528. package/examples/canister-studio/src/values/ReplyView.tsx +164 -0
  529. package/examples/canister-studio/src/values/candid-text.test.ts +516 -0
  530. package/examples/canister-studio/src/values/candid-text.ts +479 -0
  531. package/examples/canister-studio/src/values/hints.ts +58 -0
  532. package/examples/canister-studio/src/values/json.test.ts +66 -0
  533. package/examples/canister-studio/src/values/json.ts +31 -0
  534. package/examples/canister-studio/src/values/leaves.tsx +309 -0
  535. package/examples/canister-studio/src/values/schema.test.ts +123 -0
  536. package/examples/canister-studio/src/values/schema.ts +78 -0
  537. package/examples/canister-studio/src/values/test-router.tsx +21 -0
  538. package/examples/canister-studio/src/values/tree.tsx +670 -0
  539. package/examples/canister-studio/src/values/use-sha256.ts +29 -0
  540. package/examples/canister-studio/tsconfig.json +20 -0
  541. package/examples/canister-studio/vite.config.ts +50 -0
  542. package/examples/canister-studio/vitest.config.ts +20 -0
  543. package/examples/icrc-ledger/.stackblitzrc +4 -0
  544. package/examples/icrc-ledger/README.md +100 -0
  545. package/examples/icrc-ledger/ckbtc_minter.did +12 -0
  546. package/examples/icrc-ledger/icp_ledger.did +147 -0
  547. package/examples/icrc-ledger/icrc1.did +88 -0
  548. package/examples/icrc-ledger/index.html +13 -0
  549. package/examples/icrc-ledger/package.json +40 -0
  550. package/examples/icrc-ledger/scripts/smoke.ts +151 -0
  551. package/examples/icrc-ledger/src/App.tsx +71 -0
  552. package/examples/icrc-ledger/src/ErrorPanel.tsx +105 -0
  553. package/examples/icrc-ledger/src/MainnetTab.tsx +450 -0
  554. package/examples/icrc-ledger/src/SandboxTab.test.tsx +379 -0
  555. package/examples/icrc-ledger/src/SandboxTab.tsx +761 -0
  556. package/examples/icrc-ledger/src/blocks.test.ts +247 -0
  557. package/examples/icrc-ledger/src/blocks.ts +140 -0
  558. package/examples/icrc-ledger/src/canisters/ckbtc_minter.envelope.json +96 -0
  559. package/examples/icrc-ledger/src/canisters/ckbtc_minter.ts +16 -0
  560. package/examples/icrc-ledger/src/canisters/icp_ledger.envelope.json +690 -0
  561. package/examples/icrc-ledger/src/canisters/icp_ledger.ts +223 -0
  562. package/examples/icrc-ledger/src/canisters/icrc1.envelope.json +840 -0
  563. package/examples/icrc-ledger/src/canisters/icrc1.ts +78 -0
  564. package/examples/icrc-ledger/src/format.test.ts +41 -0
  565. package/examples/icrc-ledger/src/format.ts +68 -0
  566. package/examples/icrc-ledger/src/ledger.ts +15 -0
  567. package/examples/icrc-ledger/src/main.tsx +10 -0
  568. package/examples/icrc-ledger/src/minter.ts +16 -0
  569. package/examples/icrc-ledger/src/sandbox.test.ts +464 -0
  570. package/examples/icrc-ledger/src/sandbox.ts +281 -0
  571. package/examples/icrc-ledger/src/styles.css +391 -0
  572. package/examples/icrc-ledger/src/transfer-form.test.ts +62 -0
  573. package/examples/icrc-ledger/src/transfer-form.ts +97 -0
  574. package/examples/icrc-ledger/tsconfig.json +19 -0
  575. package/examples/icrc-ledger/vite.config.ts +44 -0
  576. package/examples/icrc-ledger/vitest.config.ts +12 -0
  577. package/examples/next-ssr/.stackblitzrc +4 -0
  578. package/examples/next-ssr/README.md +112 -0
  579. package/examples/next-ssr/icrc1.did +88 -0
  580. package/examples/next-ssr/next.config.ts +10 -0
  581. package/examples/next-ssr/package.json +41 -0
  582. package/examples/next-ssr/scripts/smoke.ts +169 -0
  583. package/examples/next-ssr/src/app/account/CertifiedBalances.tsx +58 -0
  584. package/examples/next-ssr/src/app/account/account.test.tsx +165 -0
  585. package/examples/next-ssr/src/app/account/page.tsx +82 -0
  586. package/examples/next-ssr/src/app/api/balance/[ledger]/[principal]/route.ts +10 -0
  587. package/examples/next-ssr/src/app/globals.css +278 -0
  588. package/examples/next-ssr/src/app/layout.tsx +34 -0
  589. package/examples/next-ssr/src/app/page.tsx +46 -0
  590. package/examples/next-ssr/src/app/providers.browser.test.tsx +127 -0
  591. package/examples/next-ssr/src/app/providers.test.tsx +73 -0
  592. package/examples/next-ssr/src/app/providers.tsx +60 -0
  593. package/examples/next-ssr/src/canisters/icrc1.envelope.json +840 -0
  594. package/examples/next-ssr/src/canisters/icrc1.ts +78 -0
  595. package/examples/next-ssr/src/components/BalanceTable.tsx +43 -0
  596. package/examples/next-ssr/src/components/LedgerCard.test.tsx +96 -0
  597. package/examples/next-ssr/src/components/LedgerCard.tsx +230 -0
  598. package/examples/next-ssr/src/components/LedgerError.tsx +32 -0
  599. package/examples/next-ssr/src/components/LedgerSections.tsx +22 -0
  600. package/examples/next-ssr/src/components/MyBalances.test.tsx +253 -0
  601. package/examples/next-ssr/src/components/MyBalances.tsx +129 -0
  602. package/examples/next-ssr/src/components/SessionBadge.tsx +18 -0
  603. package/examples/next-ssr/src/components/hydration.test.tsx +259 -0
  604. package/examples/next-ssr/src/components/server-data.ts +10 -0
  605. package/examples/next-ssr/src/format.ts +46 -0
  606. package/examples/next-ssr/src/ledgers.ts +38 -0
  607. package/examples/next-ssr/src/server/balance-route.test.ts +151 -0
  608. package/examples/next-ssr/src/server/balance-route.ts +157 -0
  609. package/examples/next-ssr/src/server/error-summary.test.ts +34 -0
  610. package/examples/next-ssr/src/server/error-summary.ts +37 -0
  611. package/examples/next-ssr/src/server/parse-owner.test.ts +39 -0
  612. package/examples/next-ssr/src/server/parse-owner.ts +34 -0
  613. package/examples/next-ssr/src/server/prefetch-ledgers.test.ts +137 -0
  614. package/examples/next-ssr/src/server/prefetch-ledgers.ts +70 -0
  615. package/examples/next-ssr/src/server/read-balances.test.ts +103 -0
  616. package/examples/next-ssr/src/server/read-balances.ts +78 -0
  617. package/examples/next-ssr/src/server/request-client.rsc.test.tsx +110 -0
  618. package/examples/next-ssr/src/server/request-client.ts +24 -0
  619. package/examples/next-ssr/src/testing/mock-ledgers.ts +115 -0
  620. package/examples/next-ssr/src/testing/react-server-dom-turbopack.d.ts +15 -0
  621. package/examples/next-ssr/src/testing/setup.ts +10 -0
  622. package/examples/next-ssr/tsconfig.json +31 -0
  623. package/examples/next-ssr/vitest.config.ts +59 -0
  624. package/examples/node-agent-tool/.stackblitzrc +4 -0
  625. package/examples/node-agent-tool/README.md +165 -0
  626. package/examples/node-agent-tool/icp.yaml +11 -0
  627. package/examples/node-agent-tool/icrc1.did +88 -0
  628. package/examples/node-agent-tool/package.json +32 -0
  629. package/examples/node-agent-tool/src/canisters/icrc1.envelope.json +840 -0
  630. package/examples/node-agent-tool/src/canisters/icrc1.ts +78 -0
  631. package/examples/node-agent-tool/src/cli.test.ts +64 -0
  632. package/examples/node-agent-tool/src/cli.ts +23 -0
  633. package/examples/node-agent-tool/src/command-line.ts +159 -0
  634. package/examples/node-agent-tool/src/commands/balance.ts +73 -0
  635. package/examples/node-agent-tool/src/commands/demo.test.ts +51 -0
  636. package/examples/node-agent-tool/src/commands/demo.ts +359 -0
  637. package/examples/node-agent-tool/src/commands/info.ts +69 -0
  638. package/examples/node-agent-tool/src/commands/reads.test.ts +133 -0
  639. package/examples/node-agent-tool/src/commands/resend.test.ts +363 -0
  640. package/examples/node-agent-tool/src/commands/transfer.test.ts +313 -0
  641. package/examples/node-agent-tool/src/commands/transfer.ts +415 -0
  642. package/examples/node-agent-tool/src/commands/watch.test.ts +159 -0
  643. package/examples/node-agent-tool/src/commands/watch.ts +135 -0
  644. package/examples/node-agent-tool/src/commands/whoami.ts +36 -0
  645. package/examples/node-agent-tool/src/context.ts +20 -0
  646. package/examples/node-agent-tool/src/dispatch.ts +70 -0
  647. package/examples/node-agent-tool/src/failure.test.ts +59 -0
  648. package/examples/node-agent-tool/src/failure.ts +176 -0
  649. package/examples/node-agent-tool/src/identity.test.ts +242 -0
  650. package/examples/node-agent-tool/src/identity.ts +228 -0
  651. package/examples/node-agent-tool/src/input.ts +110 -0
  652. package/examples/node-agent-tool/src/ledgers.test.ts +74 -0
  653. package/examples/node-agent-tool/src/ledgers.ts +62 -0
  654. package/examples/node-agent-tool/src/mock-ledger.ts +211 -0
  655. package/examples/node-agent-tool/src/network.test.ts +128 -0
  656. package/examples/node-agent-tool/src/network.ts +101 -0
  657. package/examples/node-agent-tool/src/output.test.ts +208 -0
  658. package/examples/node-agent-tool/src/output.ts +163 -0
  659. package/examples/node-agent-tool/src/run.ts +73 -0
  660. package/examples/node-agent-tool/src/test-kit.ts +167 -0
  661. package/examples/node-agent-tool/tsconfig.json +18 -0
  662. package/examples/node-agent-tool/vitest.config.ts +8 -0
  663. package/examples/vite-wallet/.stackblitzrc +4 -0
  664. package/examples/vite-wallet/Cargo.lock +646 -0
  665. package/examples/vite-wallet/Cargo.toml +5 -0
  666. package/examples/vite-wallet/README.md +107 -0
  667. package/examples/vite-wallet/backend/Cargo.toml +12 -0
  668. package/examples/vite-wallet/backend/backend.did +37 -0
  669. package/examples/vite-wallet/backend/src/lib.rs +133 -0
  670. package/examples/vite-wallet/icp.yaml +44 -0
  671. package/examples/vite-wallet/index.html +13 -0
  672. package/examples/vite-wallet/ledger.did +54 -0
  673. package/examples/vite-wallet/package.json +46 -0
  674. package/examples/vite-wallet/public/.ic-assets.json5 +26 -0
  675. package/examples/vite-wallet/scripts/faucet-args.test.ts +30 -0
  676. package/examples/vite-wallet/scripts/faucet-args.ts +41 -0
  677. package/examples/vite-wallet/scripts/faucet.ts +41 -0
  678. package/examples/vite-wallet/src/AddressBook.test.tsx +191 -0
  679. package/examples/vite-wallet/src/AddressBook.tsx +147 -0
  680. package/examples/vite-wallet/src/App.tsx +53 -0
  681. package/examples/vite-wallet/src/Balance.test.tsx +57 -0
  682. package/examples/vite-wallet/src/Balance.tsx +58 -0
  683. package/examples/vite-wallet/src/EnvironmentPanel.test.tsx +83 -0
  684. package/examples/vite-wallet/src/EnvironmentPanel.tsx +103 -0
  685. package/examples/vite-wallet/src/ErrorNote.tsx +70 -0
  686. package/examples/vite-wallet/src/Profile.test.tsx +145 -0
  687. package/examples/vite-wallet/src/Profile.tsx +92 -0
  688. package/examples/vite-wallet/src/SendIcp.test.tsx +231 -0
  689. package/examples/vite-wallet/src/SendIcp.tsx +244 -0
  690. package/examples/vite-wallet/src/SendToContact.test.tsx +113 -0
  691. package/examples/vite-wallet/src/SendToContact.tsx +102 -0
  692. package/examples/vite-wallet/src/SignIn.test.tsx +106 -0
  693. package/examples/vite-wallet/src/SignIn.tsx +120 -0
  694. package/examples/vite-wallet/src/Wallet.test.tsx +89 -0
  695. package/examples/vite-wallet/src/account-switch.test.tsx +108 -0
  696. package/examples/vite-wallet/src/auth/dev-accounts.test.ts +113 -0
  697. package/examples/vite-wallet/src/auth/dev-accounts.ts +216 -0
  698. package/examples/vite-wallet/src/auth/local-page.test.ts +28 -0
  699. package/examples/vite-wallet/src/auth/local-page.ts +20 -0
  700. package/examples/vite-wallet/src/auth/wallet-auth.test.ts +159 -0
  701. package/examples/vite-wallet/src/auth/wallet-auth.ts +131 -0
  702. package/examples/vite-wallet/src/canisters/backend.envelope.json +275 -0
  703. package/examples/vite-wallet/src/canisters/backend.ts +58 -0
  704. package/examples/vite-wallet/src/canisters/ledger.envelope.json +451 -0
  705. package/examples/vite-wallet/src/canisters/ledger.ts +56 -0
  706. package/examples/vite-wallet/src/client.ts +45 -0
  707. package/examples/vite-wallet/src/main.tsx +18 -0
  708. package/examples/vite-wallet/src/optimistic-contacts.ts +113 -0
  709. package/examples/vite-wallet/src/refusal.ts +17 -0
  710. package/examples/vite-wallet/src/styles.css +282 -0
  711. package/examples/vite-wallet/src/test/test-wallet.tsx +322 -0
  712. package/examples/vite-wallet/src/token.ts +50 -0
  713. package/examples/vite-wallet/src/transfer.test.ts +178 -0
  714. package/examples/vite-wallet/src/transfer.ts +110 -0
  715. package/examples/vite-wallet/src/use-canisters.ts +49 -0
  716. package/examples/vite-wallet/tsconfig.json +19 -0
  717. package/examples/vite-wallet/vite.config.ts +47 -0
  718. package/examples/vite-wallet/vitest.config.ts +17 -0
  719. package/package.json +9 -4
  720. package/packages/core/.size-limit.js +47 -0
  721. package/packages/core/LICENSE +21 -0
  722. package/packages/core/README.md +84 -0
  723. package/packages/core/llms.txt +610 -0
  724. package/packages/core/package.json +89 -0
  725. package/packages/core/src/call.ts +961 -0
  726. package/packages/core/src/canister.ts +259 -0
  727. package/packages/core/src/client.ts +2061 -0
  728. package/packages/core/src/errors.ts +1079 -0
  729. package/packages/core/src/hydration.ts +239 -0
  730. package/packages/core/src/index.ts +23 -0
  731. package/packages/core/src/keys.ts +108 -0
  732. package/packages/core/src/management.ts +138 -0
  733. package/packages/core/src/network.ts +1020 -0
  734. package/packages/core/src/options.ts +1000 -0
  735. package/packages/core/src/runtime.ts +50 -0
  736. package/packages/core/src/testing/fake-replica.ts +1166 -0
  737. package/packages/core/src/testing/index.ts +16 -0
  738. package/packages/core/src/testing/test-auth.ts +340 -0
  739. package/packages/core/src/testing/test-client.ts +559 -0
  740. package/packages/core/src/types.ts +541 -0
  741. package/packages/core/src/units.ts +299 -0
  742. package/packages/core/src/version.ts +4 -0
  743. package/packages/core/tests/canister-helpers.ts +187 -0
  744. package/packages/core/tests/canister-id.test.ts +158 -0
  745. package/packages/core/tests/canister-unwrap.test.ts +94 -0
  746. package/packages/core/tests/canister.test-d.ts +406 -0
  747. package/packages/core/tests/canister.test.ts +1921 -0
  748. package/packages/core/tests/client-account-change.test.ts +131 -0
  749. package/packages/core/tests/client-agents.test.ts +402 -0
  750. package/packages/core/tests/client-auth-network.test.ts +719 -0
  751. package/packages/core/tests/client-disposed.test.ts +403 -0
  752. package/packages/core/tests/client-helpers.ts +136 -0
  753. package/packages/core/tests/client-hydration.test.ts +166 -0
  754. package/packages/core/tests/client-pinned.test.ts +626 -0
  755. package/packages/core/tests/client-session.test.ts +348 -0
  756. package/packages/core/tests/client.test-d.ts +181 -0
  757. package/packages/core/tests/client.test.ts +1162 -0
  758. package/packages/core/tests/encode.test.ts +171 -0
  759. package/packages/core/tests/env-canister-id.test.ts +666 -0
  760. package/packages/core/tests/env-cookie-copies.test.ts +182 -0
  761. package/packages/core/tests/error-guard.test.ts +222 -0
  762. package/packages/core/tests/errors.test-d.ts +280 -0
  763. package/packages/core/tests/errors.test.ts +1359 -0
  764. package/packages/core/tests/fixtures/README.md +28 -0
  765. package/packages/core/tests/fixtures/accounts.did +21 -0
  766. package/packages/core/tests/fixtures/accounts.envelope.json +367 -0
  767. package/packages/core/tests/fixtures/accounts.ts +26 -0
  768. package/packages/core/tests/fixtures/archive.did +31 -0
  769. package/packages/core/tests/fixtures/archive.envelope.json +259 -0
  770. package/packages/core/tests/fixtures/archive.ts +55 -0
  771. package/packages/core/tests/fixtures/icrc1.did +88 -0
  772. package/packages/core/tests/fixtures/icrc1.envelope.json +840 -0
  773. package/packages/core/tests/fixtures/icrc1.ts +78 -0
  774. package/packages/core/tests/fixtures/management.did +724 -0
  775. package/packages/core/tests/fixtures/management.envelope.json +4455 -0
  776. package/packages/core/tests/fixtures/management.ts +486 -0
  777. package/packages/core/tests/fixtures/shapes.did +33 -0
  778. package/packages/core/tests/fixtures/shapes.envelope.json +440 -0
  779. package/packages/core/tests/fixtures/shapes.ts +56 -0
  780. package/packages/core/tests/fixtures/skippable.did +15 -0
  781. package/packages/core/tests/fixtures/skippable.envelope.json +182 -0
  782. package/packages/core/tests/fixtures/skippable.ts +33 -0
  783. package/packages/core/tests/func.test.ts +154 -0
  784. package/packages/core/tests/identity-attributes.test.ts +214 -0
  785. package/packages/core/tests/keys.test.ts +289 -0
  786. package/packages/core/tests/management.test.ts +206 -0
  787. package/packages/core/tests/network-helpers.ts +121 -0
  788. package/packages/core/tests/network.test-d.ts +78 -0
  789. package/packages/core/tests/network.test.ts +1051 -0
  790. package/packages/core/tests/options.test.ts +1594 -0
  791. package/packages/core/tests/redirect-sign-in.test.ts +393 -0
  792. package/packages/core/tests/resend.test-d.ts +81 -0
  793. package/packages/core/tests/resend.test.ts +723 -0
  794. package/packages/core/tests/skip.test-d.ts +125 -0
  795. package/packages/core/tests/testing-entry.test.ts +73 -0
  796. package/packages/core/tests/testing-fake-replica.test.ts +1242 -0
  797. package/packages/core/tests/testing-test-auth.test-d.ts +84 -0
  798. package/packages/core/tests/testing-test-auth.test.ts +483 -0
  799. package/packages/core/tests/testing-test-client.test-d.ts +184 -0
  800. package/packages/core/tests/testing-test-client.test.ts +1272 -0
  801. package/packages/core/tests/time-realm.test.ts +63 -0
  802. package/packages/core/tests/time.test.ts +238 -0
  803. package/packages/core/tests/traps/README.md +39 -0
  804. package/packages/core/tests/traps/anonymous-write.test.ts +145 -0
  805. package/packages/core/tests/traps/classification-table.test.ts +256 -0
  806. package/packages/core/tests/traps/err-arm.test.ts +144 -0
  807. package/packages/core/tests/traps/fetch-spy.ts +39 -0
  808. package/packages/core/tests/traps/invalidation-after-write.test.ts +178 -0
  809. package/packages/core/tests/traps/ledger.ts +248 -0
  810. package/packages/core/tests/traps/root-key-off-local.test.ts +130 -0
  811. package/packages/core/tests/traps/stale-data-across-users.test.ts +173 -0
  812. package/packages/core/tests/traps/unresolved-name.test.ts +155 -0
  813. package/packages/core/tests/traps/update-as-query.test.ts +244 -0
  814. package/packages/core/tests/traps/update-not-resent.test.ts +126 -0
  815. package/packages/core/tests/traps/update-resent-when-never-delivered.test.ts +159 -0
  816. package/packages/core/tests/traps.test-d.ts +476 -0
  817. package/packages/core/tests/units.test-d.ts +118 -0
  818. package/packages/core/tests/units.test.ts +634 -0
  819. package/packages/core/tsconfig.json +19 -0
  820. package/packages/core/tsconfig.typecheck.json +16 -0
  821. package/packages/core/vitest.config.ts +13 -0
  822. package/packages/mcp/LICENSE +21 -0
  823. package/packages/mcp/README.md +189 -0
  824. package/packages/mcp/package.json +62 -0
  825. package/packages/mcp/src/candid/candid-text.test.ts +455 -0
  826. package/packages/mcp/src/candid/candid-text.ts +501 -0
  827. package/packages/mcp/src/candid/contract.test.ts +398 -0
  828. package/packages/mcp/src/candid/formats.ts +155 -0
  829. package/packages/mcp/src/candid/hex.ts +29 -0
  830. package/packages/mcp/src/candid/json-schema.test.ts +758 -0
  831. package/packages/mcp/src/candid/json-schema.ts +454 -0
  832. package/packages/mcp/src/candid/json.test.ts +1207 -0
  833. package/packages/mcp/src/candid/json.ts +1053 -0
  834. package/packages/mcp/src/candid/schema-walk.ts +189 -0
  835. package/packages/mcp/src/candid/type-text.test.ts +179 -0
  836. package/packages/mcp/src/candid/type-text.ts +238 -0
  837. package/packages/mcp/src/cli.ts +134 -0
  838. package/packages/mcp/src/identity.ts +188 -0
  839. package/packages/mcp/src/interface/cache.test.ts +48 -0
  840. package/packages/mcp/src/interface/cache.ts +68 -0
  841. package/packages/mcp/src/interface/cli-cache.test.ts +150 -0
  842. package/packages/mcp/src/interface/cli-cache.ts +122 -0
  843. package/packages/mcp/src/interface/compile-worker.test.ts +246 -0
  844. package/packages/mcp/src/interface/compile-worker.ts +290 -0
  845. package/packages/mcp/src/interface/compile.test.ts +155 -0
  846. package/packages/mcp/src/interface/compile.ts +281 -0
  847. package/packages/mcp/src/interface/docs.test.ts +105 -0
  848. package/packages/mcp/src/interface/docs.ts +86 -0
  849. package/packages/mcp/src/interface/fixtures/icrc1.did +88 -0
  850. package/packages/mcp/src/interface/hex.ts +8 -0
  851. package/packages/mcp/src/interface/ids.test.ts +71 -0
  852. package/packages/mcp/src/interface/ids.ts +87 -0
  853. package/packages/mcp/src/interface/loader.test.ts +804 -0
  854. package/packages/mcp/src/interface/loader.ts +519 -0
  855. package/packages/mcp/src/interface/read-candid.test.ts +300 -0
  856. package/packages/mcp/src/interface/read-candid.ts +259 -0
  857. package/packages/mcp/src/interface/types.ts +113 -0
  858. package/packages/mcp/src/network.ts +122 -0
  859. package/packages/mcp/src/options.ts +149 -0
  860. package/packages/mcp/src/server/approval.ts +159 -0
  861. package/packages/mcp/src/server/naming.ts +43 -0
  862. package/packages/mcp/src/server/results.ts +102 -0
  863. package/packages/mcp/src/server/server.ts +797 -0
  864. package/packages/mcp/src/server/tools.ts +116 -0
  865. package/packages/mcp/src/usage.ts +40 -0
  866. package/packages/mcp/tests/approval.test.ts +60 -0
  867. package/packages/mcp/tests/identity.test.ts +234 -0
  868. package/packages/mcp/tests/naming.test.ts +62 -0
  869. package/packages/mcp/tests/options.test.ts +140 -0
  870. package/packages/mcp/tests/server.test.ts +997 -0
  871. package/packages/mcp/tsconfig.json +19 -0
  872. package/packages/mcp/tsconfig.typecheck.json +10 -0
  873. package/packages/mcp/vitest.config.ts +10 -0
  874. package/packages/react/LICENSE +21 -0
  875. package/packages/react/README.md +328 -0
  876. package/packages/react/llms.txt +95 -0
  877. package/packages/react/package.json +95 -0
  878. package/packages/react/src/index.tsx +678 -0
  879. package/packages/react/tests/abandoned-render.test.tsx +816 -0
  880. package/packages/react/tests/auth.test.tsx +315 -0
  881. package/packages/react/tests/caller-keys.test.tsx +2334 -0
  882. package/packages/react/tests/client-time.test.ts +40 -0
  883. package/packages/react/tests/entry.test.ts +52 -0
  884. package/packages/react/tests/helpers.tsx +291 -0
  885. package/packages/react/tests/hydration.test.tsx +262 -0
  886. package/packages/react/tests/ownership.test.tsx +260 -0
  887. package/packages/react/tests/provider.server.test.tsx +169 -0
  888. package/packages/react/tests/query-client.test.tsx +107 -0
  889. package/packages/react/tests/strict-mode.test.tsx +288 -0
  890. package/packages/react/tests/suspense.test-d.tsx +357 -0
  891. package/packages/react/tests/two-query-cores.test.tsx +135 -0
  892. package/packages/react/tests/types.test-d.tsx +63 -0
  893. package/packages/react/tsconfig.json +23 -0
  894. package/packages/react/tsconfig.test.json +8 -0
  895. package/packages/react/tsconfig.typecheck.json +16 -0
  896. package/packages/react/vitest.config.ts +39 -0
  897. package/packages/vite-plugin/LICENSE +21 -0
  898. package/packages/vite-plugin/README.md +532 -0
  899. package/packages/vite-plugin/package.json +76 -0
  900. package/packages/vite-plugin/src/describe-value.test.ts +43 -0
  901. package/packages/vite-plugin/src/describe-value.ts +167 -0
  902. package/packages/vite-plugin/src/dev-environment.test.ts +747 -0
  903. package/packages/vite-plugin/src/dev-environment.ts +510 -0
  904. package/packages/vite-plugin/src/env.ts +431 -0
  905. package/packages/vite-plugin/src/fetch.ts +348 -0
  906. package/packages/vite-plugin/src/file-identity.ts +106 -0
  907. package/packages/vite-plugin/src/generate.ts +691 -0
  908. package/packages/vite-plugin/src/index.test.ts +1243 -0
  909. package/packages/vite-plugin/src/index.ts +2565 -0
  910. package/packages/vite-plugin/src/interface.ts +339 -0
  911. package/packages/vite-plugin/src/local-host.ts +34 -0
  912. package/packages/vite-plugin/src/options.ts +641 -0
  913. package/packages/vite-plugin/src/principal-text.test.ts +87 -0
  914. package/packages/vite-plugin/src/principal-text.ts +98 -0
  915. package/packages/vite-plugin/tests/close.test.ts +141 -0
  916. package/packages/vite-plugin/tests/environment-mode.test.ts +136 -0
  917. package/packages/vite-plugin/tests/fake-ic.ts +214 -0
  918. package/packages/vite-plugin/tests/fetch-sdk.test.ts +27 -0
  919. package/packages/vite-plugin/tests/fetch.test.ts +1196 -0
  920. package/packages/vite-plugin/tests/fixtures/fake-cli/bin.js +90 -0
  921. package/packages/vite-plugin/tests/fixtures/fake-cli/package.json +8 -0
  922. package/packages/vite-plugin/tests/fixtures/icrc1.did +88 -0
  923. package/packages/vite-plugin/tests/generation.test.ts +1036 -0
  924. package/packages/vite-plugin/tests/icp-command.test.ts +194 -0
  925. package/packages/vite-plugin/tests/icp-timeout.test.ts +83 -0
  926. package/packages/vite-plugin/tests/no-process-exit.test.ts +83 -0
  927. package/packages/vite-plugin/tests/on-generated.test.ts +323 -0
  928. package/packages/vite-plugin/tests/output-collision.test.ts +329 -0
  929. package/packages/vite-plugin/tests/package.test.ts +62 -0
  930. package/packages/vite-plugin/tests/project.test.ts +2993 -0
  931. package/packages/vite-plugin/tests/support.ts +339 -0
  932. package/packages/vite-plugin/tests/timeout.test.ts +107 -0
  933. package/packages/vite-plugin/tsconfig.json +23 -0
  934. package/packages/vite-plugin/tsconfig.typecheck.json +16 -0
  935. package/packages/vite-plugin/vitest.config.ts +9 -0
  936. package/pnpm-workspace.yaml +23 -0
  937. package/scripts/ai-context-files.js +72 -0
  938. package/scripts/assemble-docs-site.mjs +485 -0
  939. package/scripts/assemble-docs-site.test.mjs +568 -0
  940. package/scripts/build-examples.js +58 -0
  941. package/scripts/check-ai-context.js +963 -0
  942. package/scripts/check-ai-context.test.mjs +811 -0
  943. package/scripts/check-exports.mjs +767 -0
  944. package/scripts/check-exports.test.mjs +1136 -0
  945. package/scripts/check-snippets/ambient.d.ts +428 -0
  946. package/scripts/check-snippets/app/generated/backend.did +7 -0
  947. package/scripts/check-snippets/app/generated/backend.ts +15 -0
  948. package/scripts/check-snippets/app/generated/icrc1.did +88 -0
  949. package/scripts/check-snippets/app/generated/icrc1.ts +78 -0
  950. package/scripts/check-snippets/app/globals.ts +26 -0
  951. package/scripts/check-snippets/docs/guides/App.tsx +21 -0
  952. package/scripts/check-snippets/docs/guides/app/providers.tsx +23 -0
  953. package/scripts/check-snippets/docs/guides/auth-client.ts +47 -0
  954. package/scripts/check-snippets/docs/guides/canisters/backend.ts +3 -0
  955. package/scripts/check-snippets/docs/guides/canisters/icrc1.ts +3 -0
  956. package/scripts/check-snippets/docs/guides/components/LedgerCard.tsx +26 -0
  957. package/scripts/check-snippets/docs/guides/components/shell.tsx +11 -0
  958. package/scripts/check-snippets/docs/guides/components/sign-in-page.tsx +29 -0
  959. package/scripts/check-snippets/docs/guides/components/slow-section.tsx +5 -0
  960. package/scripts/check-snippets/docs/guides/generated/accounts.ts +95 -0
  961. package/scripts/check-snippets/docs/guides/generated/ckbtc_minter.ts +40 -0
  962. package/scripts/check-snippets/docs/guides/generated/icp_ledger.ts +267 -0
  963. package/scripts/check-snippets/docs/guides/ledger.ts +10 -0
  964. package/scripts/check-snippets/docs/guides/server/request-client.ts +8 -0
  965. package/scripts/check-snippets.mjs +993 -0
  966. package/scripts/check-snippets.test.mjs +338 -0
  967. package/scripts/check-stack.mjs +501 -0
  968. package/scripts/check-stack.test.mjs +800 -0
  969. package/scripts/clean-node-modules.js +67 -0
  970. package/scripts/example-projects.js +58 -0
  971. package/scripts/export-budget.mjs +145 -0
  972. package/scripts/faults.json +6749 -0
  973. package/scripts/lib/fault-workspace.mjs +210 -0
  974. package/scripts/lib/io.mjs +29 -0
  975. package/scripts/lib/io.test.mjs +38 -0
  976. package/scripts/lib/markdown-fences.mjs +56 -0
  977. package/scripts/lib/markdown-fences.test.mjs +84 -0
  978. package/scripts/lib/package-exports.mjs +54 -0
  979. package/scripts/lib/package-exports.test.mjs +66 -0
  980. package/scripts/lib/release-pr-steps.mjs +47 -0
  981. package/scripts/release-tag.mjs +95 -0
  982. package/scripts/release-tag.test.mjs +278 -0
  983. package/scripts/release-workflow.test.mjs +98 -0
  984. package/scripts/release.js +167 -0
  985. package/scripts/release.test.mjs +347 -0
  986. package/scripts/removed-v3-names.js +125 -0
  987. package/scripts/size-app/.size-limit.js +90 -0
  988. package/scripts/size-app/core.js +3 -0
  989. package/scripts/size-app/package.json +24 -0
  990. package/scripts/size-app/react.js +4 -0
  991. package/scripts/size-app.test.mjs +73 -0
  992. package/scripts/sync-ai-context-versions.js +102 -0
  993. package/scripts/sync-example-versions.js +151 -0
  994. package/scripts/test-examples-published.mjs +1585 -0
  995. package/scripts/test-examples-published.test.mjs +1586 -0
  996. package/scripts/traps/any-method-vars-not-a-list.json +5 -0
  997. package/scripts/traps/canister-method-unknown.json +5 -0
  998. package/scripts/traps/client-says-who-calls-once.json +5 -0
  999. package/scripts/traps/client-says-who-calls.json +5 -0
  1000. package/scripts/traps/err-only-on-canister-err.json +14 -0
  1001. package/scripts/traps/format-units-refuses-missing-value.json +5 -0
  1002. package/scripts/traps/format-units-refuses-number.json +5 -0
  1003. package/scripts/traps/format-units-unknown-option.json +5 -0
  1004. package/scripts/traps/guard-err-is-the-methods.json +5 -0
  1005. package/scripts/traps/guard-false-keeps-every-kind.json +5 -0
  1006. package/scripts/traps/internals-not-exported.json +9 -0
  1007. package/scripts/traps/network-refuses-unknown-name.json +5 -0
  1008. package/scripts/traps/never-err-has-no-type.json +5 -0
  1009. package/scripts/traps/options-method-unknown.json +5 -0
  1010. package/scripts/traps/options-vars-wrong-type.json +5 -0
  1011. package/scripts/traps/parse-units-refuses-number.json +5 -0
  1012. package/scripts/traps/parse-units-unknown-option.json +5 -0
  1013. package/scripts/traps/principal-is-not-plain-text.json +14 -0
  1014. package/scripts/traps/query-options-refuses-oneway.json +5 -0
  1015. package/scripts/traps/query-options-refuses-update.json +5 -0
  1016. package/scripts/traps/refuse-next-unknown-option.json +5 -0
  1017. package/scripts/traps/resend-key-reads-the-methods-arg.json +5 -0
  1018. package/scripts/traps/resend-states-its-dedupe-key.json +5 -0
  1019. package/scripts/traps/suspense-refuses-argument-skip.json +5 -0
  1020. package/scripts/traps/suspense-refuses-skippable-read.json +5 -0
  1021. package/scripts/traps/suspense-refuses-vars-skip-fits.json +5 -0
  1022. package/scripts/traps/test-handler-method-unknown.json +5 -0
  1023. package/scripts/traps/test-handler-nat-is-bigint.json +5 -0
  1024. package/scripts/traps/test-handler-takes-the-domain-value.json +5 -0
  1025. package/scripts/traps/vars-of-two-arguments-are-the-tuple.json +5 -0
  1026. package/scripts/typecheck-examples.js +122 -0
  1027. package/scripts/verify-faults.mjs +375 -0
  1028. package/scripts/verify-faults.test.mjs +402 -0
  1029. package/scripts/verify-package-artifacts.mjs +590 -0
  1030. package/scripts/verify-peer-floors.js +399 -0
  1031. package/scripts/verify-peer-floors.test.mjs +422 -0
  1032. package/scripts/verify-test-fails-rules.mjs +104 -0
  1033. package/scripts/verify-test-fails-rules.test.mjs +215 -0
  1034. package/scripts/verify-test-fails.js +371 -0
  1035. package/scripts/verify-traps.mjs +505 -0
  1036. package/scripts/verify-traps.test.mjs +521 -0
  1037. package/scripts/verify-typescript-floor.js +239 -0
  1038. package/skill-packages/ic-reactor/SKILL.md +103 -0
  1039. package/stack.json +8 -0
  1040. package/tsconfig.base.json +23 -0
  1041. package/tsconfig.json +8 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,3268 @@
1
+ # Changelog
2
+
3
+ Notable changes to the published `@ic-reactor/*` packages. ic-reactor 4
4
+ releases from `main` in one lane, `@ic-reactor/core`, `@ic-reactor/react`,
5
+ `@ic-reactor/vite-plugin` and `@ic-reactor/mcp` at one version: stable
6
+ versions under npm's `latest` dist-tag, prereleases under `beta`. The 3.x
7
+ line, on the `v3` branch since the 4.0 GA flip, releases in three lanes, each
8
+ with its own version:
9
+
10
+ - runtime: `@ic-reactor/core`, `@ic-reactor/react`, `@ic-reactor/candid`
11
+ - codegen: `@ic-reactor/codegen`, `@ic-reactor/cli`, `@ic-reactor/vite-plugin`
12
+ - parser: `@ic-reactor/parser`
13
+
14
+ The GitHub release of each tag also lists every pull request it contains.
15
+ What a 4.x release may change, from 4.0.0 on, is in
16
+ [Versioning and support](docs/src/content/docs/versioning.mdx).
17
+ Issue numbers below refer to https://github.com/B3Pay/ic-reactor/issues.
18
+
19
+ ## Unreleased
20
+
21
+ ### New: @ic-reactor/mcp
22
+
23
+ `@ic-reactor/mcp` is an MCP server that gives an AI agent any Internet
24
+ Computer canister's methods as tools. Each canister named with
25
+ `--canister <name>=<id>` gets one tool per method, with an input schema built
26
+ from the canister's certified `candid:service` metadata, and `list_methods`,
27
+ `describe_method` and `query` read any canister. An update is sent only to a
28
+ listed canister, signed with the key given as `--identity`, after the person
29
+ approves the exact call in the MCP client's form (MCP form elicitation): the
30
+ form shows the canister, method, caller, network, the arguments as Candid text
31
+ and the sha256 of the encoded bytes, and the canister's module hash is
32
+ confirmed before the form and again before the call is sent. A certified read
33
+ goes out as the anonymous principal, never with the key, and is read with the
34
+ interface of the module installed now. Input schemas, `tools/list`, method
35
+ names in the form and each call's wait are bounded, so a hostile interface
36
+ cannot exhaust the server's memory or crowd the form.
37
+
38
+ It releases with core, react and vite-plugin, at their version, and installs
39
+ one command: `npm install --global @ic-reactor/mcp`, then
40
+ `ic-reactor-mcp --help`. Its page,
41
+ [@ic-reactor/mcp](https://ic-reactor.b3pay.net/v4/packages/mcp/), has the
42
+ tools, the approval rules and the limits, and `packages/mcp/README.md` the
43
+ JSON forms of Candid values too.
44
+
45
+ ## core, react, vite-plugin 4.1.1
46
+
47
+ Prepared on 2026-11-06. 4.1.1 moves `@ic-reactor/vite-plugin` to
48
+ `@meantjs/cli` 0.5.1, Meant's patch with the corrected source nesting count.
49
+ `@ic-reactor/core` and `@ic-reactor/react` change only their version: the
50
+ schema peer stays `@meantjs/schema` 0.6.0. The release adds or removes no
51
+ `@ic-reactor/*` export (core 13, `@ic-reactor/core/testing` 2, react 4,
52
+ vite-plugin 2), and committed modules do not change.
53
+
54
+ The plugin pins the CLI exactly, so an app on 4.1.0 moves the CLI with it:
55
+
56
+ ```sh
57
+ npm install --save-exact @ic-reactor/core@4.1.1 @ic-reactor/react@4.1.1
58
+ npm install --save-dev --save-exact @ic-reactor/vite-plugin@4.1.1 @meantjs/cli@0.5.1
59
+ ```
60
+
61
+ Name only the packages the app has.
62
+
63
+ ### @ic-reactor/vite-plugin
64
+
65
+ #### Changed
66
+
67
+ - Peer `@meantjs/cli` at exactly `0.5.1`, where it was exactly `0.5.0`, and
68
+ the install hints name `@meantjs/cli@0.5.1`. Meant's 0.5.1 is 0.5.0 with
69
+ one compiler fix, a hardening: the source nesting limit counts every `opt`
70
+ and `vec` until its operand ends, so a `.did` nested past the limit is
71
+ refused before parsing with `resource_limit_exceeded`, where 0.5.0 could
72
+ run out of stack on it and fail that canister with `internal_error`. A type
73
+ nested exactly to the type depth limit whose innermost `record {}`,
74
+ `variant {}` or `func () -> ()` is empty now compiles, where 0.5.0 refused
75
+ it. For a `.did` both accept, the module and the envelope are the same, so
76
+ committed modules do not change. Move the CLI with the plugin, both exact.
77
+
78
+ ### Examples (not published)
79
+
80
+ - `node-agent-tool` no longer prints text from a ledger raw. Without
81
+ `--json`, a token's name or symbol and an error message are printed with
82
+ each control character and bidirectional formatting character spelled out
83
+ (`\u{1b}`), so a ledger can't move the cursor, rewrite earlier lines, add a
84
+ line that looks like the CLI's own or reverse an amount. With `--json`,
85
+ DEL, the C1 controls and those formatting characters are `\u` escapes, as
86
+ the C0 controls already were.
87
+
88
+ ### CI (not published)
89
+
90
+ - A release's run of the published-examples check waits until npm serves
91
+ each package's tarball, not only until the dist-tag and the integrity name
92
+ the release (`waitForPublished` in `scripts/test-examples-published.mjs`).
93
+ On 4.1.0's release run, npm answered 404 for core's tarball for about five
94
+ minutes after its dist-tag named 4.1.0, and every example's install failed
95
+ on it.
96
+
97
+ ## core, react, vite-plugin 4.1.0
98
+
99
+ Prepared on 2026-11-02. 4.1.0 moves ic-reactor 4 onto Meant, the new name of
100
+ the candid-core pair, and changes two things `@ic-reactor/core` does at run
101
+ time: what the client keeps after the caller changes, and what it believes an
102
+ `ic_env` cookie for. Each of those entries, under `@ic-reactor/core` below,
103
+ says what an app does about it. Both packages of the pair are pinned exactly,
104
+ where 4.0.0 required `@candid-core/schema` `0.4.0` and `@candid-core/cli`
105
+ `0.3.0`:
106
+
107
+ - `@ic-reactor/core`: peer `@meantjs/schema` at exactly `0.6.0`;
108
+ - `@ic-reactor/vite-plugin`: peer `@meantjs/cli` at exactly `0.5.0`, the
109
+ version its install hints name.
110
+
111
+ `@meantjs/schema` 0.6.0 is `@candid-core/schema` 0.5.0 under the new name,
112
+ and `@meantjs/cli` 0.5.0 is `@candid-core/cli` 0.4.0 under the new name, with
113
+ one command, `meant`, where it had `candid-core-cli`. The `@candid-core/*`
114
+ packages get no new version. Meant's release notes say what each of the four
115
+ versions changed; what reaches an app through ic-reactor is below.
116
+
117
+ The repin adds or removes no `@ic-reactor/*` export (core 13,
118
+ `@ic-reactor/core/testing` 2, react 4, vite-plugin 2), and changes no call,
119
+ key or error at run time.
120
+
121
+ What an app on 4.0.0 does for the repin:
122
+
123
+ 1. Bump the three `@ic-reactor/*` packages and move to the new pair, then
124
+ remove the old one. The new names are new packages to npm, so each goes in
125
+ the section of `package.json` its install command names:
126
+
127
+ ```sh
128
+ npm install --save-exact @ic-reactor/core@4.1.0 @ic-reactor/react@4.1.0 @meantjs/schema@0.6.0
129
+ npm install --save-dev --save-exact @ic-reactor/vite-plugin@4.1.0 @meantjs/cli@0.5.0
130
+ npm uninstall @candid-core/schema @candid-core/cli
131
+ ```
132
+
133
+ Name only the packages the app has.
134
+
135
+ 2. Replace `candid-core-cli` with `meant` in the scripts that call the
136
+ generator: `"gen": "meant gen icrc1.did -o src/canisters"`. A one-off
137
+ command is `npx @meantjs/cli@0.5.0 gen …`: where `@meantjs/cli` is not
138
+ installed, `npx meant` fetches and runs another package, the one named
139
+ `meant` on npm.
140
+
141
+ 3. Regenerate every committed module with `meant gen` (the Vite plugin does
142
+ it when it starts). Each module's first two lines change, to
143
+ `// Generated by Meant from a Meant Contract. Do not edit.` and
144
+ `import * as $ from "@meantjs/schema";`, and every envelope beside one
145
+ names `meant` 0.1.0 as its producer. Until then `meant gen --check`
146
+ reports them `drifted` and exits 1. A module also changes where a declared
147
+ `blob`, `record {}` or `variant {}` is used: each use is written as the
148
+ shape itself (`Uint8Array`, `Record<string, never>`, `never`), where it
149
+ was written as the first alias of that shape. `memo: Subaccount | null`
150
+ becomes `memo: Uint8Array | null`, the same type, so code that imports
151
+ the generated names compiles unchanged.
152
+
153
+ 4. Replace `@candid-core/schema` with `@meantjs/schema` in every import of
154
+ your own code; the subpaths keep their names (`@meantjs/schema/codec`,
155
+ `/validate`, `/contract`). Keep one copy of the schema. Each package
156
+ declares its own brands, so a `Principal` typed by `@candid-core/schema`
157
+ passed where core expects one of `@meantjs/schema` fails with `TS2322` or
158
+ `TS2345`.
159
+
160
+ 5. Where your own code calls `decode` or `decodeArgs` from
161
+ `@meantjs/schema/codec`, the value is now the schema's own type, not
162
+ `unknown`, so a cast to a type the schema does not describe fails to
163
+ compile: drop the cast, or decode with the schema that describes the
164
+ type.
165
+
166
+ ### @ic-reactor/core
167
+
168
+ #### Changed
169
+
170
+ - Peer `@meantjs/schema` at exactly `0.6.0`, where it was
171
+ `@candid-core/schema` at exactly `0.4.0`. The package imports the schema
172
+ runtime under that name, in its JavaScript and its types.
173
+ - The README and `llms.txt` name the new pair and `meant gen`.
174
+ - A sign-out, an expired session or a switch of account now removes the
175
+ previous principal's reads from the client's `QueryClient`, before any
176
+ listener, or any component, hears of the change. Their keys already kept
177
+ them from the new caller's reads. Now they also leave memory, where
178
+ devtools, a read by key prefix (`getQueriesData({ queryKey: ["ic-reactor"] })`)
179
+ or a persister that saves the cache could still reach them in a shared
180
+ browser. The anonymous reads and the app's own queries stay. Migration:
181
+ none for an app that builds its options from `useClient()`. Switching back
182
+ to an account reads its data again instead of showing a cached copy.
183
+ - On a canister's own `<id>.icp0.io` page, where the client believes the
184
+ `ic_env` cookie for canister ids without `allowEnvConfig`, it now takes
185
+ canister ids from it and nothing else. The `auth` factory is no longer
186
+ handed the cookie's `INTERNET_IDENTITY_PROVIDER` and
187
+ `PUBLIC_CANISTER_ID:internet_identity` there, so
188
+ `new AuthClient(network)` signs in with mainnet's Internet Identity.
189
+ Migration: an app on `icp0.io` that signs in with its own Internet Identity
190
+ names it in the factory,
191
+ `(network) => new AuthClient({ ...network, identityProvider: { authorizeUrl, canisterId } })`,
192
+ or writes `allowEnvConfig: true`.
193
+ - Off a local page, when a page receives more than one `ic_env` cookie and
194
+ they differ, the client believes none of them: a `{ name }` target is
195
+ refused with the reason `untrusted_host` (a call on it rejects
196
+ `canister_id_unresolved`), and neither their root key nor their Internet
197
+ Identity is taken. Copies that are the same are believed, and a local page
198
+ reads the first, as before. Migration: none for an app that bakes its
199
+ canister ids into the build, as a mainnet app should. An app that names
200
+ canisters on `icp0.io` can be refused after a deploy that changes the
201
+ cookie, on a page whose browser still holds the previous copy for another
202
+ directory, until a page in that directory is loaded again.
203
+
204
+ ### @ic-reactor/vite-plugin
205
+
206
+ #### Changed
207
+
208
+ - Peer `@meantjs/cli` at exactly `0.5.0`, where it was `@candid-core/cli` at
209
+ exactly `0.3.0`. The plugin resolves `@meantjs/cli` from the app and runs
210
+ its `meant` bin for `gen`, `project` and `check`. In an app that has only
211
+ the old CLI, each canister fails to generate with
212
+ `cannot find @meantjs/cli from <root>` and the install line, a generation
213
+ failure that follows `failOnError`. The plugin's messages name the command
214
+ `meant`, and its install hints `@meantjs/cli@0.5.0`.
215
+
216
+ ### Docs
217
+
218
+ - Every page, the README of each package and the skill name `@meantjs/schema`,
219
+ `@meantjs/cli` and `meant gen` where they named the candid-core pair and
220
+ `candid-core-cli gen`. The home page, its library card and the library
221
+ switcher call the library Meant.
222
+ - The auth guide's "Data" note says what a sign-out removes from the cache,
223
+ and how to keep a persister from restoring a signed-in principal's reads on
224
+ a shared page. The consumer guide (`packages/core/llms.txt`) says the same.
225
+ - The client guide says what the cookie is believed for on `<id>.icp0.io`,
226
+ and what copies of it that differ are believed for, and tells a mainnet app
227
+ to bake its canister ids into the build. The auth guide's identity-provider
228
+ rules, the Vite plugin guide and `packages/core/llms.txt` say the same.
229
+ - The reads guide says what to certify: a read whose value decides where
230
+ value goes or what a person approves (a recipient, a deposit address, an
231
+ allowance, the details someone confirms), through a canister made with
232
+ `certified: true`, and the rest plainly. It says what a certified read
233
+ costs (as long as an update call, and cycles from the canister), and how
234
+ two canister objects of one canister keep the two apart. The consumer
235
+ guide (`packages/core/llms.txt`), its "Do not" list and the `ic-reactor`
236
+ skill say the same.
237
+ - A "Deploy to a frontend canister" guide: the response headers to serve the
238
+ app with from DFINITY's asset canister (`.ic-assets.json5`) or from icp-cli's
239
+ static-site recipe (`_headers`), and what each one does. A
240
+ Content-Security-Policy that lets the page run its own files only and call
241
+ its own origin and `https://icp-api.io`, a `Cross-Origin-Opener-Policy` of
242
+ `same-origin-allow-popups` (`same-origin` cuts off the Internet Identity
243
+ window, and the sign-in never completes), the asset canister's `hardened`
244
+ policy, and `allow_raw_access: false`. It says what the HTTP gateway checks
245
+ on `<id>.icp0.io` and not on `<id>.raw.icp0.io`, why a raw origin must not
246
+ be an Internet Identity alternative origin, and how to let a copy button
247
+ write to the clipboard. The consumer guide (`packages/core/llms.txt`) gives
248
+ the same `.ic-assets.json5`.
249
+ - The three package READMEs, which npm shows, link the 4.x docs: Getting
250
+ started and the package's own page (the Vite plugin guide for the plugin).
251
+ They linked only the 3.x docs before. Core's README installs the generator
252
+ too and shows a first read, and its npm description says what the package
253
+ is ("IC Reactor Core Library" before).
254
+ - Getting started, the root README and the core and Vite plugin READMEs say
255
+ to install the CLI at the version they name: without a version, npm installs
256
+ the newest CLI, and when that one pairs with another schema, the install
257
+ stops with `ERESOLVE` in an app on `@ic-reactor/core`.
258
+ - The docs' overview page lists the three build guides and the "Deploy to a
259
+ frontend canister" guide, and says that the CHANGELOG's "Unreleased"
260
+ section lists what `main` has and npm does not. Getting started's "Next"
261
+ list and the root README link the deploy guide, and the examples index and
262
+ the root README say the Vite wallet deploys itself with security headers.
263
+ The root README names b3pay.net/docs/ic-reactor as the docs site.
264
+ - The agent tools guide no longer announces a library as "coming next": it
265
+ names Meant's `@meantjs/tools`, which generates tools of the same shape
266
+ from a profile, and links its guide.
267
+ - The repository's issue forms offer `@ic-reactor/vite-plugin` in place of the
268
+ 3.x parser for a feature request, suggest Node 22.12.0, the floor, in a bug
269
+ report, and link the private security report and the docs (not published).
270
+
271
+ ### Examples (not published)
272
+
273
+ - The five examples move with the repin: they depend on the new pair, their
274
+ `gen` scripts run `meant gen`, and their modules are regenerated. Canister
275
+ Studio imports the compiler library from `@meantjs/cli`. Installed from
276
+ npm (`pnpm test:examples:published`), they wait for this release.
277
+ - The Vite wallet reads its address book certified, through a second
278
+ canister object of the backend made with `certified: true`: a contact
279
+ decides where a payment goes. Its optimistic adds and removes find the
280
+ list by that read's key, and a test checks the list is read as a
281
+ replicated call.
282
+ - `vite-wallet` deploys the app itself too: `pnpm icp:deploy` builds it and
283
+ uploads it to an asset canister, `frontend`, served at
284
+ http://frontend.local.localhost:8000 with the guide's headers
285
+ (`public/.ic-assets.json5`, scenario 10).
286
+
287
+ ### CI (not published)
288
+
289
+ - The e2e suite deploys the guide's headers to both asset canisters, with the
290
+ recipe versions the guide names, and checks what they serve through the
291
+ local HTTP gateway (`e2e/src/frontend-headers.test.ts`, seven more cases):
292
+ every header, certified; the asset canister's raw host redirected with its
293
+ path and query; and the guide's copy of `.ic-assets.json5` and its
294
+ `_headers` the same as what was deployed.
295
+ - The docs also publish to b3pay.net, as `b3pay-module.json` describes: the
296
+ pages, the API reference and `packages/core/llms.txt`, served at
297
+ `/docs/ic-reactor/` once b3pay.net's registry reads this repository's
298
+ `b3pay-site` branch. The new `Docs on b3pay.net` workflow builds and checks
299
+ the module with b3pay.net's reusable workflow on pull requests that touch
300
+ the docs, and on `main` commits it to that branch. The `/v4/` site and its
301
+ deploy are unchanged. `pnpm -C docs run api` writes the API reference alone,
302
+ and its files are now named in lowercase, as their routes are; no address
303
+ changes.
304
+ - `Docs on b3pay.net` runs b3pay.net's reusable workflow from that site's
305
+ `main` (67911ef), and checks the module with the site's tools from `main`,
306
+ now that the folder items its sidebar lists are there.
307
+ - ic-reactor.b3pay.net keeps the 3.x and 2.x docs, and sends readers of the
308
+ 4.x docs and of Candid Core's pages to b3pay.net: each page under `/v4/`
309
+ and `/candid-core/` moves to the same page at
310
+ `https://b3pay.net/docs/ic-reactor/` or `https://b3pay.net/docs/meant/`,
311
+ with its query and fragment, and `/` goes to
312
+ `https://b3pay.net/docs/ic-reactor/`. The 4.x build's Markdown copies and
313
+ `llms.txt` stay under `/v4/`, and `/llms.txt` is still the 4.x guide.
314
+ `scripts/assemble-docs-site.mjs` takes `--v4-moved-to` and
315
+ `--candid-core-moved-to`, and the deploy stops before it publishes when
316
+ b3pay.net does not serve IC Reactor's docs, with the API reference, or
317
+ Meant's.
318
+ - The `v4` branch, where 4 was developed until the 4.0 GA flip, is retired.
319
+ CI, e2e, CodeQL, the dependency review and the two published-package checks
320
+ run on `main` only, and `verify-test-fails` takes its default base from
321
+ `main` alone.
322
+ - `stack.json` names the Meant release this branch is tested against,
323
+ `@meantjs/schema` 0.6.0 and `@meantjs/cli` 0.5.0, in one place.
324
+ `pnpm check:stack`, a new step of the Lint & Type Check job, fails when a
325
+ Meant pin in a tracked `package.json`, an `@meantjs/<name>@<version>` in a
326
+ `package.json` value or the docs, a docs peer-table row or one of the two
327
+ pins in code names another version or a range, outside the changelogs and
328
+ the manifest's `frozen` paths (`evals/`). A repin changes `stack.json` and
329
+ runs `pnpm sync:stack`, which rewrites them in place, the examples
330
+ included (CONTRIBUTING.md, "The Meant pins"). Dependabot no longer opens
331
+ `@meantjs/*` bumps, and the vite plugin's tests read the CLI version they
332
+ expect from `MEANT_CLI_VERSION`.
333
+
334
+ ## core, react, vite-plugin 4.0.0
335
+
336
+ Prepared on 2026-10-25. 4.0.0 is the first stable release of the ic-reactor
337
+ 4 line (milestone 1, #790). It is published from `main` after the 4.0 GA
338
+ flip, under npm's `latest` dist-tag, so a plain
339
+ `npm install @ic-reactor/core` installs 4. The 3.x line gets security fixes
340
+ only, until 90 days after this release.
341
+
342
+ Changes since 4.0.0-beta.8: the version only. No export, type, peer or
343
+ behaviour changed (core 13, `@ic-reactor/core/testing` 2, react 4,
344
+ vite-plugin 2).
345
+
346
+ It requires the candid-core pair that beta.5 moved to, both pinned exactly:
347
+
348
+ - `@ic-reactor/core`: peer `@candid-core/schema` at exactly `0.4.0`;
349
+ - `@ic-reactor/vite-plugin`: peer `@candid-core/cli` at exactly `0.3.0`.
350
+
351
+ ### Upgrading from a beta
352
+
353
+ An app on beta.8 bumps all three packages to `4.0.0`. A module generated for
354
+ beta.8 need not be regenerated.
355
+
356
+ An app on an earlier beta also reads each section below, from the one after
357
+ its version up to beta.8. What changes code on the way:
358
+
359
+ - **From beta.7** (4.0.0-beta.8). Nothing changes code. Restart the dev
360
+ server after a local network restarts: once the network the Vite plugin
361
+ detected stops, `{ name }` canisters reject `canister_id_unresolved` until
362
+ the server restarts (#881). On Windows, the plugin's messages write a path
363
+ with `/` (#905).
364
+ - **From beta.6** (4.0.0-beta.7). A re-send that `client.resendOf()` offered
365
+ and that did not run rejects `outcome_unknown` with `mayHaveExecuted: true`
366
+ (#960). **Breaking:** a Vitest run that loads the Vite plugin fails at
367
+ startup when a canister fails to generate and `failOnError` is omitted
368
+ (#967). Every network host needs its scheme, and `createClient` throws on
369
+ an app shell's page with `network: "env"` (#974). On localtunnel and VS
370
+ Code dev tunnel pages `allowEnvConfig: true` throws (#961, #962), and with
371
+ `network: "env"` a dev page opened over Tailscale, a dev tunnel or a LAN name
372
+ with no dot calls its own origin, never mainnet (#962). The beta.7 section
373
+ lists the smaller changes.
374
+ - **From beta.5** (4.0.0-beta.6). **Breaking:** Node 22.12 or newer (#896);
375
+ `createClient` refuses a value its types forbid with a `TypeError` (#895);
376
+ `useClient()` returns a new object for each caller, so list it in the
377
+ dependencies of what is built from it (#894). A first send refused with
378
+ `caller_changed` ran nothing (#893). An object of the `Client` type written
379
+ by hand needs `time`, `encode` and `canisterId`. The beta.6 section lists the
380
+ smaller changes (#900 to #903, #911, #912).
381
+ - **From beta.4 or earlier** (4.0.0-beta.5). The candid-core pair moves to
382
+ schema `0.4.0` and CLI `0.3.0`. npm does not resolve a partial upgrade of
383
+ the five packages cleanly, so move them in one command, naming only the
384
+ packages the app has, and keep one copy of the schema:
385
+
386
+ ```sh
387
+ npm install --save-exact @ic-reactor/core@4.0.0 @ic-reactor/react@4.0.0 @ic-reactor/vite-plugin@4.0.0 @candid-core/schema@0.4.0 @candid-core/cli@0.3.0
388
+ ```
389
+
390
+ Then regenerate every committed module with `candid-core-cli gen` (the Vite
391
+ plugin does it when it starts). `client.queryOptions` no longer compiles
392
+ for an update or a oneway method of a generated `Actor` (#851): a write
393
+ goes through `client.mutationOptions`, and an update that answers the same
394
+ however often it runs takes `{ update: "idempotent" }`. The Vite plugin
395
+ refuses a key that is no option and a value an option does not take, where
396
+ beta.4 read them without a word.
397
+
398
+ - **From beta.3 or earlier** (4.0.0-beta.4). Nothing breaks, except an object
399
+ of the `Client` type written by hand, such as a fake: the type gains
400
+ `resendOf`. New: `isReactorError(error, canister, method)` types a direct
401
+ call's `err` as that method's `Err` arm (#849), and `client.resendOf` sends
402
+ a write again exactly as it was sent after a failure that left its outcome
403
+ unknown, for a method the app says deduplicates (#850). The Vite plugin
404
+ writes a missing `didFile` from the live canister's certified interface
405
+ when the entry names a `canisterId`.
406
+ - **From beta.2 or earlier** (4.0.0-beta.3). **Breaking:** `network: "local"`
407
+ is icp-cli's `http://127.0.0.1:8000`; an app on a dfx replica names it as
408
+ `network: { host: "http://127.0.0.1:4943" }`. The `auth` factory is handed
409
+ the client's network: `auth: (network) => new AuthClient(network)`.
410
+ - **From beta.1** (4.0.0-beta.2). The candid-core pair moved to its first
411
+ stable releases. TanStack Query's `useSuspenseQuery` and
412
+ `useSuspenseQueries` take `client.queryOptions()` without a cast (#828);
413
+ the beta.2 section lists the few type-level uses that stop compiling.
414
+
415
+ The guide (`llms.txt`) and the skill now say they apply to 4.0.0, and the
416
+ five examples pin `^4.0.0`. With the GA flip (#836), the packages' READMEs
417
+ and the skill install 4 from `latest` (bare), not `@beta`, and the READMEs
418
+ drop the prerelease note.
419
+
420
+ For the whole change from 3.x, read the 4.0.0-beta.8 to 4.0.0-beta.1 sections
421
+ below, and
422
+ [Migrating from 3.x](https://ic-reactor.b3pay.net/v4/migrating-from-3/),
423
+ with the table of every removed 3.x name and what replaces it.
424
+
425
+ ### GA exit criteria
426
+
427
+ - The four examples (#788), the 4 docs and the "Removed in 4.0" table
428
+ (#789), and the runtime-trap suite with the real-replica e2e rewrite
429
+ (#787) are done.
430
+ - The GA eval, Addendum 4 of
431
+ [`evals/PREREGISTRATION.md`](./evals/PREREGISTRATION.md), ran on the
432
+ published 4.0.0-beta.1. GA ships on the result of Addendum 4, stated as
433
+ recorded: rule 1 was not met as written, because the leak audit flagged
434
+ react-wallet/`v4`#14's read of its own persisted tool output; all 80 runs
435
+ were safe, and the pooled bound held in both analyses (−0.0897 main,
436
+ −0.0876 intent-to-treat, against −0.10). The result and the owner's
437
+ decision are recorded there, under "Result of Addendum 4" and "Decision on
438
+ the result of Addendum 4" (#844).
439
+
440
+ ## core, react, vite-plugin 4.0.0-beta.8
441
+
442
+ Prepared on 2026-10-25. It goes out under npm's `beta` dist-tag, as
443
+ `@ic-reactor/core@beta`; `latest` stays 3.x until 4.0 GA. Changes since
444
+ 4.0.0-beta.7.
445
+
446
+ This release carries the two Vite plugin fixes that landed after beta.7, the
447
+ watch on a local network that stopped (#881) and the Windows paths (#905), so
448
+ that 4.0.0 ships no code a prerelease did not carry first. core and react
449
+ change no code: core's only changes are the JSDoc of `ReactorError.expiresAt`
450
+ (#924) and a reworded sentence of its guide, `llms.txt`.
451
+
452
+ No export is added or removed (core 13, `@ic-reactor/core/testing` 2, react 4,
453
+ vite-plugin 2), no public type changes, and the candid-core pair is unchanged:
454
+ core peers `@candid-core/schema` at exactly `0.4.0` and the Vite plugin
455
+ `@candid-core/cli` at exactly `0.3.0`, so a module generated for beta.7 need
456
+ not be regenerated.
457
+
458
+ What an app on beta.7 does:
459
+
460
+ 1. Bump all three `@ic-reactor/*` packages to `4.0.0-beta.8` in one
461
+ `npm install`. react 4.0.0-beta.8 peers core at exactly `4.0.0-beta.8`, so
462
+ they move together. The command keeps each package in the section of
463
+ `package.json` it is in:
464
+
465
+ ```sh
466
+ npm install --save-exact @ic-reactor/core@4.0.0-beta.8 @ic-reactor/react@4.0.0-beta.8 @ic-reactor/vite-plugin@4.0.0-beta.8
467
+ ```
468
+
469
+ Name only the packages the app has.
470
+
471
+ 2. Restart the dev server after a local network restarts (#881). When the
472
+ network the plugin detected stops, `vite dev` and `vite preview` warn
473
+ once, and page loads get the `ic_env` cookie without the detected canister
474
+ IDs and root key (with canisters configured, no cookie at all, and the
475
+ browser's copy is cleared), so `{ name }` canisters reject
476
+ `canister_id_unresolved` until the server restarts. `vite dev` reloads the
477
+ pages it has open; under `vite preview`, reload them yourself. The cookie
478
+ used to keep naming the old canister IDs, which a restarted network can
479
+ give to other canisters.
480
+ 3. On Windows, match the plugin's messages on a path written with `/`, such
481
+ as `did/ledger.did`, where they said `did\ledger.did` (#905).
482
+
483
+ The guide (`llms.txt`) and the skill now say they apply to 4.0.0-beta.8, and
484
+ the five examples pin `^4.0.0-beta.8`.
485
+
486
+ ### @ic-reactor/vite-plugin
487
+
488
+ #### Fixed
489
+
490
+ - The dev server and the preview server stop vouching for a local network that
491
+ stopped (#881). While serving, the plugin holds a connection to the detected
492
+ network. When the network stops, it warns once, page loads get the
493
+ `ic_env` cookie without the detected canister IDs and root key (with
494
+ canisters configured, no cookie at all) until the server restarts, and
495
+ `vite dev` reloads the pages it has open. Detection had
496
+ stopped running once it was complete, so after `icp network stop`, `start`
497
+ and `icp deploy` the cookie kept naming the old canister IDs under the same
498
+ root key, and a restarted icp-cli network can give those IDs to other
499
+ canisters: a page could call a different canister than the one it named.
500
+ `{ name }` canisters now reject `canister_id_unresolved` instead. Nothing
501
+ detects the network again; restarting the dev server does.
502
+ - A page load with no cookie to send, while no network is detected, clears an
503
+ `ic_env` cookie the browser kept from an earlier dev server, which
504
+ `network: "env"` believed (#881).
505
+ - On Windows, the plugin's messages name a path relative to the project with
506
+ `/`, as the config writes it (`did/ledger.did`), where they named
507
+ `did\ledger.did` (#905). The plugin's whole test suite now runs on Windows
508
+ in CI, where only the tests of how it starts `icp` ran.
509
+
510
+ ### Docs
511
+
512
+ - `SECURITY.md` describes ic-reactor 4 (#909). Its threat model covers core
513
+ and react (cache scoping, root keys, the `ic_env` cookie's canister IDs, a
514
+ write misreported as not executed) and the Vite plugin (the
515
+ `candid-core-cli` child process, live interfaces and `verify: "live"`, the
516
+ cookie and the `/api` proxy, generated files). It says that
517
+ `@ic-reactor/core/testing` is not for production and sends Candid decoding
518
+ bugs to candid-core. The 3.x threat model stays on the `v3` branch.
519
+
520
+ - A Versioning and support page (#897). It says what a 4.x patch, minor and
521
+ major may change. It names the unions that may grow in a minor
522
+ (`ReactorErrorKind`, `ReactorError.code`, `AuthState.status`) and the
523
+ interfaces to consume rather than implement (`Client`, `Canister`, `Resend`,
524
+ `ReactorError`; `AuthLike` gains only optional members). It says how
525
+ candid-core releases reach an app while the pins stay exact, what a
526
+ dehydrated cache from a newer version does, and the oldest Node,
527
+ TypeScript, React, TanStack Query, Vite and `@icp-sdk/core` supported.
528
+
529
+ - The errors page and `ReactorError.expiresAt`'s JSDoc cite the IC interface
530
+ specification for what the expiry proves (#924): the IC neither accepts a
531
+ call nor moves it from `received` to `processing` once its `ingress_expiry`
532
+ is past, so no send of the write can start after it. They also say why that
533
+ settles only a method that commits without awaiting another canister: the
534
+ expiry does not stop a send that started before it.
535
+
536
+ ### Examples (not published)
537
+
538
+ - `canister-studio` requires zod `^4.6.6`, past the high advisory
539
+ GHSA-mrhx-c7xc-m3w5, and resolves 4.6.7 (#1015).
540
+
541
+ ### CI (not published)
542
+
543
+ - `release.yml` publishes what it tested, and only after CI passed on the
544
+ tagged commit (#909):
545
+ - Preflight packs core, react and vite-plugin from the build it tested and
546
+ uploads the tarballs. The publish job publishes those three back to back,
547
+ with no install, build or test between them, so a failing test can no
548
+ longer stop a release after core is out and before react is.
549
+ - A new job waits for the push runs of `ci.yml` and `e2e.yml` on the
550
+ tagged commit and fails unless both succeeded, so lint, the export
551
+ budget, the trap and fault checks, the snippets, the peer floors, the
552
+ examples and the e2e suite gate the publish too. The approval is asked
553
+ for only after it passes.
554
+ - The publish job holds `id-token: write` and nothing else. It has no
555
+ checkout, install, cache or build, and installs npm 11.21.0 exactly,
556
+ where it installed `npm@^11.5.1`. A re-run skips a package npm already
557
+ has from the same tarball, so a release that stopped half-way can be
558
+ finished.
559
+ - The GitHub release is made by a job of its own, the only one with
560
+ `contents: write`, once all three packages are out.
561
+ - The Audit check passes again (#1015). The root `pnpm.overrides` lift undici
562
+ past the high advisory GHSA-xjj6-f33q-929c in the docs site's tree (7.30.1
563
+ under astro, 8.12.3 under linkinator) and happy-dom past GHSA-834v-8h66-3h79;
564
+ happy-dom, an optional peer of Vitest that no suite uses, is no longer
565
+ installed.
566
+ - `release.yml`'s publish job names each tarball `./tarballs/<file>.tgz`.
567
+ npm read `tarballs/<file>.tgz` as a GitHub repository, so the first run for
568
+ the `v4.0.0-beta.8` tag failed on `git ls-remote` with nothing published.
569
+ `scripts/release-workflow.test.mjs` checks every path the job publishes.
570
+
571
+ ## core, react, vite-plugin 4.0.0-beta.7
572
+
573
+ Prepared on 2026-10-22. It goes out under npm's `beta` dist-tag, as
574
+ `@ic-reactor/core@beta`; `latest` stays 3.x until 4.0 GA. Changes since
575
+ 4.0.0-beta.6.
576
+
577
+ This release carries the fixes of the audit of beta.6 (#960 to #980), the
578
+ `client.time` fix under jsdom (#949), the redirect sign-in fix (#913) and the
579
+ Internet Identity guide (#914), so that 4.0.0 ships no code a prerelease did
580
+ not carry first. One change is marked breaking (the Vite plugin under Vitest),
581
+ and several more change what an app sees; the steps below list them.
582
+
583
+ No export is added or removed (core 13, `@ic-reactor/core/testing` 2, react 4,
584
+ vite-plugin 2), no public type changes, and the candid-core pair is unchanged:
585
+ core peers `@candid-core/schema` at exactly `0.4.0` and the Vite plugin
586
+ `@candid-core/cli` at exactly `0.3.0`, so a module generated for beta.6 need
587
+ not be regenerated.
588
+
589
+ What an app on beta.6 does:
590
+
591
+ 1. Bump all three `@ic-reactor/*` packages to `4.0.0-beta.7` in one
592
+ `npm install`. react 4.0.0-beta.7 peers core at exactly `4.0.0-beta.7`, so
593
+ they move together. The command keeps each package in the section of
594
+ `package.json` it is in:
595
+
596
+ ```sh
597
+ npm install --save-exact @ic-reactor/core@4.0.0-beta.7 @ic-reactor/react@4.0.0-beta.7 @ic-reactor/vite-plugin@4.0.0-beta.7
598
+ ```
599
+
600
+ Name only the packages the app has.
601
+
602
+ 2. Treat a re-send that `client.resendOf()` offered and that did not run as
603
+ any other `outcome_unknown` (#960). Refused before it went out, or turned
604
+ away with an HTTP 4xx or reject code 1 to 3, it used to reject
605
+ `cancelled`, `invalid_args`, `not_delivered` or `rejected`; it now rejects
606
+ `outcome_unknown` with `mayHaveExecuted: true`, since the write it re-sends
607
+ may have run. `client.resendOf()` offers the write again, and a mutation
608
+ invalidates its reads. Read `kind` and `mayHaveExecuted` first, and read
609
+ the state back. The error keeps the re-send's `code`, which says only why
610
+ the re-send did not go out: unlike a first send's (#893), its
611
+ `caller_changed` does not mean nothing ran, so do not call `mutate()`
612
+ again on it.
613
+ 3. Check a Vitest run that loads the Vite plugin (breaking, #967). A canister
614
+ that fails to generate now fails the run at startup when `failOnError` is
615
+ omitted. Fix the failure, set `IC_REACTOR_VERIFY=0` to skip the live check
616
+ (`verify: "live"`) in an offline run, or set `failOnError: false`.
617
+ 4. Write the scheme in every network host (#974):
618
+ `{ host: "http://localhost:4943" }`, `ICP_HOST=http://localhost:8000`. On
619
+ an app shell's page, such as `capacitor://localhost`, pass
620
+ `network: { host }` in place of `"env"`. Such a client used to be built
621
+ and then fail on every call; `createClient` now throws.
622
+ 5. Check where pages run:
623
+ - on a localtunnel page (`*.loca.lt`) or a VS Code dev tunnel page
624
+ (`*.devtunnels.ms`), `allowEnvConfig: true` now throws, whatever the
625
+ network; name canisters with `{ id }` there (#961, #962);
626
+ - with `network: "env"`, a dev page opened over Tailscale, by a dev tunnel
627
+ or by a LAN name with no dot now calls its own origin, never mainnet; a
628
+ production app served from such a name needs `network: "ic"` or a
629
+ `{ host, rootKey }` network (#962);
630
+ - a page or host whose name ends with a dot now counts as the name without
631
+ it, except that `<id>.icp0.io.` does not believe the cookie's canister
632
+ ids by default (#975).
633
+ 6. Smaller changes, each with its migration in the entries below: an `auth`
634
+ factory returns the auth itself, synchronously, and an async one's
635
+ `TypeError` now comes from `signIn()` (#972); a signer that declines a
636
+ first send, such as a user who closes a passkey prompt, now rejects
637
+ `not_delivered` with `mayHaveExecuted: false`, not `outcome_unknown`,
638
+ while a declined re-send stays `outcome_unknown` (#963); and
639
+ `client.time(canister)` now stops waiting at the canister object's
640
+ `timeoutMs` (#973).
641
+
642
+ The guide (`llms.txt`) and the skill now say they apply to 4.0.0-beta.7, and
643
+ the five examples pin `^4.0.0-beta.7`.
644
+
645
+ ### @ic-reactor/core
646
+
647
+ #### Changed
648
+
649
+ - A re-send of a write `client.resendOf()` offered that did not run now
650
+ rejects `outcome_unknown` with `mayHaveExecuted: true`, and
651
+ `client.resendOf()` offers the write it re-sends again (#960). That covers a
652
+ re-send refused before it went out (code `caller_changed`, `target_changed`,
653
+ `arg_changed`, `client_disposed`, or any other refusal before sending,
654
+ including the one of a mutation built from a pinned `useClient()` view), and
655
+ one turned away when sent (an HTTP 4xx such as 429, or reject code 1 to 3). Each proves only that the re-send
656
+ did not run, while the earlier attempt may have: the error used to say
657
+ `cancelled`, `invalid_args`, `not_delivered` or `rejected` with `mayHaveExecuted: false`,
658
+ so `resendOf` offered nothing after it, a mutation did not invalidate its
659
+ reads, and an app could tell the user the transfer had not happened. The
660
+ error keeps the re-send's `code`, `httpStatus` and `rejectCode`, has the
661
+ re-send's own error as its `cause`, and carries the latest `expiresAt` of
662
+ the write's sends. A timeout before the re-send went out already worked
663
+ this way; it now also carries `expiresAt`. A re-send the canister answers (`canister_err`, such as
664
+ `Duplicate`) and the same failures of a first send are unchanged.
665
+ Migration: an app that branched on `kind` for a refused re-send reads its
666
+ `code`, `httpStatus` or `rejectCode` instead, and treats it as any other `outcome_unknown`: read the
667
+ state back. That `code` says only why the re-send did not go out: unlike a
668
+ first send's (#893), its `caller_changed` does not mean nothing ran.
669
+ - `createClient` now also throws a `TypeError` for `allowEnvConfig: true` on
670
+ a localtunnel page (`*.loca.lt`) or a VS Code dev tunnel page
671
+ (`*.devtunnels.ms`) (#961). Neither parent is a public suffix, so any other
672
+ tunnel can set the `ic_env` cookie these pages read, as on the `ic0.app`,
673
+ Codespaces and Gitpod pages #900 refuses. The client used to believe that
674
+ cookie's canister ids and Internet Identity URL there, and with
675
+ `network: "env"` on `*.loca.lt` its root key too. Migration: as for #900,
676
+ name the canisters with `{ id }` and a replica that is not mainnet with
677
+ `{ host, rootKey }`.
678
+ - A signed-in render of `useClient()` converts the caller's principal to text
679
+ once, where it did three times (#977). The client now hands back a view it
680
+ already made without reading who is signed in, and drops the views it no
681
+ longer needs only when it makes a new one. So the view of a user who signed
682
+ out, or whose session expired, is kept until the next view is made, and
683
+ that user signing in again before then gets the same object back. Measured
684
+ in the React tests with React's production build; its development build
685
+ reads `useSyncExternalStore`'s snapshot twice per render, so there the count
686
+ goes from 4 to 2.
687
+
688
+ #### Fixed
689
+
690
+ - A write whose signer fails, such as a user who declines a passkey or
691
+ hardware-wallet prompt, now rejects `not_delivered` with
692
+ `mayHaveExecuted: false` (#963). Nothing was sent, but the agent signs
693
+ outside its own error handling, so the failure used to read as
694
+ `outcome_unknown` with `mayHaveExecuted: true`: `client.resendOf()`
695
+ offered it, a mutation invalidated its reads, and an app could tell the
696
+ user the payment may have gone through. The client now hands the agent an
697
+ identity that marks its signing failures, and the error's `cause` is the
698
+ signer's own error. A signer that fails on a `read_state` poll of an update
699
+ the IC already took still rejects `outcome_unknown`, and says the call was
700
+ sent. A read whose signer declines the `read_state` for the subnet's keys
701
+ rejects `not_delivered` and is no longer retried, which asked the signer
702
+ again on every attempt. A re-send that `client.resendOf()` offered and the
703
+ signer declines stays `outcome_unknown` and offered, as on v4: the write
704
+ it re-sends may have run. An `AbortError` from the signer is still
705
+ `cancelled`, as every abort is.
706
+ - `network: "env"` no longer falls back to mainnet on a dev page opened over
707
+ Tailscale (a 100.64/10 address or a `*.ts.net` name), by a VS Code dev
708
+ tunnel (`*.devtunnels.ms`) or by a LAN name with no dot
709
+ (`http://devbox:5173`) (#962). An app that names its canisters with
710
+ `{ id }` from its build-time env sent its local ids to mainnet there. Such
711
+ a page now routes through its own origin, as #902 made LAN addresses and
712
+ the other tunnels do: the cookie is not believed and no root key is taken
713
+ or fetched, so certified calls fail verification instead of reaching
714
+ mainnet. A name with no dot counts only on an `http:` or `https:` page, so
715
+ a browser extension's or a desktop app's page falls back as before.
716
+ `allowEnvConfig: true` is refused with a `TypeError` on a dev tunnel page,
717
+ as on Codespaces: `devtunnels.ms` is not a public suffix, so any other
718
+ tunnel could set the cookie whose root key the page would take. Migration:
719
+ a production app served from such a name now calls its own origin; give it
720
+ `network: "ic"` or a `{ host, rootKey }` network.
721
+ - A page or agent host whose name ends with a trailing dot
722
+ (`https://victim.ic0.app./`) now counts as the name without it (#975). The
723
+ URL parser keeps the dot, so such a page got past the rule that refuses
724
+ `allowEnvConfig: true` under `ic0.app`, `github.dev` and `gitpod.io`, and
725
+ the client believed the cookie's canister ids and Internet Identity URL
726
+ there. Such a page is now refused the opt-in and routes as the name without
727
+ the dot does, and a mainnet host written that way still never takes the
728
+ cookie's root key. A `localhost.` page or host is local, as `localhost` is:
729
+ the cookie is believed there and the replica's root key fetched. One rule
730
+ stays closed: unlike `<id>.icp0.io`, `<id>.icp0.io.` does not believe the
731
+ cookie's canister ids by default.
732
+ - An auth factory that returns something other than an auth, such as a
733
+ Promise, is called once (#972). An `async` factory returns a Promise, and so
734
+ does `auth: () => AuthClient.create()` with `@icp-sdk/auth` 5 or earlier.
735
+ Its result was checked after the step that records a factory's
736
+ failure, so the client built a new auth on every `caller()`,
737
+ `authState()`, `subscribe()` and render, threw a `TypeError` from each,
738
+ and leaked every auth it built. It now fails as a factory that throws
739
+ does: the client calls anonymously, so public reads still work, logs the
740
+ error once in development, and `signIn()` and `signOut()` reject with the
741
+ `TypeError`, which names what the result lacks. Migration: catch the
742
+ `TypeError` from `signIn()`, not from the first read, and return the auth
743
+ itself, synchronously, with `AuthClient` from `@icp-sdk/auth` 10:
744
+ `auth: (network) => new AuthClient(network)`.
745
+ - `client.time(canister)` now stops waiting once the canister object's
746
+ `timeoutMs` passes, as a call through that object does (#973). It awaited
747
+ its `read_state` with no bound, so a node that never answered left it
748
+ pending forever, and with it any flow that awaits `client.time(ledger)`
749
+ before a transfer. It now rejects `cancelled`, code `"timeout"`, with
750
+ `mayHaveExecuted: false`, so such a flow fails before it sends anything,
751
+ and a `read_state` not yet sent when the bound passes is never sent.
752
+ Without `timeoutMs` it waits as before, as long as the `fetch` in use does.
753
+ - `createClient` throws a `TypeError` for a network host the agent cannot
754
+ call over `http:` or `https:` (#974). It accepted any host the URL parser
755
+ read: `{ host: "localhost:4943" }` and `ICP_HOST=localhost:8000` on a server
756
+ read as the scheme `localhost:`, and `ftp:`, `javascript:` and `data:` URLs
757
+ passed too, so the client was built and every call then failed. The message
758
+ names the scheme it read. Under `network: "env"` it also says that the host
759
+ came from the page's origin or from `ICP_HOST` or `IC_HOST`, since an app
760
+ shell's page, such as `capacitor://localhost`, now throws there too.
761
+ Migration: write the scheme, `{ host: "http://localhost:4943" }` or
762
+ `ICP_HOST=http://localhost:8000`, and on an app shell's page pass
763
+ `network: { host }` in place of `"env"`.
764
+ - A page with an element whose id is `Deno`, or a frame named `Deno`, no
765
+ longer counts as a server (#976). The client knew Deno by a `Deno` global,
766
+ which in a browser such an element is (`window.Deno`), and user content
767
+ that a sanitizer such as DOMPurify keeps can carry the id. On such a page
768
+ the auth was never built, `signIn()` rejected as on a server, the `ic_env`
769
+ cookie was not read, so `{ name }` targets were refused as `no_browser`,
770
+ and reads were not retried. Deno is now known by its `Deno.version.deno`,
771
+ and a page where reading it throws (a cross-origin frame named `Deno`) is
772
+ a page too.
773
+ - `client.time(canister)` resolves under a test's jsdom environment, where the
774
+ fake replica refused a signed-in client's signature (#949). There
775
+ `TextEncoder` hands out Node's bytes, which are not the global (jsdom's)
776
+ `Uint8Array`, and the agent's CBOR encoder sent the `time` label built from
777
+ them as a map, so the request the replica received was not the one the
778
+ agent signed. The label is now copied into the global `Uint8Array`. In a
779
+ browser, `TextEncoder` and the agent share one realm, so an app there is
780
+ not expected to have been affected.
781
+ - Two `client.signIn()` calls with the same options in one synchronous run are
782
+ now one sign-in: the second returns the first one's promise, and the auth is
783
+ asked once (#913). `StrictMode` makes such a pair in development when an
784
+ effect calls `signIn()`, as a sign-in route does for Internet Identity's
785
+ `transport: "redirect"`. There, the two calls put two requests in one
786
+ redirect, and both sign-ins failed on the load Internet Identity returned
787
+ to. Options are the same when they are the same value, or objects with the
788
+ same keys and values (`Object.is`, one level deep). A call in a later run,
789
+ or with other options, signs in again, and a call after `dispose()`
790
+ rejects, as before. `useAuth().signIn` goes through the client, so it joins
791
+ the same way. The auth guide has a new "Redirect
792
+ sign-in" section: the route for Vite, Next.js and TanStack Start, entered
793
+ with a full page load, the `/.well-known/ii-auth-callbacks` file, and
794
+ `memoize`.
795
+
796
+ ### @ic-reactor/react
797
+
798
+ #### Fixed
799
+
800
+ - A `ReactorProvider` handed the view an outer provider's `useClient()`
801
+ returned (`client={() => view}`, as the README and the SSR guide describe)
802
+ no longer removes the outer client from the registry that disposes the
803
+ clients of renders React throws away (#965). Both providers render in the
804
+ same pass, so in development every first mount logged the "lazily shared"
805
+ warning for correct code, and when a Suspense boundary above the outer
806
+ provider threw that render away, the outer client and the auth listening
807
+ to the window were never disposed.
808
+
809
+ ### @ic-reactor/vite-plugin
810
+
811
+ #### Changed
812
+
813
+ - **Breaking for a Vitest run that loads the plugin and whose generation
814
+ fails:** under Vitest (mode `test`), a canister that fails to generate now
815
+ fails the run at startup when `failOnError` is omitted, as under
816
+ `vite build` (#967). Vitest runs the plugin as a dev server, so the failure
817
+ used to be only logged, and the tests ran against the modules left over
818
+ from the last good run: a `.did` that no longer parsed still reported its
819
+ tests passing. Every failure counts: a `.did` that does not parse, a fetch
820
+ that fails, a missing `@candid-core/cli`, refused canister options, an
821
+ `onGenerated` that throws, and a live check (`verify: "live"`,
822
+ `IC_REACTOR_VERIFY`) that fails or cannot reach the network. Migration: fix
823
+ the failure; set `IC_REACTOR_VERIFY=0` for an offline test run with the
824
+ live check on; or set `failOnError: false` to log the failure and run the
825
+ tests anyway. A Vitest config that does not load the plugin (a
826
+ `vitest.config.ts` without it, as in the examples) is not affected.
827
+ - The package no longer publishes its TypeScript source (#978). It shipped
828
+ `src` beside `dist` with no source map or declaration map pointing at it,
829
+ and nothing in `dist` refers to it. Measured with `npm pack`: 17 files and
830
+ 487,020 B unpacked before, 7 files and 268,245 B now.
831
+
832
+ #### Fixed
833
+
834
+ - `vite build --watch` generates a canister again when its module or envelope
835
+ was deleted, though its `.did` is unchanged (#980). A rebuild skipped a
836
+ canister whose `.did` text had not changed without looking for the files,
837
+ so after the module was deleted every rebuild failed until the `.did`
838
+ changed.
839
+ - On Linux and macOS, an `icp` that does not answer within 10 seconds is
840
+ killed together with the native binary it started (#980). npm's `icp` is a
841
+ Node script that starts that binary, and the timeout killed only the
842
+ script, so while detection was incomplete each page load could leave one
843
+ more `icp` running. `icp` now runs in a session and process group of its
844
+ own there, and the timeout kills the group. So the terminal's Ctrl-C no
845
+ longer reaches an `icp` that is running when the dev server is stopped: it
846
+ ends on its own, or, if it hangs, keeps running, as every timed-out one did
847
+ before.
848
+ - `icp` is no longer asked for the ID of a canister whose `canisterId` is
849
+ configured, which wins over what `icp` says anyway: one `icp` process fewer
850
+ per page load while detection is incomplete (#980). `internet_identity` is
851
+ still asked for, since what `icp` reports for it decides whether the cookie
852
+ also names icp-cli's built-in Internet Identity.
853
+
854
+ ### Docs
855
+
856
+ - The auth guide's "Identity attributes" shows the flow DFINITY's
857
+ `identity-attributes` library (Motoko and Rust) expects, end to end: the
858
+ attributes reach the canister as the finish call's `sender_info`, not as
859
+ arguments. The nonce goes through a second client built with
860
+ `new AnonymousIdentity()`, `signIn()` and `requestAttributes()` start
861
+ together, and the finish call goes through a client built with an
862
+ `AttributesIdentity`, on the same network and with the signer the factory is
863
+ handed. It says to trust `verified_email`, what a refusal and an untrusted
864
+ signer reject as, and what the canister must check. The consumer guide
865
+ (`packages/core/llms.txt`) states the same, and the migration guide's recipe
866
+ points at it (#914).
867
+ - The auth guide has a table of what Internet Identity offers beyond a plain
868
+ sign-in, 2.0's features among them, and the `AuthClient` option for each:
869
+ one-click OpenID and organization SSO, session length,
870
+ `disableBrowserActivity`, `namespace`, `derivationOrigin`, a sign-in shared
871
+ by sibling subdomains, and redirect sign-in (#914).
872
+ - The auth guide installs `@icp-sdk/auth@^10`, since npm's newest is 11, and
873
+ says that on dfx the client hands the factory icp-cli's built-in Internet
874
+ Identity, which dfx does not serve (#914).
875
+ - `signIn()` rejects with the auth's own errors, never a `ReactorError`, as
876
+ the auth guide's "useAuth" and the consumer guide now say; the consumer
877
+ guide's example catches it (#914).
878
+
879
+ ### Examples (not published)
880
+
881
+ - `canister-studio`'s Safety lab now says what a re-send refused as another
882
+ sender rejects with, and checks it: `outcome_unknown` / `caller_changed`,
883
+ with `mayHaveExecuted: true`. The texts of the error kinds now say that a
884
+ held re-send that did not run is `outcome_unknown`, no longer
885
+ `invalid_args` or `cancelled` (#960).
886
+
887
+ ### CI (not published)
888
+
889
+ - `pnpm verify:test-fails` compares with where the branch left v4: the
890
+ merge-base with `v4` or `origin/v4`, whichever HEAD left last, instead of
891
+ `main`, the 3.x line, whose sources cannot load v4's tests (#968). With
892
+ neither ref, it asks for `--base`. A test with no result on the base,
893
+ because its file did not load there, a hook failed there or the test was
894
+ skipped there, is listed on its own as no evidence, with the file's error,
895
+ and makes the run exit 2; it used to count as passing either way, and
896
+ `main` made every v4 test look like a guard.
897
+ - `pnpm check:ai-context` holds every skill under `skill-packages/` to the
898
+ rules for a skill an app installs, with or without a plugin marketplace:
899
+ its `SKILL.md` lists `@ic-reactor/react`'s version, and any other package's
900
+ it lists, at the current version, and it names no path that exists only in
901
+ this repository. They ran only on the skills of
902
+ `.claude-plugin/marketplace.json`, which v4 does not have, so
903
+ `skill-packages/ic-reactor/SKILL.md` could drop its version lines or point
904
+ at `packages/core/src/` and pass. The summary line now counts them as
905
+ "consumer skill(s)". `skill-packages/ic-reactor/SKILL.md` passes both as it
906
+ stands (#970).
907
+ - `pnpm verify:traps` finds a `@ts-ignore` or `@ts-expect-error` wherever
908
+ TypeScript reads one, found with TypeScript's own parser (#971): after code
909
+ on its line (`ok() // @ts-ignore`), on a block comment's last line, and
910
+ spelled `@ts-ignored`, as well as a `@ts-nocheck` TypeScript obeys after a
911
+ block comment on the first line. It found only a directive that started its
912
+ line, so a trailing `// @ts-ignore` hid an error that no trap proves, and
913
+ the gate still passed. A tag's `@ts-expect-error` must still start the line
914
+ under it.
915
+ - The scripts keep one copy of each helper they shared by hand, in
916
+ `scripts/lib/`: the fence rule for Markdown code blocks (`check-snippets`
917
+ and both readers of `check-ai-context`), the list of a package's public
918
+ subpaths (`check-exports`, `verify-package-artifacts` and
919
+ `verify-typescript-floor`), and `run`, `readJson` and `writeJson` (the two
920
+ floor scripts). `check-ai-context` now reads a docs page's heading ids by
921
+ CommonMark's rule, as `check-snippets` does: a fence line with an info
922
+ string, such as ` ```ts `, no longer ends a block, so a heading in
923
+ the code after it is not taken for one of the page's.
924
+ `verify-package-artifacts` and `verify-typescript-floor` now read
925
+ `exports` as `check-exports` does: a subpath mapped to `null` is left out,
926
+ and a string or a set of conditions is the root entry. No package of the
927
+ repository uses either form, so no run changes (#979).
928
+ - `pnpm check:snippets` checks a code block that is never closed, which runs
929
+ to the end of the file as CommonMark has it, instead of dropping it and
930
+ passing (#980).
931
+ - `pnpm verify:peer-floors` fails when one of its pnpm filters matches no
932
+ package (`--fail-if-no-match`), where pnpm exited 0 having run nothing, and
933
+ its TypeScript floor compiles the Vite plugin's declarations as well as
934
+ core's and react's, in a program of their own so that the Node types Vite's
935
+ declarations bring in do not reach core's and react's (#980).
936
+ - `pnpm release <version>` stops at the release commit and prints the rest
937
+ of the release: the `CHANGELOG.md` section, the `release/v<version>` branch
938
+ and its pull request into `v4`, and the tag on that pull request's merge
939
+ commit, which `release.yml` publishes from (#969). It no longer tags the
940
+ commit, and it refuses `--dry-run` and `--publish` before changing
941
+ anything: `--dry-run` made a real commit and tag, and `--publish` published
942
+ from the machine it ran on, outside `release.yml`'s preflight, approval and
943
+ provenance. Its step for a root `llms.txt`, which `v4` does not have and
944
+ which failed on every run, is gone.
945
+ - react's StrictMode tests check the disposals of the client in use, where
946
+ they counted those of every client the factory built (#966). The registry
947
+ disposes the client React's development render throws away whenever a
948
+ collection runs, so a collection during the test failed it, as in
949
+ 4.0.0-beta.6's release run. The double-mount test now runs real
950
+ collections where `gc()` is exposed.
951
+ - A core test fails if an update the IC accepted is no longer recorded as
952
+ accepted (#964). Without that record, a poll answered HTTP 429 reads as the
953
+ IC turning the update away, and the update is sent again.
954
+ - The size limits rose by the measured amount of each fix that grew the
955
+ bundles, each in a commit that states the measurement: core's from
956
+ 18.17 kB to 19.1 kB, and the size app's two checks from 93.1 kB to
957
+ 94.07 kB and from 94.39 kB to 95.38 kB.
958
+
959
+ ## core, react, vite-plugin 4.0.0-beta.6
960
+
961
+ Prepared on 2026-10-21. It goes out under npm's `beta` dist-tag, as
962
+ `@ic-reactor/core@beta`; `latest` stays 3.x until 4.0 GA. Changes since
963
+ 4.0.0-beta.5.
964
+
965
+ This release carries the fixes of the pre-GA audit of beta.5 (#893 to #912)
966
+ and the ten fixes and features filed just before it (#873 to #882), so that
967
+ 4.0.0 ships no code a prerelease did not carry first. Three changes are marked
968
+ breaking (the Node floor, `createClient`'s option checks and `useClient()`),
969
+ and several more change what an app sees; the steps below list them.
970
+
971
+ No export is added or removed (core 13, `@ic-reactor/core/testing` 2, react 4,
972
+ vite-plugin 2), and the candid-core pair is unchanged: core peers
973
+ `@candid-core/schema` at exactly `0.4.0` and the Vite plugin
974
+ `@candid-core/cli` at exactly `0.3.0`, so a module generated for beta.5 need
975
+ not be regenerated. What is new comes as members and options, as the entries
976
+ below say:
977
+
978
+ - on the client: `client.time(canister)`, `client.canisterId(target)` and
979
+ `client.encode(canister, method, vars)`;
980
+ - on what the client gives back: `ReactorError.expiresAt`, `Resend.bytes` and
981
+ `AuthState.expiresAt`;
982
+ - options: `timeoutMs` on a canister and on a mutation, `maxElements` on
983
+ `createClient` and `createTestClient`, and the Vite plugin's `onGenerated`;
984
+ - in `@ic-reactor/core/testing`: `dropNextReply({ method, canister, times })`.
985
+
986
+ Code that writes its own object of the `Client` type, such as a hand-made
987
+ fake, no longer compiles until it has `time`, `encode` and `canisterId` (a
988
+ client from `createClient` or `createTestClient` has them).
989
+
990
+ What an app on beta.5 does:
991
+
992
+ 1. Run Node 22.12 or newer. All three packages now say so in `engines`
993
+ (#896).
994
+ 2. Bump all three `@ic-reactor/*` packages to `4.0.0-beta.6` in one
995
+ `npm install`. react 4.0.0-beta.6 peers core at exactly `4.0.0-beta.6`, so
996
+ they move together. The command keeps each package in the section of
997
+ `package.json` it is in:
998
+
999
+ ```sh
1000
+ npm install --save-exact @ic-reactor/core@4.0.0-beta.6 @ic-reactor/react@4.0.0-beta.6 @ic-reactor/vite-plugin@4.0.0-beta.6
1001
+ ```
1002
+
1003
+ Name only the packages the app has.
1004
+
1005
+ 3. Check what the app passes `createClient` (breaking, #895). A value the
1006
+ types forbid now fails closed with a `TypeError`: compare environment text
1007
+ (`allowEnvConfig: import.meta.env.VITE_ALLOW_ENV === "true"`), move a
1008
+ top-level `host` into `network: { host }`, decode a hex `rootKey` to its
1009
+ bytes, and drop or correct the key an error names.
1010
+ 4. Build options from the current `useClient()` value (breaking, #894). It is
1011
+ a new object for each caller, so list it in `useMemo` and effect
1012
+ dependencies, compare two clients by `client.queryClient`, and keep a
1013
+ write's outcome in the app's own state when it must stay on screen across
1014
+ an account switch.
1015
+ 5. Treat `caller_changed` from a write as the same error from a read: nothing
1016
+ ran (#893). A write TanStack Query held back (offline, behind a mutation
1017
+ `scope`, or retried) is refused, with nothing sent, when someone else is
1018
+ signed in by the time it would go out, and a write restored from a
1019
+ persister is always refused. Call `mutate()` again as the new caller.
1020
+ 6. Check where pages run with `network: "env"`:
1021
+ - on `ic0.app`, Codespaces or Gitpod, `allowEnvConfig: true` now throws;
1022
+ name canisters with `{ id }` there (#900);
1023
+ - a dev page opened by a LAN address, a `*.local` name or a tunnel now
1024
+ calls its own origin, never mainnet (#902);
1025
+ - on a canister's own `icp0.io` page, `{ name }` targets now resolve from
1026
+ the cookie's canister ids, with mainnet's root key (#901).
1027
+ 7. Smaller changes, each with its migration in the entries below: a `switch`
1028
+ over `AuthLike`'s `getStatus().state` needs a `default` (#911); an app's
1029
+ own queries on `client.queryClient` now retry as a plain `QueryClient`'s do
1030
+ (#903); two copies of `@tanstack/query-core` now throw instead of
1031
+ misbehaving (#873); with the method typed `any`, pass `arg`, not `[arg]`
1032
+ (#874); and an auth factory's error now comes from `signIn()`, not from the
1033
+ first read (#912).
1034
+
1035
+ The guide (`llms.txt`) and the skill now say they apply to 4.0.0-beta.6, and
1036
+ the five examples pin `^4.0.0-beta.6`.
1037
+
1038
+ ### @ic-reactor/core, @ic-reactor/react, @ic-reactor/vite-plugin
1039
+
1040
+ #### Changed
1041
+
1042
+ - **Breaking for Node below 22.12:** all three packages now declare
1043
+ `"engines": { "node": ">=22.12.0" }`; core and react said `>=18` and the
1044
+ Vite plugin said nothing. None of them worked on Node 18 (no global
1045
+ `crypto` in an ES module for `createTestClient`, and a `candid-core-cli gen`
1046
+ the plugin runs that needs a newer `readdir`), and `require()` of core and
1047
+ react, which are ES-only, needs Node 22.12 (or 20.19, which reached end of life on
1048
+ 2026-04-30). A floor can fall in a 4.x minor but not rise, so it starts at
1049
+ the first Node 22 that works. CI now runs the package tests and a
1050
+ `candid-core-cli gen` on Node 22.12.0 itself. Migration: run Node 22.12 or
1051
+ newer (#896).
1052
+
1053
+ #### Fixed
1054
+
1055
+ - The tarball now ships the MIT `LICENSE` the manifest names; the repository
1056
+ has a root `LICENSE` again, and `pnpm verify:packages` fails a tarball that
1057
+ has none or one that is not a copy of the root file (#898).
1058
+
1059
+ ### @ic-reactor/core
1060
+
1061
+ #### Added
1062
+
1063
+ - `timeoutMs` bounds how long a call waits for its answer (#875). On the
1064
+ canister target, `client.canister(actor, { id, timeoutMs })`, it bounds every
1065
+ direct call, `queryOptions` read and mutation made through that canister
1066
+ object; in `client.mutationOptions()`' third argument, `{ timeoutMs }`, it
1067
+ bounds that mutation's runs instead. When it passes, the call rejects with
1068
+ `code: "timeout"`: `cancelled` with `mayHaveExecuted: false` for a read and
1069
+ for a write that had not gone out, and `outcome_unknown` with
1070
+ `mayHaveExecuted: true` for a write whose request went out (an
1071
+ `{ update: "idempotent" }` read's included) and for a re-send of a write
1072
+ `client.resendOf()` offered, at any point, which `client.resendOf()` offers
1073
+ like any other unknown outcome when given the canister object the write went
1074
+ through (another `timeoutMs` is another object). Such a write is to be
1075
+ reconciled, never sent again blindly. A timed-out write that went out
1076
+ carries `expiresAt`; a re-send that timed out before it went out has none,
1077
+ and the earlier error's still holds. Nothing is re-sent for a timeout, and
1078
+ the request is not torn down. Canister objects that differ only in
1079
+ `timeoutMs` share query keys. Without `timeoutMs`, nothing changes, except
1080
+ for the next entry.
1081
+ - A read aborted while it waits for its caller's identity now rejects
1082
+ `cancelled` at once, with no code, and sends nothing. It used to wait for the
1083
+ identity, then reject `caller_changed`, `client_disposed` or
1084
+ `unauthenticated` (`identity_unavailable`) when one of those applied, and
1085
+ otherwise send the read nobody waited for. The abort now comes first.
1086
+ - Core's size limit is raised to 16.32 kB for `timeoutMs` (16,269 B, +381 B),
1087
+ and the app checks with the peers included to 91.34 kB (91,291 B, +394 B)
1088
+ and 92.61 kB (92,561 B, +399 B).
1089
+ - `client.time(canister)`: the IC's time in nanoseconds since the epoch, as a
1090
+ `bigint`, the unit of an ICRC-1 `created_at_time` (#876). It reads the
1091
+ `time` path with a `read_state` routed by the canister's subnet and verifies
1092
+ the certificate against the network's root key, as every reply is; a
1093
+ certificate that does not verify rejects `not_delivered`. The certificate's
1094
+ age is not compared with the host's clock, the clock this member replaces.
1095
+ It needs no signed-in caller. The management canister `aaaaa-aa` routes no
1096
+ `read_state`, so it is refused `invalid_args` (`management_canister`) with
1097
+ nothing sent. It is a `Client` member: the export budget is unchanged.
1098
+ - `client.encode(canister, method, vars)`: the Candid bytes a call of
1099
+ `method` with `vars` sends, as a `Uint8Array`, for an ICRC-21 consent
1100
+ request's `arg` or an audit log (#877). It uses the call path's own encoder
1101
+ and variables rule, and variables that do not encode throw `invalid_args`
1102
+ with the `issues` a call of them rejects with. It sends
1103
+ nothing and needs no canister id.
1104
+ - `Resend.bytes`, on what `client.resendOf()` offers: the bytes the attempt
1105
+ sent, which `send()` sends again; a copy on every read (#877).
1106
+ - `ReactorError.expiresAt`: on a write that got no answer (`outcome_unknown`,
1107
+ or `cancelled` with `mayHaveExecuted: true`), the latest ingress expiry its
1108
+ sends were signed with, in nanoseconds since the epoch on the IC's clock
1109
+ (#878). It is read from each call request as the agent builds it, before
1110
+ it is signed, so a signal that aborts while the answer is awaited keeps it.
1111
+ A call request whose signal aborted before it was signed is not sent. A
1112
+ write the client sent again carries the latest of its sends' expiries, and
1113
+ a `resendOf` re-send its own. Answered or refused writes and reads carry
1114
+ none. It is not enumerable, so `JSON.stringify(error)` keeps working. A
1115
+ field of an existing type: the export budget is unchanged.
1116
+ - `client.canisterId(target)` gives what a target resolves to now, from the
1117
+ resolver the calls use, without a call: `{ ok: true, id }`, or
1118
+ `{ ok: false, reason, message }` with `reason` `"no_browser"`,
1119
+ `"untrusted_host"` or `"absent"`, and the resolver's reason text, which a
1120
+ call on that target includes in its error. It reads the `ic_env` cookie on each call. A `Client`
1121
+ member, so the export budget is unchanged (#881).
1122
+ - `AuthState.expiresAt`: when the session ends, in milliseconds since the
1123
+ epoch, while signed in through an auth whose `getStatus()` carries
1124
+ `expiresAtMs` (`AuthClient` 10 does), so an app can show "signed in until".
1125
+ `AuthLike`'s `getStatus()` may carry that optional `expiresAtMs` (#912).
1126
+ - `createTestClient().dropNextReply({ method, canister, times })`
1127
+ (`@ic-reactor/core/testing`): loses the replies of the next `times` call
1128
+ requests that name `method` and are addressed to `canister`, with
1129
+ `refuseNext`'s matching rules and checks, and lets every other request
1130
+ through (#882). A test can aim a lost reply at its write while a certified
1131
+ read or an ICRC-21 consent read before it is answered. `dropNextReply()`
1132
+ with no argument is unchanged: it loses the reply of whatever call request
1133
+ comes next, certified reads included, as its JSDoc and the testing page now
1134
+ say. No new export.
1135
+
1136
+ #### Changed
1137
+
1138
+ - `network: "env"` on a canister's own `<id>.icp0.io` or `<id>.raw.icp0.io`
1139
+ page, with a mainnet agent host, believes the `ic_env` cookie's canister
1140
+ ids by default (#901). Both parents are on the Public Suffix List, so the
1141
+ cookie is the asset canister's own, and `{ name }` targets now resolve
1142
+ after `icp deploy -e ic` with no `allowEnvConfig`. Its root key is never
1143
+ taken there (the agent keeps mainnet's), and its Internet Identity URL
1144
+ only over `https:`. `ic0.app` and custom domains are unchanged. Migration:
1145
+ an app that relied on `{ name }` failing on icp0.io needs nothing; to
1146
+ refuse the cookie there, pass `allowEnvConfig: false`.
1147
+ - `AuthLike`'s `getStatus().state` takes any string beside the four states
1148
+ of `@icp-sdk/auth` 10, so an `@icp-sdk/auth` release that adds a session
1149
+ state does not stop `auth: (network) => new AuthClient(network)` compiling.
1150
+ The client already called anonymously in such a state; `authState()` reads
1151
+ it as `"anonymous"`. A weekly CI job type-checks core and runs the core and
1152
+ react tests against `@icp-sdk/auth@latest`. Code that switches over
1153
+ `getStatus().state` exhaustively needs a `default` (#911).
1154
+ - `allowEnvConfig: true` trusts less of the `ic_env` cookie (#900). The
1155
+ cookie's root key is never taken for a mainnet host (a mainnet boundary
1156
+ domain, or the `https://icp-api.io` fallback a custom-domain page uses):
1157
+ the agent keeps mainnet's own key. Its `INTERNET_IDENTITY_PROVIDER` is
1158
+ taken over `http:` only on a local page. And on a page under `ic0.app`,
1159
+ `*.app.github.dev` (Codespaces) or `*.gitpod.io`, none of which is a public
1160
+ suffix, so any sibling site can set the cookie, `createClient` throws a
1161
+ `TypeError` for `allowEnvConfig: true`. Migration: on those pages, name the
1162
+ canisters with `{ id }` (build-time env) and a replica that is not mainnet
1163
+ with `{ host, rootKey }`; on mainnet, drop any reliance on a cookie root
1164
+ key; serve a deployed Internet Identity URL over `https:`.
1165
+ - A view of a client pinned to a principal (what `useClient()` returns)
1166
+ writes only as that principal: its `mutationOptions` reject `cancelled`
1167
+ (`caller_changed`, `mayHaveExecuted: false`) and send nothing while someone
1168
+ else is the caller. Their `mutationKey` ends with the principal
1169
+ (`[..., method, caller]`, still found by a filter on the shorter key), so
1170
+ TanStack never hands a run pressed before a switch the later render's
1171
+ options, on any version and with any `QueryClient`; after a switch the
1172
+ mutation a component shows starts over, idle, and the stale run reports its
1173
+ refusal to the options it was pressed with. An app that shows an outcome
1174
+ across a switch (an unknown one, to read back as its sender) keeps it in
1175
+ its own state, from the promise `mutateAsync` returns. Its `authState()` is the client's own
1176
+ while its principal is the live caller, so an expired session reads as
1177
+ expired (#894). The client's own `mutationOptions`, a canister's direct
1178
+ calls and `func` still sign as the caller current when they run.
1179
+
1180
+ #### Fixed
1181
+
1182
+ - **BREAKING:** `createClient` checks every option, so a value the types
1183
+ forbid but untyped code can pass (an `import.meta.env` value, a spread,
1184
+ plain JavaScript) fails closed with a `TypeError` naming the option and
1185
+ what it got (#895). Before, `allowEnvConfig: "false"` was truthy and
1186
+ believed the `ic_env` cookie on any page, `fetchRootKey: "false"` fetched
1187
+ the root key from a remote host, a hex `rootKey` made every call fail, and
1188
+ an unknown key was ignored (`host` beside `network`, the 3.x habit, talked
1189
+ to port 8000). It now refuses an option it does not have, in the options
1190
+ and in a network object, listing those it has; an `allowEnvConfig` or
1191
+ `fetchRootKey` that is not `true` or `false`; a `rootKey` that is not 133
1192
+ bytes in a `Uint8Array` (`null` included); and a `fetch` that is not a
1193
+ function. `createTestClient` checks the network and `allowEnvConfig` it
1194
+ forwards the same way, and refuses a `signedIn` that is not a boolean. `parseUnits` and `formatUnits` refuse
1195
+ options that are not an object or have a key they do not take (Intl's
1196
+ `maximumFractionDigits`, a misspelt `signd`), and `parseUnits` a `signed`
1197
+ that is not a boolean. Migration: compare environment text,
1198
+ `allowEnvConfig: import.meta.env.VITE_ALLOW_ENV === "true"`; move `host`
1199
+ into `network: { host }`; decode a hex root key to its bytes; drop or
1200
+ correct the key the error names.
1201
+ - A write from `client.mutationOptions()` goes out as the caller current when
1202
+ `mutate()` was called, or not at all (#893). The client's `queryClient`
1203
+ reads the caller of each mutation run in its `MutationCache` when the run
1204
+ starts, so an `onMutate` of the app's that replaces the client's keeps the
1205
+ rule; on another `QueryClient`, the client's `onMutate` reads it (before
1206
+ TanStack Query 5.89, which passes no run context, by the options object
1207
+ TanStack calls it on; the runs of one observer share that object, so
1208
+ before 5.89 a write whose observer has another run pending that started
1209
+ as someone else is refused). It reads no caller for a mutation of the app's. When
1210
+ TanStack Query held the write back (paused offline, queued behind another
1211
+ mutation of its `scope`, or retried by an app's `retry`) and someone else is
1212
+ current by the time it is sent, it rejects with `kind: "cancelled"`,
1213
+ `code: "caller_changed"`, `mayHaveExecuted: false`, and nothing is sent; it
1214
+ was sent as whoever was signed in then. A mutation restored from a
1215
+ persister is now always refused that way, since who started it is not
1216
+ known. Migration: treat `caller_changed` from a write like the same error
1217
+ from a read (nothing ran), and call `mutate()` again as the new caller if
1218
+ that is what the user wants; resume a persisted write by calling `mutate()`
1219
+ again, not with `resumePausedMutations()`.
1220
+ - `network: "env"` no longer falls back to mainnet on a dev page opened by a
1221
+ LAN address (RFC 1918, link-local, IPv6 ULA or link-local), a `*.local`,
1222
+ `*.test` or `*.internal` name (`host.docker.internal`), or a tunnel
1223
+ (`*.ngrok-free.app`, `*.ngrok-free.dev`, `*.ngrok.app`, `*.ngrok.io`,
1224
+ `*.trycloudflare.com`, `*.loca.lt`) (#902). Such a page routes through its
1225
+ own origin, the dev server's proxy, as a Codespaces page does: the cookie
1226
+ is not believed and no root key is taken or fetched, so certified calls
1227
+ fail verification instead of reaching mainnet. A dev wallet opened from a
1228
+ phone showed, and after a mainnet sign-in sent, real ICP. Migration: a
1229
+ production app served from such a name now calls its own origin; give it
1230
+ `network: "ic"` or a `{ host, rootKey }` network.
1231
+ - An app's own queries on `client.queryClient`, such as REST calls under
1232
+ `ReactorProvider`, keep TanStack Query's retries: the default `retry`
1233
+ applies the client's rule only to a `ReactorError`, and retries any other
1234
+ error up to 3 times in a browser, as a plain `QueryClient` does (never on a
1235
+ server). The client's own reads set `retry` themselves and are unchanged.
1236
+ An app that turned retries off through this default sets `retry: false` on
1237
+ its queries instead (#903).
1238
+ - A write from `client.mutationOptions` invalidates the reads on the
1239
+ `QueryClient` it ran on, which TanStack Query 5.89 and later hands it, as
1240
+ well as on `client.queryClient`: an app that nests its own
1241
+ `QueryClientProvider` inside `ReactorProvider` now sees its reads refresh
1242
+ after a write (#903).
1243
+ - A reply over about a million bytes decodes. candid-core charges a blob one
1244
+ element per byte against its 1,000,000-element budget, so an SNS wasm from
1245
+ SNS-W's `get_wasm`, the ICPSwap factory's `getActiveWasm` or a large asset
1246
+ chunk rejected `invalid_reply` (`value_elements limit 1000000 exceeded`), and
1247
+ an update with such a reply rejected after it ran. A reply may now hold one
1248
+ value per byte plus the budget, which bounds only values that take no
1249
+ bytes, such as a long `vec null`, however large a blob beside them. A new `maxElements` option of
1250
+ `createClient` and `createTestClient` sets that budget, and a mock decodes
1251
+ its arguments the same way, so a test can upload a large chunk (#899).
1252
+ - `client.queryOptions()` and `client.queryKey()` refuse a symbol given as
1253
+ the variables that is not core's `skipToken`, with a `TypeError` that says
1254
+ two copies of `@tanstack/query-core` are installed and how to get one
1255
+ (#873). Such a symbol is the other copy's `skipToken`; it used to be
1256
+ encoded as the argument, so a read meant to wait ran and rejected
1257
+ `invalid_args`. A method whose one argument is Candid `reserved` still
1258
+ takes any value, a symbol included. This check runs in production too: an
1259
+ app with two copies gets a thrown `TypeError` where it got a rejected read.
1260
+ Migration: install `@tanstack/query-core` at the exact version your
1261
+ `@tanstack/react-query` depends on, so that `npm ls @tanstack/query-core`
1262
+ shows one copy.
1263
+ - A read made with `queryOptions` stops waiting for the HTTP answer to its
1264
+ request, and to each `read_state` that polls for it, once its query
1265
+ function's signal aborts, and rejects `cancelled` (part of #875). Its signal
1266
+ used to reach only the check before sending and the waits between polls:
1267
+ neither the client nor the agent bounds an HTTP request, so a request that
1268
+ got no answer kept the read waiting after TanStack cancelled it. The request
1269
+ itself is not torn down. `mayHaveExecuted` is `false`, except for an update
1270
+ read with `{ update: "idempotent" }` whose call request went out.
1271
+ - With the method given as `any` (`client.queryOptions<A, any>(canister,
1272
+ method, vars)` for a method named by data), the variables of
1273
+ `queryOptions`, `queryKey`, `mutationOptions` (its `mutationFn`) and
1274
+ `resendOf` (its `dedupedBy` and `arg`) follow the run-time rule: the value
1275
+ for a one-argument method, a tuple for several, `undefined` or `[]` for
1276
+ none (#874). They were typed `unknown[]`, so a one-argument call compiled
1277
+ only as `[arg]`, the shape the client refuses with `invalid_args`. They are
1278
+ now any value but a symbol, so a read with a real value keeps
1279
+ `CanisterQueryOptions<unknown, never, never>`. `[arg]` still compiles (an
1280
+ array is a value too) and the client still refuses it with `invalid_args`.
1281
+ Migration: code that wrote `[arg]` (or cast `arg as never`) on this path
1282
+ writes `arg`; a `dedupedBy` that read `arg[0]` reads `arg`. Inside a
1283
+ function generic over the `Actor`, only `queryOptions` gets the wider type;
1284
+ `mutationOptions`, `queryKey` and `resendOf` still need a cast there.
1285
+ - A session that ends by the clock is announced: the client sets a timer for
1286
+ the auth's `expiresAtMs` and tells its listeners, so `useAuth()` renders
1287
+ `"expired"` at the session's end with no user activity, where it kept
1288
+ saying `"signed-in"` while calls went out anonymous. A read of `caller()`
1289
+ or `authState()` that sees a change no notification announced also tells
1290
+ the listeners, in a microtask. A sign-in that ends at another time than the
1291
+ last one now notifies too, since `expiresAt` changed (#912).
1292
+ - An auth factory that throws (`new AuthClient()` reads `localStorage`,
1293
+ which throws where site data is blocked) no longer breaks every read: it is
1294
+ called once, the client calls anonymously so public reads still work, the
1295
+ error is logged once in development, and `signIn()` and `signOut()` reject
1296
+ with what the factory threw. It was called again on every read and render,
1297
+ and its error escaped from `caller()`, `authState()`, `subscribe()`,
1298
+ `queryOptions()` and direct calls. Migration: catch the factory's error
1299
+ from `signIn()`, not from the first read; build a new client to try the
1300
+ factory again (#912).
1301
+ - The test auth of `createTestClient()` expires by the clock, as
1302
+ `AuthClient` does: past its `expiresAtMs` (eight hours after a sign-in) its
1303
+ status reads `"expired"` without telling anyone. `auth.expire()` is still
1304
+ the explicit form, which does tell them (#912).
1305
+
1306
+ ### @ic-reactor/react
1307
+
1308
+ #### Changed
1309
+
1310
+ - **BREAKING:** `useClient()` returns a view of the client for the caller
1311
+ the render shows every time, not the provider's client object once the
1312
+ live caller renders (#894). The view is a new object after a sign-in, a
1313
+ switch or a sign-out and the same one across renders for one caller, so a
1314
+ `useMemo`, an effect or React Compiler's cache keyed on it builds the new
1315
+ caller's options, where they kept the previous caller's key (and showed
1316
+ that caller's data) before. A write built from it is refused rather than
1317
+ signed by a caller the render did not show. Migration: build options from
1318
+ the current `useClient()` value and list it in the dependencies; compare
1319
+ two clients by `client.queryClient`, not by identity; read who signs now
1320
+ from the client you created, since the view's `caller()` is its render's.
1321
+ A handler that does `await client.signIn()` and then `mutation.mutate(arg)`
1322
+ presses with the last committed render's options: it is refused
1323
+ (`caller_changed`) when the signed-in render has not committed yet (one
1324
+ that suspends on the user's data); let the next render press, or use a
1325
+ direct canister call, which signs as the caller current.
1326
+ - `useAuth()` returns `expiresAt` while signed in, and renders again when it
1327
+ changes and when a session ends by the clock (#912).
1328
+
1329
+ #### Fixed
1330
+
1331
+ - In development, `ReactorProvider` throws when the client's `QueryClient`
1332
+ is not an instance of `@tanstack/react-query`'s `QueryClient`: two copies
1333
+ of `@tanstack/query-core` are loaded (#873). Production builds do not
1334
+ check. Migration: install `@tanstack/query-core` at the exact version your
1335
+ `@tanstack/react-query` depends on; `npm ls @tanstack/query-core` must show
1336
+ one copy.
1337
+
1338
+ ### @ic-reactor/vite-plugin
1339
+
1340
+ #### Added
1341
+
1342
+ - `onGenerated`, an option called once for each module a run generated, in
1343
+ `vite build` and `vite dev`, with the module's canister `names`, the
1344
+ absolute `didFile`, `module` and `envelope` paths, its `status`
1345
+ (`"written"` or `"unchanged"`) and a `signal` that the run's close aborts.
1346
+ It runs inside the plugin's queue of runs, so `buildStart` waits for it,
1347
+ and a close stops waiting. A throw fails the module's canisters as a
1348
+ failed generation does, and the next run calls it again. Not called for a
1349
+ canister that failed, nor for a run that does not generate the module
1350
+ again. An option, not an export (#880).
1351
+ - Under `vite dev` and `vite preview`, a warning, once per server, when a
1352
+ page is loaded by a `Host` that is not loopback, saying that `"env"` is for
1353
+ `localhost` pages (#902).
1354
+
1355
+ #### Fixed
1356
+
1357
+ - On Windows, local network detection works with icp-cli installed from npm
1358
+ (`@icp-sdk/icp-cli`). npm links it as `icp.cmd`, which the plugin could not
1359
+ start, so detection always failed while the network was running. On
1360
+ Windows the plugin now runs the `icp.exe` that package installs, from the
1361
+ app's own install first, then a global npm install (`%APPDATA%\npm`), so a
1362
+ command that hangs is killed after its timeout; it runs the package's bin
1363
+ script with Node when that `icp.exe` is missing, and falls back to an
1364
+ `icp.exe` on `PATH`. When no `icp` can be started at all, the
1365
+ warning says so and how to make one runnable on that platform, instead of
1366
+ asking whether the network is running. CI runs the tests of how
1367
+ the plugin starts `icp` on Windows (#905).
1368
+
1369
+ ### Docs
1370
+
1371
+ - The README and the Vite plugin guide say that any local network restart
1372
+ needs a dev server restart, even when the root key stays the same: the
1373
+ plugin keeps sending the ids it detected first (#881).
1374
+ - The client guide has a "Deploy to mainnet" section: `icp deploy -e ic`,
1375
+ what `"env"` resolves to on `icp0.io`, `ic0.app` and a custom domain,
1376
+ `{ id }` from build-time env with the local and the mainnet id, and when
1377
+ `allowEnvConfig: true` is safe; `llms.txt` states the rule (#901).
1378
+ - The consumer guide (`packages/core/llms.txt`) and the Reads, Writes, Values
1379
+ and Client pages say five things that already worked (#879): a read with
1380
+ `{ update: "idempotent" }` is still an update, refused for a signed-out or
1381
+ `"anonymous"` client, and an ICRC-21 consent message is read as the caller
1382
+ who will send the write; after a `canister_err` the canister calls
1383
+ temporary, send the same argument object again; `parseUnits` accepts
1384
+ trailing zeros, `"5."` and `".5"`, and refuses only significant excess
1385
+ fraction digits; a kept `resendOf` offer is typed
1386
+ `NonNullable<ReturnType<typeof client.resendOf<A, "method">>>`; and a client
1387
+ made outside a provider, or kept across its unmount, is the app's to release
1388
+ with `client.dispose()`.
1389
+ - The guide and the Writes page say how to get the bytes a write sends
1390
+ (#877).
1391
+ - The guide and the Errors page say when a read-back after an unknown write
1392
+ outcome is final: once the IC's time is past `error.expiresAt` (#878).
1393
+ - The guide, Writes and Errors stamp `created_at_time` from
1394
+ `client.time(ledger)` instead of `Date.now()` (#876).
1395
+ - The guide, the auth and client pages and `AuthLike`'s JSDoc no longer say a
1396
+ wallet fits with a small adapter. What fits is a sign-in that hands over an
1397
+ `Identity` (a delegation or a key); a wallet that signs each call itself
1398
+ (ICRC-25 and ICRC-49: OISY, Plug, NFID accounts) does not. The auth page
1399
+ shows such a wallet's writes through `SignerAgent` beside an anonymous
1400
+ client, with the reads invalidated by hand (#904).
1401
+ - The client guide has a section for a local network without the Vite plugin
1402
+ (Next.js, Rsbuild, webpack, esbuild): `network: "local"` with `{ id }` from
1403
+ a build-time variable written by `icp canister status -e local -i`, or a
1404
+ Next.js rewrite of `/api/v2` and `/api/v3` to keep `"env"`. Core's guide
1405
+ states the rule in a few lines (#906).
1406
+ - Getting started has a step 0 that creates the project (Vite's `react-ts`
1407
+ template, or `npm init -y` and `npm pkg set type=module` for Node), so its
1408
+ top-level `await` scripts run under `npx tsx` on the first try. It, core's
1409
+ package page and core's guide now say that core and react are ES modules
1410
+ only and what CommonJS code can do, and show the `.js` import form
1411
+ `moduleResolution: "nodenext"` needs (#907).
1412
+ - The guide's Errors section says how long a call waits: unless the call has
1413
+ a `timeoutMs`, an HTTP request has no timeout in the client or the agent, an accepted update polls for about
1414
+ five minutes (a deadline checked when a poll is answered) before `outcome_unknown`, and
1415
+ a cancelled read stops waiting (part of #875).
1416
+ - The guide's Setup, both READMEs, the core and react package pages and
1417
+ Getting started say to install `@tanstack/query-core` at the version
1418
+ `@tanstack/react-query` depends on, and to check that
1419
+ `npm ls @tanstack/query-core` shows one copy (#873).
1420
+ - The auth guide says that a `"signed-in"` session may be read-only:
1421
+ Internet Identity can scope one to queries, and `@icp-sdk/auth` does not
1422
+ expose that scope yet (#912).
1423
+ - The docs site has three guides that build a whole app step by step: an
1424
+ ICRC-1 wallet with Internet Identity sign-in, a Next.js server page for
1425
+ one user, and an MCP server that gives an AI agent a ledger's tools
1426
+ (#948, #953, #954). It now has B3Pay's look and the core mark, and one
1427
+ home for the three libraries with a library switcher (#943, #944, #945,
1428
+ #947, #955).
1429
+
1430
+ ### Examples (not published)
1431
+
1432
+ - `icrc-ledger`'s Sandbox tab finds its sandbox by the client's
1433
+ `QueryClient`, and reads who signs now from the sandbox's client, since
1434
+ `useClient()` returns a view per caller (#894).
1435
+ - `canister-studio`'s write runner shows the confirm step again, with the
1436
+ new sender, when a write is refused because the caller changed before the
1437
+ page caught up (#894).
1438
+ - An outcome stays on screen across an account switch: `canister-studio`'s
1439
+ write runner and wallet Send panel, `icrc-ledger`'s Sandbox transfer, and
1440
+ `vite-wallet`'s Send and Pay forms (through one `useTransfer` hook), keep
1441
+ the outcome of the write they sent in their own state, since the mutation
1442
+ the new caller's render shows starts over idle; `vite-wallet` reads
1443
+ `client.resendOf`'s offer from that kept outcome (#894).
1444
+ - `node-agent-tool` no longer tells you to export an identity's key into the
1445
+ project. Its README and docs page say to give the tool, and an agent running
1446
+ it, an identity of its own that controls nothing, with its key outside the
1447
+ project and readable by you alone, and that the agent can read the seed
1448
+ variable.
1449
+ The example's `.gitignore` and the root one now ignore `*.pem` (#910).
1450
+ - A fifth example, `canister-studio`: a TanStack Start app, rendered on the
1451
+ server, that gives every method of any mainnet canister an automatic form
1452
+ and calls it with ic-reactor's rules in view. The ICP, ckBTC and ckETH
1453
+ ledgers and the ckBTC minter are typed by three generated modules; any
1454
+ other canister's interface is read from its certified `candid:service`
1455
+ metadata, compiled on the server by `@candid-core/cli` and rebuilt in the
1456
+ browser with `schemaFromContract`, and an interface from the uncertified
1457
+ fallback query is labelled so. It shows one client per request and per
1458
+ tab, reads hydrated without a refetch, shared query links rendered on the
1459
+ server, writes confirmed with their sender and exact bytes, `resendOf`
1460
+ after an unknown outcome, certified reads, an idempotent update read as a
1461
+ query (ckBTC's `get_btc_address`), a Safety lab of ten scenarios over
1462
+ `createTestClient()` in the browser, and a playground that compiles a
1463
+ pasted `.did` in the browser. Its tests run offline; its `smoke` script
1464
+ checks its `.did` files and reads against mainnet. It pins
1465
+ `^4.0.0-beta.6`, `@candid-core/schema` `0.4.0` and `@candid-core/cli`
1466
+ `0.3.0`, a runtime dependency here, and has a page in the examples docs.
1467
+ The root README, the docs' home page and Getting started now say five
1468
+ examples.
1469
+
1470
+ ## core, react, vite-plugin 4.0.0-beta.5
1471
+
1472
+ Prepared on 2026-10-11. It goes out under npm's `beta` dist-tag, as
1473
+ `@ic-reactor/core@beta`; `latest` stays 3.x until 4.0 GA. Changes since
1474
+ 4.0.0-beta.4.
1475
+
1476
+ This release moves ic-reactor 4 onto the candid-core pair published on
1477
+ 2026-10-10. Both are pinned exactly, where beta.4 required `0.3.0` and
1478
+ `0.2.0`:
1479
+
1480
+ - `@ic-reactor/core`: peer `@candid-core/schema` at exactly `0.4.0`;
1481
+ - `@ic-reactor/vite-plugin`: peer `@candid-core/cli` at exactly `0.3.0`, the
1482
+ version its install hints now name.
1483
+
1484
+ Three things are new, as core's and the Vite plugin's entries below say:
1485
+
1486
+ - `client.queryOptions` reads the mode each method of a generated `Actor`
1487
+ now carries, and an update or a oneway method does not compile there
1488
+ (#851).
1489
+ - The Vite plugin's `methods` on a canister entry: a fetched `didFile` is
1490
+ written as the projection of the live interface to the methods the app
1491
+ calls, so the generated module has only them (#852, phase 2).
1492
+ - The Vite plugin's live check: `verify: "live"` on an entry, or
1493
+ `IC_REACTOR_VERIFY`, checks a `didFile` on disk against the live canister's
1494
+ certified interface when a build or dev server starts.
1495
+
1496
+ No `@ic-reactor/*` export is added or removed (core 13,
1497
+ `@ic-reactor/core/testing` 2, react 4, vite-plugin 2). The pair itself changes
1498
+ no call, key or error at run time: what it adds, each method's mode in the
1499
+ generated `Actor`, is read by `client.queryOptions` at compile time only.
1500
+ Schema 0.4.0's JavaScript is 0.3.0's but for one header comment, so an app's
1501
+ bundle does not change.
1502
+
1503
+ What an app on beta.4 does:
1504
+
1505
+ 1. Bump all three `@ic-reactor/*` packages to `4.0.0-beta.5` and the pair to
1506
+ `0.4.0` and `0.3.0`, in one `npm install`. npm does not resolve a partial
1507
+ upgrade cleanly: core 4.0.0-beta.5 peers schema exactly
1508
+ `0.4.0` and core 4.0.0-beta.4 exactly `0.3.0`; the Vite plugin
1509
+ 4.0.0-beta.5 peers CLI exactly `0.3.0` and 4.0.0-beta.4 exactly `0.2.0`;
1510
+ and CLI 0.3.0 peers schema exactly `0.4.0`, CLI 0.2.0 exactly `0.3.0`. So
1511
+ a command that moves only some of them meets the others' peers: with npm
1512
+ 11, the CLI alone fails with `ERESOLVE`, and the schema alone installs
1513
+ with an `ERESOLVE overriding peer dependency` warning, leaving core's peer
1514
+ unmet. One command moves them all, and keeps each package in the section
1515
+ of `package.json` it is in (`dependencies` or `devDependencies`):
1516
+
1517
+ ```sh
1518
+ npm install --save-exact @ic-reactor/core@4.0.0-beta.5 @ic-reactor/react@4.0.0-beta.5 @ic-reactor/vite-plugin@4.0.0-beta.5 @candid-core/schema@0.4.0 @candid-core/cli@0.3.0
1519
+ ```
1520
+
1521
+ Name only the packages the app has. Keep one copy of the schema. A second
1522
+ one left at `0.3.0`, beside core's `0.4.0`, makes its `Principal` another
1523
+ type than core's, and passing one for the other fails with `TS2322` or
1524
+ `TS2345` ("Property '[principalBrand]' is missing").
1525
+
1526
+ 2. Regenerate every committed module with `candid-core-cli gen` (the Vite
1527
+ plugin does it when it starts). Until then `gen --check` reports each
1528
+ module CLI 0.2.0 wrote as `drifted` and exits 1; the `.envelope.json`
1529
+ beside it does not change. A module CLI 0.2.0 wrote still compiles against
1530
+ schema 0.4.0, but a module CLI 0.3.0 writes does not compile against
1531
+ `0.3.0` (`TS2694`, "has no exported member 'WithMode'", once per method).
1532
+
1533
+ 3. Code that imports the generated names compiles unchanged, but for the
1534
+ `queryOptions` calls of step 4. Two things in the module change what the
1535
+ compiler shows:
1536
+ - Each type is declared under its Candid name, `export type Account = …`,
1537
+ so a type error names `Account` where it named `$Account`. A name that
1538
+ cannot be a type keeps the `$`: the globals `Array`, `Promise`, `Record`
1539
+ and `Uint8Array`, and words TypeScript refuses as a type's name, such as
1540
+ `delete` or `string`.
1541
+ - Each method of `type Actor` is its call signature intersected with
1542
+ `$.WithMode<"query" | "composite_query" | "update" | "oneway">`.
1543
+ `Parameters<>`, `ReturnType<>`, `keyof Actor` and a plain async function
1544
+ that implements a method are unchanged, and so is what
1545
+ `client.canister<Actor>()` returns. A type test that compares a method
1546
+ type of `Actor` for exact equality with its bare signature now fails.
1547
+
1548
+ 4. Expect the compile errors of `client.queryOptions` listed under
1549
+ `@ic-reactor/core` below: `TS2769` ("No overload matches this call") for
1550
+ an update or a oneway method of a generated `Actor`, and for some calls
1551
+ that ran without a `TypeError` on beta.4: an update's fourth argument whose
1552
+ type may be `undefined`, and a method TypeScript cannot tell is a read (a
1553
+ type parameter, a union that may name an update, any method of a generic
1554
+ `Actor`). A write goes through `client.mutationOptions`; an update that
1555
+ answers the same however often it runs takes `{ update: "idempotent" }`;
1556
+ and a generic wrapper gives the type arguments,
1557
+ `client.queryOptions<A, any>(…)`. Nothing changes at run time.
1558
+
1559
+ 5. Check the Vite plugin's options. It now refuses what beta.4 read without a
1560
+ word, as its entries below say: a key that is no option (a misspelled
1561
+ `didfile` or `canisterID`), a value given that is not one (`null`
1562
+ included), a `failOnError` or `injectEnvironment` other than `true` or
1563
+ `false`, and options, `canisters` or an entry that is not a plain object.
1564
+ A key or value of a canister entry fails that canister, under
1565
+ `failOnError`; one of the options themselves throws when the config loads.
1566
+ Each message names the option or quotes the value.
1567
+
1568
+ ### @ic-reactor/core
1569
+
1570
+ #### Changed
1571
+
1572
+ - Its peer `@candid-core/schema` is exactly `0.4.0`, where it was `0.3.0`
1573
+ (#866). Core's types read its `ModeOf` for the check below; core's
1574
+ JavaScript does not change but for its version string.
1575
+
1576
+ #### `queryOptions` refuses an update or a oneway method at compile time (#851)
1577
+
1578
+ `client.queryOptions(canister, method, …)` reads the mode each method of a
1579
+ generated `Actor` carries (`$.WithMode<mode>`, from `candid-core-cli gen`
1580
+ 0.3.0), and its `method` takes only a method a read may be built from:
1581
+
1582
+ | The method's mode in the `Actor` | Without the fourth argument | With `{ update: "idempotent" }` |
1583
+ | --------------------------------- | --------------------------- | ------------------------------- |
1584
+ | `query`, `composite_query` | compiles | compiles |
1585
+ | `update` | **does not compile** | compiles |
1586
+ | `oneway` | **does not compile** | **does not compile** |
1587
+ | none (an `Actor` written by hand) | compiles | compiles |
1588
+
1589
+ This is a compile-time break for code that names an update method of a
1590
+ generated `Actor` without a fourth argument whose type is
1591
+ `{ update: "idempotent" }`, or that names a oneway method with or without it.
1592
+ The error is `TS2769` ("No overload matches this call"), saying that the
1593
+ method `is not assignable to parameter of type 'ReadMethod<Actor>'`, or
1594
+ `'OptInMethod<Actor>'` for a oneway with the opt-in. Use
1595
+ `client.mutationOptions` for a write, or call it directly; pass
1596
+ `{ update: "idempotent" }` only for an update that answers the same however
1597
+ often it runs.
1598
+
1599
+ Such a call with the method's name written out compiled on beta.4 and threw a
1600
+ `TypeError` when it ran. Two kinds of call compiled on beta.4, could run
1601
+ without a `TypeError`, and no longer compile:
1602
+
1603
+ - **A fourth argument that may be `undefined`.** Its type, such as that of an
1604
+ optional options parameter a wrapper passes on, does not say it is the
1605
+ opt-in, so for an update method the call does not compile, though it
1606
+ ran whenever the value was defined. Pass `{ update: "idempotent" }`, or a
1607
+ value typed `{ readonly update: "idempotent" }` (the type is not
1608
+ exported). For a query it compiles as before.
1609
+ - **A method whose type is not one name the `Actor` marks as a read.** The
1610
+ call compiles only when TypeScript can tell that the method is a
1611
+ `ReadMethod<A>`. It cannot for three kinds of method, each of which compiled
1612
+ on beta.4 and is valid at run time whenever the value names a query:
1613
+ - a type parameter: in
1614
+ `function read<A, M extends keyof A & string>(c: Canister<A>, method: M, vars: …)`,
1615
+ `client.queryOptions(c, method, vars)`;
1616
+ - a union that may name an update, such as a method picker typed
1617
+ `keyof Actor & string` or `"icrc1_name" | "icrc1_transfer"` (a union of
1618
+ queries compiles);
1619
+ - any method of a canister whose `Actor` type is generic: in
1620
+ `function fee<A extends { icrc1_fee: () => Promise<bigint> }>(c: Canister<A>)`,
1621
+ `client.queryOptions(c, "icrc1_fee", …)`, with or without the opt-in.
1622
+
1623
+ `ReadMethod` is not exported for a function to constrain its types with.
1624
+ The escape is explicit type arguments,
1625
+ `client.queryOptions<A, any>(c, method, vars)`: it compiles, the run-time
1626
+ check still refuses an update or a oneway, and the options' data is
1627
+ `unknown`, for the caller to state. (A wrapper that passed `...args: any[]`,
1628
+ or no variables, did not compile on beta.4 either.)
1629
+
1630
+ An `Actor` written by hand is unchanged: its methods say no mode, every one of
1631
+ them compiles as before, and the run-time check refuses an update or a oneway
1632
+ as before.
1633
+
1634
+ Nothing changes at run time: every check `queryOptions` made, it still
1635
+ makes, and it is the only check for an `Actor` written by hand and for a
1636
+ composite query of a certified canister (the type of a canister object does
1637
+ not say it is certified). A call that compiled and did not throw builds the
1638
+ same options, with the same types. No export is added (`ReadMethod` and
1639
+ `OptInMethod` are named in compiler messages, not exported), and the bundle
1640
+ does not change. The reads guide, the writes guide and the guide
1641
+ (`llms.txt`) say so.
1642
+
1643
+ ### @ic-reactor/react
1644
+
1645
+ - No change but the version. Its peer `@ic-reactor/core` is packed as
1646
+ exactly `4.0.0-beta.5`, so the two move together.
1647
+
1648
+ ### @ic-reactor/vite-plugin
1649
+
1650
+ #### Changed
1651
+
1652
+ - Its peer `@candid-core/cli` is exactly `0.3.0`, where it was `0.2.0`
1653
+ (#866), and the install hints the plugin prints name it. The plugin
1654
+ generates with it as before; `methods` and the live check also run its
1655
+ `project` and `check` commands, which 0.3.0 adds.
1656
+ - Its optional peer `@icp-sdk/core` is loaded for the live check too, as for
1657
+ a fetch.
1658
+
1659
+ #### Added
1660
+
1661
+ - `methods` on a canister entry (#852, phase 2): a `didFile` fetched from its
1662
+ `canisterId` is written as the projection of the live interface to those
1663
+ methods and every type they reach (`candid-core-cli project`), so the
1664
+ generated module has only them. A name the canister does not have fails the
1665
+ canister, naming it and listing the canister's methods, and nothing is
1666
+ written. A `didFile` on disk is never fetched for a change of `methods`: it
1667
+ is compared with the list, with no network access, and a name the file
1668
+ does not hold fails the canister, while a method the file holds that the
1669
+ list does not name is a warning. Both say to fetch it again with
1670
+ `IC_REACTOR_FETCH`. A file whose methods cannot be listed fails the
1671
+ canister. Canisters that name one `didFile` share its fetch only
1672
+ when they name one list, entries with no `canisterId` included (no
1673
+ `methods` is the whole interface): with different lists, or a list that
1674
+ cannot be projected to, nothing is fetched, before any request. A shared
1675
+ `didFile` on disk is read by them all and compared with each list.
1676
+ - The live check: `verify: "live"` on an entry, or `IC_REACTOR_VERIFY=1` (or
1677
+ `true`, `all`, or canister names separated by commas) for every entry with a
1678
+ `canisterId`; `IC_REACTOR_VERIFY=0` (or `false`) turns it off for a run,
1679
+ `verify: "live"` included. When a build or dev server starts, the plugin fetches the
1680
+ certified interface and checks the `didFile` on disk against it
1681
+ (`candid-core-cli check`). A method that is missing (`method_missing`),
1682
+ changed mode (`mode_changed`) or is no longer compatible
1683
+ (`method_incompatible`, with the path into its type) fails the canister,
1684
+ naming the canister and each method, under `failOnError`. A compatible
1685
+ change is a warning: `special_opt_rule` findings, and a change to the types
1686
+ of the file's methods, told by the identities of the two projections to
1687
+ them. A check that cannot be made fails the canister, and says to build
1688
+ with `IC_REACTOR_VERIFY=0`. Under `vite dev` a failed check stands after a
1689
+ save, whatever it changed, until a restart checks it again and it passes,
1690
+ fetches the file again, or runs with the check off. A pass is reused only
1691
+ within one build or dev server (a rebuild, another Vite environment), and
1692
+ only until a check of that canister fails or cannot be made, or a fetch of
1693
+ its file fails or reads another interface: a restart, or the next build of
1694
+ the same plugin object, checks every canister again. `verify` takes only `"live"`, and any other
1695
+ value fails the canister.
1696
+ - A run (the start of a build or dev server, or a save's regeneration) reads
1697
+ each `didFile` once, and again after each step that runs `candid-core-cli`
1698
+ on it (listing its methods, the live check, generation), since each reads
1699
+ the file itself. A file written before the run is done with it, even saved
1700
+ back, is not taken, and the run reports nothing its steps found of it.
1701
+ `vite build` fails the canister for that reason alone
1702
+ (`changed during the build`) and puts back a module the generator wrote
1703
+ meanwhile; `vite dev` queues the file again, as a save, and says nothing of
1704
+ the canister until that run does. A check of a file written before the
1705
+ check was done, passed or failed, records no pass and neither sets nor
1706
+ clears a failed check.
1707
+ - Every option value given is checked when the plugin is made, on every
1708
+ canister, one without a `didFile` included, and whether or not a build
1709
+ would use it: only an omitted one counts as not given. A canister whose
1710
+ entry is not a plain object, or whose value is not one (`null`, `false`,
1711
+ `0` or `""` from a JavaScript config, a `didFile` or `outDir` that is not a
1712
+ path, a `canisterId` that is not the text of a principal, a `network`,
1713
+ `methods` or `verify` that is none of their values, `verify: "live"` with
1714
+ no `canisterId`, a getter that throws) fails, quoted as given whatever its
1715
+ kind (a bigint, a symbol, an object that holds itself), before the plugin
1716
+ fetches, writes, runs or requests anything to generate it or a `didFile` it
1717
+ shares; the other canisters build. So does a canister whose module a
1718
+ canister before it writes (two `.did` files of one name in one `outDir`), at
1719
+ the start of each run, and a canister `IC_REACTOR_FETCH` or
1720
+ `IC_REACTOR_VERIFY` names with no `canisterId`. Under `vite dev` a save of
1721
+ the file of any of these, or its deletion, leaves the canister failed for
1722
+ the same reason and runs nothing for it: a save neither generates it, over
1723
+ another's module or not, nor takes it for fixed. Each is still named in the
1724
+ `ic_env` cookie, but one whose name the cookie cannot carry (below); a
1725
+ `canisterId` that is not one is left out of it, with a warning.
1726
+ `failOnError` and `injectEnvironment` take `true` or `false`, and
1727
+ `canisters` a plain object: any other value, `null` included, throws when
1728
+ the config loads, instead of being read by truthiness or taken for none.
1729
+ Every default is taken for an omitted option alone. Every key is checked
1730
+ too, whatever its value: a key of an entry that is none of its fields (a
1731
+ misspelled `didfile` or `canisterID`), or of a `network` of `{ host }`
1732
+ other than `host`, fails the canister, and a key of the options that is
1733
+ none of `canisters`, `injectEnvironment` and `failOnError` throws when the
1734
+ config loads; the message names the option the key may misspell.
1735
+ - A dev server restarted with the same plugin object (an inline config, as
1736
+ `server.restart()` keeps it) has runs of its own: the old server's close,
1737
+ which Vite runs after the new server started, stops only the old server's.
1738
+ The new server's overlay starts as the old one's, and a failed live check
1739
+ still stands through the restart until a check of it passes.
1740
+ - Both run the app's `candid-core-cli` in a child process, as `gen` does, and
1741
+ never the library in the Vite process. No export is added; the two fields
1742
+ are new keys of `IcReactorPluginOptions["canisters"]`.
1743
+
1744
+ #### Fixed
1745
+
1746
+ - A canister's name is kept as given. One named `__proto__` (from a JSON
1747
+ config, or a computed key) with no `canisterId` of its own lost the ID `icp`
1748
+ reported for it to `Object.prototype`'s setter, so the `ic_env` cookie
1749
+ carried none; and one named after a member of `Object.prototype`
1750
+ (`__proto__`, `constructor`, `toString`, `hasOwnProperty`) with no ID found
1751
+ that member and was never missing: no warning, no page load asked `icp`
1752
+ again, and the cookie never carried its ID. A name that begins with `-`,
1753
+ which `icp` allows, was handed to `icp canister status` where `icp` reads a
1754
+ flag: it refused `-x`, and for `--help` printed its help, which the cookie
1755
+ carried as the canister's ID. A name that holds `&` or `=`, which no name
1756
+ `icp` gives a canister does, is left out of the cookie, with a warning: the
1757
+ cookie joins its entries with them, so it was read back as other keys, one
1758
+ of which could take the root key's place, and the app read no environment.
1759
+ - The options, `canisters`, each entry and a `network` of `{ host }` are read
1760
+ only as plain objects. A `Map` or a `Promise` of canisters (a loader that
1761
+ was not awaited) was read for its own keys, none, so the plugin started
1762
+ with no canister without a word, and an entry given so was taken for a
1763
+ canister only named in the cookie. Each is now refused, as any value that
1764
+ is not one, and so is an instance of a class, which was read through its
1765
+ class, or an object that inherits from another.
1766
+ - A key that is no option was never read, without a word. A misspelled
1767
+ `canisters` started the plugin with no canister; a misspelled `didFile`
1768
+ took its canister for one only named in the cookie, so nothing was
1769
+ generated and the module of the last run stayed; a misspelled `canisterId`
1770
+ generated it without its ID; and a `rootKey` beside a network's `host` was
1771
+ read as nothing. Each is now refused, naming the option the key may
1772
+ misspell: a key of the options throws when the config loads, and a key of
1773
+ an entry or of its network fails the canister.
1774
+ - The generator names its module and envelope after the `.did`'s file name
1775
+ with a final `.did` cut off, and nothing else, and reads only a file whose
1776
+ name ends in `.did`. The plugin's check of two canisters writing one module
1777
+ cut any extension, so a `didFile` such as `a/api.candid`, which the
1778
+ generator cannot read, claimed `api.ts`, and a canister after it on
1779
+ `c/api.did` was refused. The plugin now names these files by the
1780
+ generator's rule, which a test compares with what the CLI reports.
1781
+
1782
+ ### Docs
1783
+
1784
+ - The guide (`llms.txt`), react's guide and the skill say they apply to
1785
+ 4.0.0-beta.5 (#866).
1786
+ - Every install line and peer table names `@candid-core/schema` `0.4.0` and
1787
+ `@candid-core/cli` `0.3.0`: getting started, the Vite plugin guide, the
1788
+ core, react and Vite plugin pages, and the root and package READMEs.
1789
+ Getting started says that each method of `type Actor` is its call
1790
+ signature marked with its mode (`& $.WithMode<"query">`) (#866).
1791
+ - The reads guide's "Update methods" shows that `queryOptions` with an update
1792
+ method of a generated `Actor` does not compile, says what `ReadMethod<Actor>`
1793
+ is, that an `Actor` written by hand is checked at run time, which methods
1794
+ TypeScript cannot tell are reads and the escape `queryOptions<A, any>`, and
1795
+ that `{ update: "idempotent" }` opts in an update only, after the variables.
1796
+ It also says that a composite query of a certified canister is refused at
1797
+ run time only. The writes guide says `queryOptions` does not compile for an
1798
+ update of a generated `Actor`, and the guide (`llms.txt`) says the same in
1799
+ its reads section (#851).
1800
+ - The Vite plugin guide has four new sections, "Only the methods the app
1801
+ calls", "The live check", "How the plugin runs candid-core" and "A .did
1802
+ saved while the plugin reads it", and its options section says which values
1803
+ and keys are refused, that the options are plain objects, and that a
1804
+ canister's name is kept as given. The plugin's README has the first two
1805
+ sections and the same options text.
1806
+ Getting started points at `methods` (#852, phase 2).
1807
+
1808
+ ### Examples (not published)
1809
+
1810
+ - The four examples pin `^4.0.0-beta.5`, `@candid-core/schema` `0.4.0` and
1811
+ `@candid-core/cli` `0.3.0`, and their seven generated modules are
1812
+ regenerated with CLI 0.3.0. They moved in #866, ahead of this release: an
1813
+ example on schema `0.4.0` cannot install beta.4, whose core peers `0.3.0`.
1814
+ Until this release is on npm, their check against the published packages
1815
+ waits for it.
1816
+ - `icrc-ledger`'s Sandbox test marks
1817
+ `client.queryOptions(minter, "get_btc_address", ownAddress)` with
1818
+ `@ts-expect-error`, since that call no longer compiles, and still expects
1819
+ its `TypeError` at run time. The README says the call does not compile
1820
+ (#851).
1821
+ - `icrc-ledger`'s `icp_ledger.did` and `ckbtc_minter.did`, and `vite-wallet`'s
1822
+ `ledger.did`, were trimmed by hand. Each is now the projection the plugin
1823
+ wrote from the mainnet canister, and its entry names the `canisterId` and
1824
+ `methods` it came from. The files stay committed, so the examples build
1825
+ offline, and `IC_REACTOR_VERIFY=1` checks them against mainnet. The ICP
1826
+ ledger's projection names its transfer error `Icrc1TransferError`, so
1827
+ `vite-wallet` imports it by that name. `icp_ledger.envelope.json` does not
1828
+ change; the minter's argument record is inline, as the canister declares it.
1829
+
1830
+ ### CI (not published)
1831
+
1832
+ - Every module this repository generates (core's test fixtures, the e2e
1833
+ `hello_actor`, the snippet checker's modules) is regenerated with CLI 0.3.0,
1834
+ and core's type tests compile against them unchanged. React's type tests
1835
+ write their own stand-in for a generated `Actor`; it now marks each method
1836
+ with `WithMode`, as CLI 0.3.0 does, and compiles. The root, `e2e/`,
1837
+ `scripts/size-app/` and the export budget's schema pin move to the new pair
1838
+ (#866).
1839
+ - `pnpm verify:traps` has two new traps, `query-options-refuses-update` and
1840
+ `query-options-refuses-oneway`, which weaken `ReadMethod` and `OptInMethod`
1841
+ and show that the `@ts-expect-error` lines of `traps.test-d.ts` that refuse
1842
+ an update and a oneway then stop biting; `options-method-unknown` follows
1843
+ the new signatures. Core's type tests add the calls that compiled on beta.4
1844
+ and no longer do (#851).
1845
+ - `scripts/faults.json` grows from 298 to 417 entries, for the Vite plugin's
1846
+ new tests (#852, phase 2).
1847
+ - No size limit moves: core 14.96 kB of 15 kB, react 1.28 kB of 1.28 kB, and
1848
+ the app checks 89.95 kB of 90 kB and 91.16 kB of 91.2 kB, as at beta.4.
1849
+
1850
+ ## core, react, vite-plugin 4.0.0-beta.4
1851
+
1852
+ Prepared on 2026-10-10. It goes out under npm's `beta` dist-tag, as
1853
+ `@ic-reactor/core@beta`; `latest` stays 3.x until 4.0 GA. Changes since
1854
+ 4.0.0-beta.3.
1855
+
1856
+ An app on beta.3 upgrades by bumping all three packages to `4.0.0-beta.4`.
1857
+ Nothing in this release breaks code written for beta.3: no export is added or
1858
+ removed (core 13, `@ic-reactor/core/testing` 2, react 4, vite-plugin 2), no
1859
+ error kind or code is removed, no required peer changed, and a module
1860
+ generated for beta.3 need not be regenerated. The one exception is code that
1861
+ writes its own object of the `Client` type, such as a hand-made fake: the type
1862
+ gains `resendOf`, so that object no longer compiles until it has one (a client
1863
+ from `createClient` or `createTestClient` has it). Three things are new, as
1864
+ core's and the Vite plugin's entries below say:
1865
+
1866
+ - `isReactorError(error, canister, method)` narrows a direct call's error to
1867
+ that method's `Err` arm, so `err` is typed after `kind === "canister_err"`
1868
+ without a cast. The one-argument form is unchanged.
1869
+ - `client.resendOf(error, canister, method, { dedupedBy })` offers to send a
1870
+ write again exactly as it was sent, after a failure that left its outcome
1871
+ unknown, and only for a method the app says deduplicates (an ICRC-1
1872
+ transfer with `created_at_time`).
1873
+ - The Vite plugin writes a canister's missing `didFile` from the live
1874
+ canister's certified `candid:service`, when the entry names a `canisterId`.
1875
+ The entry's new `network` field says where the canister is, and
1876
+ `IC_REACTOR_FETCH` fetches a file on disk again.
1877
+
1878
+ Two of these change what some setups see:
1879
+
1880
+ - The Vite plugin lists `@icp-sdk/core` `^6.1.0` as an optional peer. An app
1881
+ on `@ic-reactor/core` has it already, at the range core asks for, and npm
1882
+ installs nothing for an optional peer. A project that installs the plugin
1883
+ next to an `@icp-sdk/core` outside `^6.1.0` gets a peer conflict from its
1884
+ package manager.
1885
+ - A canister entry with a `canisterId` whose `didFile` is not on disk is
1886
+ fetched from `network` (mainnet by default) and the file is written, where
1887
+ beta.3 failed that canister's generation for want of the file. A project
1888
+ that writes such a `.did` in another step (a local canister's build, say)
1889
+ writes it before Vite starts, or gives that entry `network: "local"` so
1890
+ the fetch reads the deployed local canister.
1891
+
1892
+ The guide (`llms.txt`) and the skill now say they apply to 4.0.0-beta.4, use
1893
+ `isReactorError(error, canister, method)` after a direct call, and teach
1894
+ `client.resendOf` as the one safe re-send. The four examples pin
1895
+ `^4.0.0-beta.4`, and `vite-wallet` offers its re-send through
1896
+ `client.resendOf`. The size limits are tighter in CI only, which affects
1897
+ pull requests to this repository, not apps.
1898
+
1899
+ It requires the stable candid-core pair, both pinned exactly, as in beta.3:
1900
+
1901
+ - `@ic-reactor/core`: peer `@candid-core/schema` at exactly `0.3.0`;
1902
+ - `@ic-reactor/vite-plugin`: peer `@candid-core/cli` at exactly `0.2.0`.
1903
+
1904
+ ```sh
1905
+ npm install --save-exact @candid-core/schema@0.3.0
1906
+ npm install --save-dev --save-exact @candid-core/cli@0.2.0
1907
+ ```
1908
+
1909
+ ### @ic-reactor/core
1910
+
1911
+ #### Added
1912
+
1913
+ - `isReactorError(error, canister, method)` narrows a direct call's error to
1914
+ `ReactorError<E>`, where `E` is that method's `Err` arm, so `err` is typed
1915
+ after `kind === "canister_err"` without a cast; for a method without an `Err`
1916
+ arm, `err` stays `undefined` (#849). The guard checks at run time: every
1917
+ error a call rejects with (a direct call, a query function or a mutation
1918
+ function built from the canister) carries the canister object it was made
1919
+ on, under an internal symbol, and the guard is `true` only for an error of
1920
+ that method on that canister object. An error of another method, of another
1921
+ canister object (the `certified: true` one, or the same canister of another
1922
+ client) or of a `client.func` call is `false`, and a `false` result leaves
1923
+ every `kind` possible. The one-argument form is unchanged, including
1924
+ point-free use: `errors.filter(isReactorError)` is still typed
1925
+ `ReactorError<unknown>[]`, because the one-argument signature is listed last.
1926
+ It is an overload of an existing export: core still has 13 names.
1927
+ - `client.resendOf(error, canister, method, { dedupedBy })` offers to send a
1928
+ write again exactly as it was sent, after a failure that left its outcome
1929
+ unknown, and only for a method the app says deduplicates (#850). It returns
1930
+ `undefined` unless `error` is a write of that method on that canister with
1931
+ `mayHaveExecuted: true`, its argument is an object in which `dedupedBy`
1932
+ finds a key (`(arg) => arg.created_at_time` for an ICRC-1 transfer; `null`
1933
+ or `undefined` offers nothing), and the principal that sent it is the
1934
+ caller now: nothing after a sign-out or a sign-in as another account, the
1935
+ offer again once the sender signs back in, and on a view pinned to a
1936
+ principal (hydration) the live caller decides. It also offers nothing once
1937
+ the argument object was changed since it was sent, or once the canister
1938
+ object resolves to another canister id than the write went to (a `{ name }`
1939
+ the `ic_env` cookie maps elsewhere). The offer's `arg` is the attempt's own
1940
+ argument, and goes out only as the same write. `send()` sends the bytes
1941
+ the attempt sent, to the canister id it went to (a management call routed
1942
+ by the effective canister id it went out with). `mutate(arg)`, on any
1943
+ mutation of this client, sends it only to that method and canister id, and
1944
+ only while it encodes to those bytes. Each refusal sends nothing: `cancelled`
1945
+ (`caller_changed`) once someone else is the caller, `cancelled`
1946
+ (`target_changed`) for another method or canister id, and `invalid_args`
1947
+ (`arg_changed`) for an argument changed since. Once offered, that object
1948
+ stays held to that write, so a new write needs a new argument object. The
1949
+ client cannot check that a canister deduplicates: a method without such a
1950
+ key has no safe re-send. It is a method of `Client`, not a new export: core
1951
+ still has 13 names. Core's size limit is raised to 15 kB (14,957 B
1952
+ measured, +508 B for `resendOf`), and the app checks to 90 kB (89,949 B,
1953
+ +465 B) and 91.2 kB (91,155 B, +463 B). React's own code is unchanged.
1954
+
1955
+ ### @ic-reactor/react
1956
+
1957
+ - No change but the version. Its peer `@ic-reactor/core` is packed as
1958
+ exactly `4.0.0-beta.4`, so the two move together.
1959
+
1960
+ ### @ic-reactor/vite-plugin
1961
+
1962
+ #### Added
1963
+
1964
+ - A canister entry with a `canisterId` has its `didFile` fetched from the live
1965
+ canister when the file is not on disk (#852, phase 1). The plugin reads
1966
+ `candid:service` from certified state with `HttpAgent.readState`, checked
1967
+ against the network's root key, writes it to `didFile`, and generates the
1968
+ module from it; the file is committed with the app. A `didFile` on disk
1969
+ costs no network request, under `vite build` and `vite dev`, and is fetched
1970
+ again only on request: `IC_REACTOR_FETCH=<name>` (names separated by
1971
+ commas, or `all`). The new `network` field says where the canister is:
1972
+ `"ic"` (mainnet's root key, the default), `"local"` (icp-cli's
1973
+ `http://127.0.0.1:8000`, its root key fetched) or `{ host }` (its root key
1974
+ fetched only when the host is local, as `createClient` decides). An
1975
+ unreachable network, a refused read (private metadata), absent metadata, a
1976
+ canister that does not exist, a certificate that does not verify (naming
1977
+ the root key it was checked against) and no answer within 30 seconds each
1978
+ fail with their own message, naming the canister and what to do next, under
1979
+ `failOnError` as generation does. A `didFile` that cannot be written fails
1980
+ only the canisters that name it.
1981
+ No export is added.
1982
+ - `@icp-sdk/core` `^6.1.0` is an optional peer, loaded only for a fetch.
1983
+ Every app on `@ic-reactor/core` has it already.
1984
+
1985
+ ### Docs
1986
+
1987
+ - Getting started's "Get a canister's .did" leads with the plugin's
1988
+ `canisterId`, and keeps the script for an app without Vite. The Vite plugin
1989
+ guide has a section on the fetch: when it happens, `network` and its root
1990
+ keys, `IC_REACTOR_FETCH`, and its four failures.
1991
+ - The errors guide shows `isReactorError(error, canister, method)` and what it
1992
+ checks; its duplicate-transfer recipe and the writes guide's direct
1993
+ transfer read the typed `err` instead of casting it. The consumer guide
1994
+ (`llms.txt`) and the skill say to use it after a direct call.
1995
+ - The writes guide has a section, "Sending the same write again", on
1996
+ `client.resendOf` (#850): when a re-send is safe, what `dedupedBy` states,
1997
+ that the offer goes out only as its sender, and the refusals
1998
+ `caller_changed`, `target_changed` and `arg_changed`, each of which leaves
1999
+ the write's outcome unknown. The errors guide lists the two new codes,
2000
+ `target_changed` under `cancelled` and `arg_changed` under `invalid_args`,
2001
+ says not to ignore a refused re-send as another `cancelled`, and its
2002
+ duplicate-transfer recipe offers the re-send with `client.resendOf`. The
2003
+ core reference page shows the three-argument `isReactorError` signature and
2004
+ `resendOf` on `Client`, and the Vite plugin reference page lists the
2005
+ optional `@icp-sdk/core` peer and when it is loaded.
2006
+ - Getting started has a section on keeping the IC stack (the client,
2007
+ `AuthClient` and the generated module) out of an app's first load with
2008
+ `React.lazy`, with no change to the library (#855). Measured on the Vite
2009
+ wallet with Vite 8.3.0, the first load drops from 622.46 kB (193.56 kB
2010
+ gzipped) to 222.30 kB (69.90 kB gzipped), and 401.79 kB (126.21 kB gzipped)
2011
+ moves to chunks loaded asynchronously. It says what a route guard sees meanwhile:
2012
+ `AuthState.status` keeps its four members, with none for "not loaded yet",
2013
+ so a guard lives inside the lazy tree and the `Suspense` fallback stands for
2014
+ "not known yet". It also says that the boundaries for `useSuspenseQuery`
2015
+ reads and lazy routes go below the `ReactorProvider`: with only the entry's
2016
+ boundary above it, a suspending read rebuilds the client on every attempt
2017
+ and never renders. The Auth guide links to it.
2018
+
2019
+ ### CI (not published)
2020
+
2021
+ - Core's size limit was set to 14.4 kB gzipped, just above its measured 14,347 B,
2022
+ where it was 50 kB, as react's 1.28 kB already sits at its size. Two new
2023
+ checks measure what an app pays with the peers included, which the
2024
+ packages' own checks never see: `{ createClient }` (89,363 B against
2025
+ 89.5 kB) and `createClient` with `ReactorProvider` and `useClient`, React
2026
+ itself left out (90,566 B against 90.7 kB). They run in `pnpm size`, CI's
2027
+ "Check package sizes" step, from `scripts/size-app/`. A pull request that
2028
+ moves a size states the delta and raises its limit deliberately
2029
+ (CONTRIBUTING.md, "Size budget"). Core's config moved from its
2030
+ `package.json` to `packages/core/.size-limit.js`, which is not published.
2031
+ - Core's size limit is raised to 14.5 kB: the guard's canister check adds
2032
+ 102 B (14,449 B measured). The app checks measure 89,484 B (+121 B) and
2033
+ 90,692 B (+126 B), still under their limits.
2034
+
2035
+ ### Examples (not published)
2036
+
2037
+ - The four examples pin `^4.0.0-beta.4`.
2038
+ - `vite-wallet`'s "Send the same transfer again" is `client.resendOf`'s offer,
2039
+ in `SendIcp.tsx` and `SendToContact.tsx`, in place of the attempt and its
2040
+ sender the page tracked by hand. The offer is made only while the account
2041
+ that sent the transfer is the caller, and a new test checks it is withdrawn
2042
+ on a sign-out and made again once the same account signs back in. Its
2043
+ `vite.config.ts` says when the plugin would fetch `ledger.did`: never while
2044
+ the trimmed, committed file is on disk, unless `IC_REACTOR_FETCH=ledger`
2045
+ asks. These edits waited for this release because each example installs
2046
+ the published packages, which carry `resendOf` and the fetch from
2047
+ 4.0.0-beta.4 on.
2048
+
2049
+ ## core, react, vite-plugin 4.0.0-beta.3
2050
+
2051
+ Prepared on 2026-10-07. It goes out under npm's `beta` dist-tag, as
2052
+ `@ic-reactor/core@beta`; `latest` stays 3.x until 4.0 GA. Changes since
2053
+ 4.0.0-beta.2.
2054
+
2055
+ An app on beta.2 upgrades by bumping all three packages to `4.0.0-beta.3`. An
2056
+ app on `network: "local"` against a dfx replica names it instead:
2057
+ `network: { host: "http://127.0.0.1:4943" }`.
2058
+
2059
+ No export is added or removed (core 13, `@ic-reactor/core/testing` 2, react 4,
2060
+ vite-plugin 2), no peer changed, and a module generated for beta.2 need not be
2061
+ regenerated. Two things changed, as core's and the Vite plugin's entries
2062
+ below say:
2063
+
2064
+ - The `auth` factory is handed the client's network, so
2065
+ `auth: (network) => new AuthClient(network)` signs users in on the network
2066
+ the client calls. Code that compiled against beta.2 compiles unchanged,
2067
+ unless it calls a function typed `ClientOptions["auth"]` with no argument,
2068
+ uses one where a function of no argument is expected, or passes as `auth` a
2069
+ factory whose optional first parameter is not the network.
2070
+ - **BREAKING:** `network: "local"` is icp-cli's local network,
2071
+ `http://127.0.0.1:8000`, not dfx's `http://127.0.0.1:4943`. The test
2072
+ client's default host (where its fake replica answers) and the Vite
2073
+ plugin's fallback `/api` proxy moved with it. `"env"` on a server with
2074
+ dfx's `DFX_NETWORK=local` and no `ICP_NETWORK` keeps 4943.
2075
+
2076
+ The guide (`llms.txt`) and the skill now say they apply to 4.0.0-beta.3, and
2077
+ the guide teaches the factory's argument. The four examples pin
2078
+ `^4.0.0-beta.3`, and the two that sign in (`next-ssr`, `vite-wallet`) pass
2079
+ the network to `AuthClient`.
2080
+
2081
+ It requires the stable candid-core pair, both pinned exactly, as in beta.2:
2082
+
2083
+ - `@ic-reactor/core`: peer `@candid-core/schema` at exactly `0.3.0`;
2084
+ - `@ic-reactor/vite-plugin`: peer `@candid-core/cli` at exactly `0.2.0`.
2085
+
2086
+ ```sh
2087
+ npm install --save-exact @candid-core/schema@0.3.0
2088
+ npm install --save-dev --save-exact @candid-core/cli@0.2.0
2089
+ ```
2090
+
2091
+ ### @ic-reactor/core
2092
+
2093
+ #### Added
2094
+
2095
+ - The `auth` factory of `createClient` is called with one argument, the
2096
+ client's network, under `@icp-sdk/auth` 10's own option names, so
2097
+ `auth: (network) => new AuthClient(network)` signs users in on the network
2098
+ the client calls (#790). `() => new AuthClient()` and every other factory
2099
+ that takes no argument work as before, which for `AuthClient` means on
2100
+ mainnet only, since such a factory ignores the network. No export is added:
2101
+ the argument's type is written inline in `ClientOptions`.
2102
+ - `network.agentOptions` is the host, root key and `shouldFetchRootKey` of
2103
+ the client's own agents, and the client's `fetch` when one was given,
2104
+ which `AuthClient` makes its mint and revoke calls with.
2105
+ - `network.identityProvider` is, in order: the `ic_env` cookie's
2106
+ `INTERNET_IDENTITY_PROVIDER` where the client trusts the cookie, with the
2107
+ cookie's `PUBLIC_CANISTER_ID:internet_identity` or
2108
+ `rdmx6-jaaaa-aaaaa-aaadq-cai`; for `"local"` or a network object whose
2109
+ host is local, icp-cli's built-in Internet Identity,
2110
+ `http://id.ai.localhost:<port of the host>/authorize` with
2111
+ `rdmx6-jaaaa-aaaaa-aaadq-cai`; and otherwise absent, which `AuthClient`
2112
+ reads as mainnet's Internet Identity.
2113
+ - A replica that is not mainnet rejects every delegation mainnet's Internet
2114
+ Identity mints. Where no provider can be named on a network that does not
2115
+ verify against mainnet's root key (a trusted cookie that names an
2116
+ `internet_identity` canister but no URL, `"env"` on a local page whose
2117
+ cookie names no provider, or a replica with a root key of its own, given
2118
+ or fetched), the client warns once, in development, when the factory
2119
+ reads `network.identityProvider`, as `new AuthClient(network)` does, that
2120
+ sign-in cannot work there, with the line that names a provider:
2121
+ `(network) => new AuthClient({ ...network, identityProvider: { authorizeUrl, canisterId } })`.
2122
+ A factory that names its own provider, as that line does, or builds an
2123
+ auth that is not Internet Identity never sees it, and neither does a
2124
+ spread that adds other options and names no provider, such as
2125
+ `{ ...network, derivationOrigin }`, which must name one there. `"ic"` and
2126
+ `"env"` on a mainnet page never warn, and neither does the test client of
2127
+ `@ic-reactor/core/testing`, whose factory ignores the network.
2128
+
2129
+ Code that compiled against 4.0.0-beta.2 and stops compiling: a call of a
2130
+ function typed `ClientOptions["auth"]` with no argument, or its use as a
2131
+ `() => AuthLike`. Pass it the network, or type the function
2132
+ `() => AuthLike`. A factory whose optional first parameter is not the
2133
+ network, such as `(x?: string) => ...`, stops compiling too.
2134
+
2135
+ #### Changed
2136
+
2137
+ - **BREAKING:** `network: "local"` is icp-cli's local network,
2138
+ `http://127.0.0.1:8000`, rather than dfx's replica on
2139
+ `http://127.0.0.1:4943` (#790). 8000 is icp-cli's default gateway port
2140
+ ([`gateway.port`](https://github.com/dfinity/icp-cli/blob/ffcb2235515bc5623e6c98ba0b9d24a303aaac36/docs/reference/configuration.md#L294),
2141
+ [the `local` network](https://github.com/dfinity/icp-cli/blob/ffcb2235515bc5623e6c98ba0b9d24a303aaac36/docs/reference/configuration.md#L513),
2142
+ [environments](https://github.com/dfinity/icp-cli/blob/ffcb2235515bc5623e6c98ba0b9d24a303aaac36/docs/concepts/environments.md#L73)).
2143
+ For a dfx replica, name it: `network: { host: "http://127.0.0.1:4943" }`.
2144
+ The same holds for:
2145
+ - `"env"` on a server with `ICP_NETWORK=local` and no `ICP_HOST` or
2146
+ `IC_HOST`, which now falls back to `http://127.0.0.1:8000`. With dfx's
2147
+ `DFX_NETWORK=local` and no `ICP_NETWORK` (which overrides it), the
2148
+ fallback stays dfx's `http://127.0.0.1:4943`, since that variable says
2149
+ the project runs dfx.
2150
+ - The Internet Identity the `auth` factory is handed for `"local"`, which
2151
+ is `http://id.ai.localhost:8000/authorize`, the Vite plugin's default.
2152
+ - `createTestClient` of `@ic-reactor/core/testing`, whose default host is
2153
+ `"local"`'s. With no `network`, outside a page, it now answers at
2154
+ `http://127.0.0.1:8000`, and its query keys carry that host as their
2155
+ network segment. A test that asserted `http://127.0.0.1:4943` there
2156
+ passes `network: { host: "http://127.0.0.1:4943" }` to keep it.
2157
+
2158
+ ### @ic-reactor/react
2159
+
2160
+ #### Documentation
2161
+
2162
+ - The README and `ReactorProviderProps`' doc comments build the client with
2163
+ `auth: (network) => new AuthClient(network)`. The code is unchanged.
2164
+
2165
+ ### @ic-reactor/vite-plugin
2166
+
2167
+ #### Changed
2168
+
2169
+ - **BREAKING:** When detection fails, `/api` goes to icp-cli's local network,
2170
+ `http://127.0.0.1:8000`, rather than `http://127.0.0.1:4943` (#790), as
2171
+ `"local"` does in core and as the cookie's fallback Internet Identity,
2172
+ `http://id.ai.localhost:8000/authorize`, already did. A project on a dfx
2173
+ replica proxies `/api` itself (`server.proxy["/api"]`), which the plugin
2174
+ leaves in place.
2175
+
2176
+ ### Docs
2177
+
2178
+ - Every `auth` factory in the docs, the guide (`llms.txt`) and the READMEs
2179
+ is `(network) => new AuthClient(network)`. The Auth guide's 25-line local
2180
+ Internet Identity setup is that one line, with the rules above and the
2181
+ override for a project that deploys its own `internet_identity`.
2182
+ - Getting started says where `icrc1.did` comes from, runs the first read
2183
+ with `npx tsx`, says to change the default `App` import of Vite's
2184
+ template, builds the transfer's argument in place, says to commit the
2185
+ `.did` and the generated module (Vite's `react-ts` template type-checks
2186
+ before the plugin generates), and names `injectEnvironment: false` for an
2187
+ app that only talks to mainnet. A new section, "Get a canister's .did",
2188
+ adds a script that saves a live canister's `.did` from certified state,
2189
+ with the `icp canister metadata` command that does the same.
2190
+
2191
+ ### Examples (not published)
2192
+
2193
+ - The four examples pin `^4.0.0-beta.3`. The two that sign in pass the
2194
+ network to `AuthClient`: `next-ssr`'s provider and `vite-wallet`'s client
2195
+ build it with
2196
+ `(network) => new AuthClient(network)`. `vite-wallet`'s hand-built local
2197
+ Internet Identity (`src/auth/internet-identity.ts` and its test) is gone: on
2198
+ a local page the client hands its factory the cookie's provider and the
2199
+ local network, and on a deployed page no provider, which `AuthClient` reads
2200
+ as mainnet's. Its `isLocalPage` tests, which decide where the dev account is
2201
+ offered, moved to `src/auth/local-page.test.ts`. These edits waited for this
2202
+ release because each example installs the published packages, which carry
2203
+ the factory's argument from 4.0.0-beta.3 on.
2204
+ - `node-agent-tool`'s network comment says `local` is icp-cli's
2205
+ `http://127.0.0.1:8000`.
2206
+
2207
+ ## core, react, vite-plugin 4.0.0-beta.2
2208
+
2209
+ Prepared on 2026-10-06. It goes out under npm's `beta` dist-tag, as
2210
+ `@ic-reactor/core@beta`; `latest` stays 3.x until 4.0 GA. Changes since
2211
+ 4.0.0-beta.1.
2212
+
2213
+ An app on beta.1 upgrades by bumping all three packages to `4.0.0-beta.2`, and
2214
+ the candid-core pair to its stable releases, both exact:
2215
+
2216
+ ```sh
2217
+ npm install --save-exact @candid-core/schema@0.3.0
2218
+ npm install --save-dev --save-exact @candid-core/cli@0.2.0
2219
+ ```
2220
+
2221
+ No export is added or removed (core 13, `@ic-reactor/core/testing` 2, react 4,
2222
+ vite-plugin 2), and a module generated for beta.1 need not be regenerated.
2223
+ Code that compiled against beta.1 compiles unchanged, unless it does one of
2224
+ the four things core's Changed entry on `queryOptions` lists, or switches
2225
+ exhaustively over candid-core's `ContractIssueCode`. What is new is
2226
+ one error code a missing canister now carries, an aimed form of the test
2227
+ client's `refuseNext`, and read options that TanStack Query's suspense hooks
2228
+ take without a cast.
2229
+
2230
+ The 4 docs are at https://ic-reactor.b3pay.net/v4/: getting started, guides to
2231
+ the client, reads, writes, errors, auth, SSR, testing, values and the Vite
2232
+ plugin, a page per package, and "Migrating from 3.x" with the table of every
2233
+ removed 3.x name and what replaces it (#789). Each package's `homepage` now
2234
+ points at its page there.
2235
+
2236
+ ### @ic-reactor/core
2237
+
2238
+ #### Added
2239
+
2240
+ - `ReactorError.code` is `"canister_not_found"` when the IC answers that the
2241
+ canister a call was routed to does not exist (#821), on a read or a write.
2242
+ The IC says it in two ways, and both get the code: a boundary node's HTTP
2243
+ 400 whose body's error line is `canister_not_found`, for a canister id
2244
+ outside every subnet's range, and a replica's reject code 3 with the IC
2245
+ error code `IC0301`, for an id inside a subnet's range that holds no
2246
+ canister. Mainnet answers both ways, and the fake replica of
2247
+ `@ic-reactor/core/testing` answers the second. `kind` and `mayHaveExecuted`
2248
+ are unchanged from the classification table. Any other HTTP 400 and any
2249
+ other reject still has no `code`.
2250
+ - `refuseNext(status, { method?, canister?, times? })` on the object
2251
+ `createTestClient()` returns (`@ic-reactor/core/testing`): a refusal aimed
2252
+ at the queries and calls that name `method` and are addressed to
2253
+ `canister`, each when it is given, so a test can throttle `icrc1_transfer`
2254
+ while the reads around it are answered (#822). A query and a replicated
2255
+ call of the same method both match; `canister` is the canister a request
2256
+ names, and need not be mocked; `times` (default 1) counts matching
2257
+ requests, each send of a call the client re-sends included. The status and
2258
+ `read_state` requests an agent makes on its own never match. A request that
2259
+ several refusals match is refused by the one armed first. An option it does
2260
+ not have, an empty `method` or a `canister` that is not a canister id
2261
+ throws a `TypeError`. `refuseNext(status, times?)` is unchanged: it refuses
2262
+ the next `times` queries or calls, whatever they are for. No export is
2263
+ added.
2264
+
2265
+ #### Changed
2266
+
2267
+ - `client.queryOptions(...)` built from variables that cannot be `skipToken`
2268
+ (a method without arguments called without them or with `undefined`, or
2269
+ variables of a type `skipToken` is not assignable to) returns options whose
2270
+ `queryFn` type excludes `SkipToken`, so TanStack Query's `useSuspenseQuery`
2271
+ and `useSuspenseQueries` take them without a cast (#828). Variables typed
2272
+ `V | SkipToken`, or `skipToken` itself, keep `SkipToken` in `queryFn`'s
2273
+ type, and the suspense hooks still refuse them. `queryOptions` has a second
2274
+ signature for that case, and `CanisterQueryOptions` a third type parameter
2275
+ that defaults to today's type. The options' `retry` also takes an error
2276
+ typed `unknown`, as `useQueries` and `useSuspenseQueries` type it, so those
2277
+ hooks take the options too; `useQuery` and a `QueryObserver` still type the
2278
+ query's error `ReactorError<E>`. Variables of a type `skipToken` is
2279
+ assignable to cannot be told from it: the options of such a method keep
2280
+ `SkipToken` whatever the variables: read it with `useQuery`, or cast its
2281
+ `queryFn` for a suspense read. Of what `candid-core-cli gen` writes, that is only a method whose one
2282
+ argument is Candid `reserved` (typed `unknown`): it writes `record {}` as
2283
+ `Record<string, never>`, which a suspense read takes. A type written by
2284
+ hand that `skipToken` fits, such as `{}`, is another. Types only: nothing
2285
+ changes at run time, and no export is added. Code that compiled against
2286
+ 4.0.0-beta.1 and stops compiling:
2287
+ - `queryFn === skipToken` on options built from variables that cannot be
2288
+ `skipToken` is now TS2367 ("no overlap"). Such a check was always false,
2289
+ so remove it.
2290
+ - A function typed `Client["queryOptions"]` (one function cannot satisfy
2291
+ both signatures), and a call that spreads
2292
+ `Parameters<Client["queryOptions"]>` back into `client.queryOptions`
2293
+ (`Parameters` takes the last signature only). Call `client.queryOptions`
2294
+ with the canister and method, and spread its result to add options:
2295
+ `{ ...options, staleTime: 1 }`. `ReturnType<Client["queryOptions"]>`
2296
+ still compiles.
2297
+ - A `retry` written into the options' type with its error parameter
2298
+ annotated, such as `retry: (n: number, e: ReactorError<E>) => ...` in a
2299
+ value typed by the options: the options' `retry` must now also take an
2300
+ error typed `unknown`. Annotate it `unknown`, or leave the parameter to
2301
+ the context. Passing such a `retry` to `useQuery({ ...options, retry })`
2302
+ still compiles.
2303
+ - A variable inferred from the options of a read that cannot be skipped,
2304
+ given the options of one that can: `let o = client.queryOptions(c, m, v)`
2305
+ then `o = client.queryOptions(c, m, skipToken)` is now TS2322, and so is
2306
+ the same through a `useState` setter, a `push` onto an inferred array or
2307
+ a parameter typed `typeof o`. The two options are different types now.
2308
+ Build both in one expression,
2309
+ `ready ? client.queryOptions(c, m, v) : client.queryOptions(c, m, skipToken)`,
2310
+ whose union `useQuery` takes, or type the variables `V | SkipToken` so
2311
+ that both reads give the options that keep `SkipToken`.
2312
+
2313
+ Wrong variables or an unknown method name are now reported as TS2769 ("No
2314
+ overload matches this call", listing both signatures), where they were
2315
+ TS2345.
2316
+
2317
+ - Peer dependency: `@candid-core/schema` at exactly `0.3.0`, the stable
2318
+ release of the 0.3 line, where beta.1 pinned `0.3.0-beta.1`. The other peers
2319
+ are unchanged. Migration: `npm install --save-exact @candid-core/schema@0.3.0`,
2320
+ with the plugin's `@candid-core/cli@0.2.0` below. What an app meets, from
2321
+ candid-core's [0.3.0 release notes](https://github.com/b3hr4d/candid-core/blob/acabe0fcfbd8019d14e2c2550c5e6cd872a3529a/.github/release-notes/npm/schema/0.3.0.md):
2322
+ - A `rec` hop is no longer charged against the decode limits, so the
2323
+ client's `maxDepth` (default 256) counts a reply's Candid nesting alone. A
2324
+ recursive reply, such as an ICRC-3 block's `Value`, may nest deeper at the
2325
+ same `maxDepth` than before; through a `schemaFromContract()` actor, about
2326
+ 128 levels used to reach the default, and now 256 do.
2327
+ - Replies refused that were accepted, only at a limit: the `null` a reply
2328
+ gets for a field its message omits (`opt`, `null` or `reserved`) is now
2329
+ charged one depth level, at the field's own level, and one element. So
2330
+ such a field at exactly the client's `maxDepth` rejects `invalid_reply`
2331
+ with a `value_depth` issue, and a large reply whose records omit such
2332
+ fields can pass the decoder's 1,000,000-element bound, which the client
2333
+ does not change: 500,000 records that each omit one field now cost
2334
+ 1,000,001 elements and reject `invalid_reply` with a `value_elements`
2335
+ issue. A test client checks its mocks' replies at its `maxDepth` too,
2336
+ where a tag-only variant (`{ tag }`) at exactly `maxDepth` is now
2337
+ refused. At the default `maxDepth`, only a reply 256 levels deep reaches
2338
+ the depth refusals.
2339
+ - `schemaFromContract()` refuses a Contract document with a key the format
2340
+ does not define, a type node nothing reaches, or type nodes and no root,
2341
+ as candid-core's own loader does. Every document `candid-core compile`
2342
+ writes still loads. Its `ContractIssueCode` gains `unknown_key`,
2343
+ `orphan_type_node` and `rootless_type_arena`.
2344
+
2345
+ #### Documentation
2346
+
2347
+ - The guide (`llms.txt`) said the `invalidates` option of
2348
+ `client.mutationOptions()` names other reads. The list replaces the
2349
+ default (every read of the canister written to): it now says so, and to
2350
+ name the written canister too if its reads change (#829). The code is
2351
+ unchanged.
2352
+
2353
+ ### @ic-reactor/react
2354
+
2355
+ #### Documentation
2356
+
2357
+ - The README, `llms.txt` and `useClient()`'s doc comment told an app to
2358
+ ignore kind `"cancelled"` (code `"caller_changed"`) in a `QueryCache`
2359
+ `onError`, which no app can set: the client owns its `QueryClient` and
2360
+ takes no `QueryCache`. They now name the route an app has, a listener of
2361
+ `client.queryClient.getQueryCache().subscribe()`, whose `"updated"` event
2362
+ with `action.type` `"error"` carries the cancellation (#829). The code is
2363
+ unchanged.
2364
+
2365
+ ### @ic-reactor/vite-plugin
2366
+
2367
+ CI now runs its typecheck and tests at the floor of each Vite major its `vite`
2368
+ peer range accepts, 4.2.0, 5.0.0, 6.0.0, 7.0.0 and 8.0.0, where before it ran
2369
+ on the newest Vite only (#823). The range is unchanged.
2370
+
2371
+ #### Changed
2372
+
2373
+ - Peer dependency: `@candid-core/cli` at exactly `0.2.0`, the stable generator
2374
+ that pairs with `@candid-core/schema` `0.3.0`, where beta.1 pinned
2375
+ `0.2.0-beta.1`. The plugin's install hints name it; nothing else in the
2376
+ plugin changed. Migration:
2377
+ `npm install --save-dev --save-exact @candid-core/cli@0.2.0`. It writes the
2378
+ same module as 0.2.0-beta.1 for every `.did` both accept, and refuses two
2379
+ that 0.2.0-beta.1 did not: a type on a cycle through `opt` alone
2380
+ (`type T = opt T;`, `did_type_check_error`) and a run of more than 256
2381
+ consecutive comments (`resource_limit_exceeded`). The plugin reports either
2382
+ as that canister's generation failure. See candid-core's
2383
+ [0.2.0 release notes](https://github.com/b3hr4d/candid-core/blob/acabe0fcfbd8019d14e2c2550c5e6cd872a3529a/.github/release-notes/npm/cli/0.2.0.md).
2384
+
2385
+ ### Examples, e2e and evals (not published)
2386
+
2387
+ - The four examples pin `^4.0.0-beta.2`, and three use what it adds:
2388
+ `node-agent-tool`'s mock ledger throttles exactly `icrc1_transfer` with the
2389
+ aimed `refuseNext` instead of counting the reads before it, the
2390
+ `icrc-ledger` sandbox's HTTP 429 faults hit only the transfer, and
2391
+ `next-ssr`'s balance route answers a missing canister with a 502 that
2392
+ carries `code: "canister_not_found"` (#821, #822).
2393
+ - A new script and workflow run each example the way someone who copies it
2394
+ out of the repository does, and as StackBlitz does: a plain `npm install`
2395
+ of the `@ic-reactor/*` beta published on npm, then its typecheck, tests,
2396
+ build and StackBlitz start command. The release workflow runs it once the
2397
+ packages are published (#788). Each example has a `.stackblitzrc`, and
2398
+ `jsdom` is pinned to `30.0.1`.
2399
+ - `vite-wallet`'s and `icrc-ledger`'s transfers carry a memo of each
2400
+ transfer's own, kept for its re-send, so two transfers of the same amount
2401
+ in the same millisecond are never taken for each other's `Duplicate`; their
2402
+ test ledgers deduplicate on the whole argument, memo included (#833, #838).
2403
+ - The e2e suite is rewritten for 4: twelve cases on an icp-cli 1.2.0 local
2404
+ network, over a `hello_actor` module generated by `candid-core-cli gen`
2405
+ (#787).
2406
+ - The examples and the e2e suite pin `@candid-core/schema` `0.3.0` and
2407
+ `@candid-core/cli` `0.2.0`. Every module the examples, core's tests and the e2e
2408
+ suite generate is byte-identical under 0.2.0.
2409
+ - The eval harness can build the `v4` condition from the published
2410
+ 4.0.0-beta.1 tarballs, and the GA eval (Addendum 4 of
2411
+ `evals/PREREGISTRATION.md`) ran on the published 4.0.0-beta.1 (#790). GA
2412
+ ships on the result of Addendum 4, stated as recorded: rule 1 was not met as
2413
+ written, because the leak audit flagged react-wallet/`v4`#14's read of its
2414
+ own persisted tool output; all 80 runs were safe, and the pooled bound held
2415
+ in both analyses (−0.0897 main, −0.0876 intent-to-treat, against −0.10).
2416
+
2417
+ ## core, react, vite-plugin 4.0.0-beta.1
2418
+
2419
+ The first prerelease of ic-reactor 4 (milestone 1, #790), published under
2420
+ npm's `beta` dist-tag: install it as `@ic-reactor/core@beta`. `latest` stays
2421
+ 3.x until 4.0 GA. Changes since core and react 3.13.0 and vite-plugin 0.15.1.
2422
+
2423
+ ic-reactor 4 is a breaking rewrite, not an upgrade of 3.x. It is a thin layer
2424
+ over the module `candid-core-cli gen` writes from a `.did` file, plus one
2425
+ guide, `node_modules/@ic-reactor/core/llms.txt`, shipped in core's tarball.
2426
+ An app calls canisters through a client from `createClient` and reads and
2427
+ writes them with TanStack Query's own hooks and the options the client
2428
+ builds. There are no reactors, hook factories or generated hooks. The three
2429
+ packages release together, at one version.
2430
+
2431
+ 3.x stays on `main` (security fixes only until 4.0 GA) and is documented at
2432
+ https://ic-reactor.b3pay.net/v3/. Migration: there is no in-place upgrade;
2433
+ generate each canister's module with `candid-core-cli gen` and port the calls
2434
+ to the client by the guide. The table of every removed name and what replaces
2435
+ it comes with the 4 docs (#789).
2436
+
2437
+ ### @ic-reactor/core
2438
+
2439
+ #### Added
2440
+
2441
+ - `createClient(options)`: one client per browser tab, or per request on a
2442
+ server. It takes a `network` and exactly one caller: `identity` (an
2443
+ `Identity`, or `"anonymous"` for a read-only client) or `auth` (a factory
2444
+ of an `AuthLike`, such as `() => new AuthClient()` from `@icp-sdk/auth` 10,
2445
+ called once and only in a browser). `allowEnvConfig`, `fetch` and
2446
+ `maxDepth` (default 256) are optional. The client owns a TanStack
2447
+ `QueryClient` (`client.queryClient`) that dehydrates and hydrates `bigint`,
2448
+ `Uint8Array` and non-finite floats without loss.
2449
+ - `network`: `"ic"` (mainnet and its built-in root key), `"local"`
2450
+ (`http://127.0.0.1:4943`, root key fetched), `"env"` (the page's network,
2451
+ with canisters named `{ name }` through the `ic_env` cookie), or
2452
+ `{ host, rootKey?, name?, fetchRootKey? }`. A given `rootKey` is used and
2453
+ never fetched; without one, a root key is fetched only from a loopback or
2454
+ `localhost` host, unless `fetchRootKey: true` is written out.
2455
+ - `client.canister<Actor>(actor, { id } | { name })`: a frozen object of plain
2456
+ async methods, one per Candid method, typed from the generated `Actor` and
2457
+ memoized, so it can be called in render. A method whose result is
2458
+ `variant { Ok; Err }` resolves with the `Ok` value and rejects the `Err` as
2459
+ kind `canister_err`. `{ id, certified: true }` sends `query` methods through
2460
+ the certified path. A call to the management canister (`aaaaa-aa`) takes
2461
+ its effective canister id from its arguments.
2462
+ - `client.queryOptions(canister, method, args | skipToken)`,
2463
+ `client.mutationOptions(canister, method, { invalidates? })` and
2464
+ `client.queryKey(canister, method?, args?)`, the options and keys for
2465
+ TanStack Query. `queryOptions` throws for an update method unless its
2466
+ fourth argument is `{ update: "idempotent" }`.
2467
+ - `client.func<F>(funcSchema, ref)` calls a func reference a reply carried,
2468
+ such as an ICRC ledger's archive callback.
2469
+ - `client.caller()`, `client.authState()`, `client.subscribe(fn)`,
2470
+ `client.signIn()`, `client.signOut()` and `client.dispose()`. Every call
2471
+ made after `dispose()`, writes included, rejects `cancelled` with code
2472
+ `client_disposed` and sends nothing (#818).
2473
+ - `isReactorError(error)` and the types `ReactorError` and
2474
+ `ReactorErrorKind`. Every rejection of a client call is a `ReactorError`
2475
+ with one of eight kinds (`invalid_args`, `unauthenticated`,
2476
+ `not_delivered`, `outcome_unknown`, `rejected`, `invalid_reply`,
2477
+ `canister_err`, `cancelled`) and a `mayHaveExecuted` boolean, which a
2478
+ classifier sets from the reject code, the HTTP status and whether the call
2479
+ was an update. On the management canister, reject codes 1 to 3 do not prove
2480
+ that a write had no effect.
2481
+ - `parseUnits(text, decimals, { signed? })` and
2482
+ `formatUnits(value, decimals, { maxFractionDigits?, minFractionDigits? })`
2483
+ convert token amounts exactly, in `bigint`. `parseUnits` throws for text
2484
+ that is not a plain decimal, for a negative unless `signed`, and for excess
2485
+ fraction digits.
2486
+ - The types `Client`, `ClientOptions`, `Network`, `AuthLike`, `AuthState`,
2487
+ `Canister` and `CanisterTarget`. With the names above, the entry exports 13.
2488
+ - `@ic-reactor/core/testing` exports `createTestClient` and `TestHandlers`: a
2489
+ real client over an in-memory replica that signs and verifies as a replica
2490
+ does, passed in through the client's `fetch` (nothing global is stubbed),
2491
+ with canisters written as functions of Candid values and a sign-in the test
2492
+ controls.
2493
+
2494
+ #### Changed
2495
+
2496
+ - The client keeps one immutable agent per principal and never replaces an
2497
+ agent's identity. A call goes out as the principal current when it was
2498
+ made. A read made for one caller that settles after a sign-in, sign-out or
2499
+ switch rejects `cancelled` instead of landing in the new caller's cache.
2500
+ - Query keys name the caller:
2501
+ `["ic-reactor", network, caller, canisterId, method, args]`, so one
2502
+ principal's cached answer is never served to another. Build keys with
2503
+ `client.queryKey`, never by hand.
2504
+ - An update is sent again only on proof that the canister never received it
2505
+ (reject code 2 or HTTP 429), at most twice, inside the call; any other
2506
+ failure is final. A read retries at most 3 times, only a `not_delivered`
2507
+ failure, and never on a server.
2508
+ - A mutation is never retried: `mutationOptions` returns `retry: false`. By
2509
+ default it invalidates its canister's reads, for every caller, after a
2510
+ success, a `canister_err`, or a failure that may have executed.
2511
+ - A write is refused before it is sent (`unauthenticated`) while nobody is
2512
+ signed in, and always on a client built with `identity: "anonymous"`.
2513
+ - Values are candid-core's: a principal is branded text (`principal()` and
2514
+ `isPrincipal()` from `@candid-core/schema`), `opt T` is `T | null`, a
2515
+ variant is `{ tag, value }` and every `blob` is a `Uint8Array`.
2516
+ - Peer dependencies: `@candid-core/schema` at exactly `0.3.0-beta.1` (new:
2517
+ install it with `--save-exact`), `@tanstack/query-core` `^5.62.0` (no longer
2518
+ optional), `@icp-sdk/core` `^6.1.0`, and `@noble/curves` `^2.2.0`, still
2519
+ optional and used only by `./testing`. `zod` is no longer a dependency.
2520
+ Migration: `npm install --save-exact @candid-core/schema@0.3.0-beta.1`.
2521
+
2522
+ #### Removed
2523
+
2524
+ - The 3.x runtime: `ClientManager`, `Reactor`, `DisplayReactor`, the display
2525
+ codecs and zod validation, the 3.x errors (`CanisterError`, `CallError`), the key and
2526
+ retry helpers (`generateKey`, `reactorRetry`), `formatTokenAmount`,
2527
+ `parseTokenAmount` and `isPrincipalText`, and the 3.x test helpers
2528
+ (`installFakeReplica`, `createTestCanister`). They stay on the 3.x line.
2529
+
2530
+ ### @ic-reactor/react
2531
+
2532
+ #### Added
2533
+
2534
+ - `ReactorProvider({ client: () => Client, children })` calls its factory
2535
+ once per mounted tree, renders TanStack Query's `QueryClientProvider` with
2536
+ the client's `QueryClient`, and disposes a client its factory created when
2537
+ it unmounts (`StrictMode`-safe). A client the app owns, passed as
2538
+ `client={() => client}`, is borrowed and never disposed.
2539
+ - `useClient()` returns the nearest provider's client, and throws outside one.
2540
+ Its component renders again whenever the caller's principal changes, and
2541
+ the keys it builds are the caller's that the render shows: on a server and
2542
+ while a page hydrates, the anonymous caller's. So a tab that holds a
2543
+ session hydrates from the keys the server prefetched, with no mismatch and
2544
+ nothing read again, then reads the session's keys (#813).
2545
+ - `useAuth()` returns `{ status, principal, signIn, signOut }`. `status` is
2546
+ `"anonymous"`, `"signed-in"`, `"expired"` or `"signed-in-elsewhere"`; it is
2547
+ `"anonymous"` on a server and in a hydrating page's first render.
2548
+ - Known limits of a signed-in reload, described in the README's "Keys follow
2549
+ the caller a render shows": a Suspense boundary still hydrating below a
2550
+ component that renders with the caller is rendered on the client when that
2551
+ component moves to the session (pass such a boundary as `children`), and a
2552
+ component that reads with `useSuspenseQuery` needs a Suspense boundary
2553
+ above it.
2554
+ - The type `ReactorProviderProps`. The entry exports these 4 names and never
2555
+ re-exports `@ic-reactor/core`.
2556
+
2557
+ #### Changed
2558
+
2559
+ - `@ic-reactor/core` is a peer dependency at exactly this package's version
2560
+ (it was a dependency). The other peers are `@tanstack/react-query`
2561
+ `^5.90.2` and `react` `>=18.0.0`; `@icp-sdk/auth`, `@icp-sdk/core`,
2562
+ `@noble/curves` and `react-dom` are no longer peers.
2563
+
2564
+ #### Removed
2565
+
2566
+ - Every 3.x hook and factory: `defineReactor`, `createActorHooks`,
2567
+ `createReactorProvider`, `createQuery`, `createMutation`, the suspense and
2568
+ infinite variants, `createAuthHooks`, the identity-attribute hooks and the
2569
+ re-exported `skipToken`; and the auth classes behind them,
2570
+ `AuthenticationManager` and `IdentityAttributesManager`. Sign in with an
2571
+ `AuthLike` passed to `createClient` and `useAuth()`. Read and write with TanStack Query's `useQuery`
2572
+ and `useMutation` over `client.queryOptions` and `client.mutationOptions`.
2573
+ - The `react-server` entry and `@ic-reactor/react/testing`.
2574
+
2575
+ ### @ic-reactor/vite-plugin
2576
+
2577
+ The plugin joins the lane of core and react, at their version.
2578
+
2579
+ #### Changed
2580
+
2581
+ - Generation runs `candid-core-cli gen` from the app's own `@candid-core/cli`,
2582
+ in a child process per `.did` file, so a generator failure stops that
2583
+ process and not the dev server. It writes candid-core's module (named after
2584
+ the `.did` file, exporting `actor` and the type `Actor`) and its
2585
+ `.envelope.json` into `outDir`, by default `src/canisters`, and nothing
2586
+ else.
2587
+ - `canisters` is a record keyed by canister name,
2588
+ `{ didFile?, outDir?, canisterId? }`, instead of an array of entries.
2589
+ `injectEnvironment` and `failOnError` work as before. The first line the
2590
+ plugin logs names `node_modules/@ic-reactor/core/llms.txt`. Migration:
2591
+ `canisters: [{ name: "ledger", didFile }]` becomes
2592
+ `canisters: { ledger: { didFile } }`.
2593
+ - `@candid-core/cli` is a peer dependency at exactly `0.2.0-beta.1`, the
2594
+ generator that pairs with `@candid-core/schema` `0.3.0-beta.1`. The `vite`
2595
+ range is unchanged. Migration:
2596
+ `npm install --save-dev --save-exact @candid-core/cli@0.2.0-beta.1`.
2597
+
2598
+ #### Removed
2599
+
2600
+ - Generation through `@ic-reactor/codegen`, which is no longer a dependency:
2601
+ the generated reactor and hook files (`index.generated.ts`,
2602
+ `index.factories.generated.ts`, `index.ts`) and the options that shaped
2603
+ them (the top-level `outDir`, `clientManagerPath` and `target`, and each
2604
+ entry's `name`, `mode`, `target` and `factories`).
2605
+
2606
+ ## core, react, candid 3.13.0
2607
+
2608
+ Changes since core, react and candid 3.12.5.
2609
+
2610
+ ### @ic-reactor/core
2611
+
2612
+ #### Added
2613
+
2614
+ - `reactor.forCanister(canisterId)` returns a memoized sibling reactor for
2615
+ another canister of the same interface. It has the same class, `ClientManager`
2616
+ and validators, and its calls and query keys use its own canister. Use it
2617
+ instead of `setCanisterId` or one hand-built reactor per canister. A
2618
+ subclass's constructor must build on the `canisterId` it is given;
2619
+ `forCanister` throws when the new reactor lands on another canister.
2620
+ - `ServiceOf`, `TransformOf`, `ReactorArgsOf`, `ReactorDataOf` and
2621
+ `ReactorErrorOf` read a method's types off `typeof reactor`.
2622
+ - `formatTokenAmount(value, decimals, options?)` and
2623
+ `parseTokenAmount(text, decimals)` convert ledger base units exactly.
2624
+ `isPrincipalText(text)` validates typed principal text.
2625
+ - `clientManager.fetchAcrossIdentitySwitch(fetch)` runs a hand-written
2626
+ `queryClient.fetchQuery` / `fetchInfiniteQuery` again when a sign-in or
2627
+ sign-out switches the principal while it runs (#647).
2628
+ - `clientManager.explicitRootKey` reports a root key the app passed as
2629
+ `agentOptions.rootKey` (#713).
2630
+ - `isRetryableUpdateError` and `reactorUpdateRetry` retry an update call only
2631
+ after a `SysTransient` rejection, and `Reactor.getQueryRetry()` gives the
2632
+ retry a query of a method falls back to (#622).
2633
+ - `Reactor.fetchQuery(params, options?)` takes a second argument of TanStack
2634
+ Query fetch options (`retry`, `retryDelay`, `networkMode`, `meta`, ...); the
2635
+ key and query function still come from `params`. The query factories pass
2636
+ their config's options through it, so a subclass that overrides `fetchQuery`
2637
+ still sees their fetches (#502).
2638
+ - `fromZodSchema(schema, { async: true })` accepts zod schemas with async
2639
+ refinements (#593).
2640
+ - `createPollingStrategy({ timeoutMs })`, 5 minutes per request by default
2641
+ (#563).
2642
+ - `@ic-reactor/core/testing` exports `installFakeReplica` and
2643
+ `createTestCanister`. The fake replica signs certificates and checks request
2644
+ signatures, so tests run the real `Reactor` and hooks. `@noble/curves` is a
2645
+ new optional peer dependency, used only by this entry.
2646
+
2647
+ #### Changed
2648
+
2649
+ - `Reactor.invalidateQueries()` returns TanStack Query's `Promise<void>`, which
2650
+ resolves once the active matches have refetched. Migration: `await` it, or
2651
+ prefix a call you do not wait for with `void`.
2652
+ - `reactorRetry` no longer retries an HTTP 4xx other than 408 and 429, such as
2653
+ an expired delegation's 400 (#646). Migration: none; these errors now show
2654
+ after the agent's own attempts instead of about 20 seconds later.
2655
+ - A query of an update method that sets no `retry` retries only a
2656
+ `SysTransient` rejection, within the QueryClient's retry default (#622).
2657
+ Migration: call state-changing methods through a mutation.
2658
+ - `Reactor.fetchQuery()`, and so every query factory's `fetch()`, fetches again
2659
+ as the new principal when a sign-in or sign-out cancels it, instead of
2660
+ rejecting with TanStack's `CancelledError` (#647). When the first run and
2661
+ three runs again are each overtaken by a switch, it rejects with a
2662
+ `CallError`. Migration: remove `CancelledError` handling from loaders.
2663
+ - `ClientManager.updateAgent()` keeps the cache when the new identity has the
2664
+ principal already installed, as a renewed delegation or a repeated sign-in
2665
+ does. Only entries that failed, or whose fetch in flight then fails, refetch
2666
+ (#719). Migration: after installing an identity that changes what canisters
2667
+ see under the same principal, such as an `AttributesIdentity`, invalidate the
2668
+ affected queries yourself.
2669
+ - Codespaces (`*.github.dev`) and Gitpod (`*.gitpod.io`) pages without an
2670
+ `agentOptions.host` route through their own origin with network `"remote"`
2671
+ instead of `https://ic0.app`, and their `ic_env` cookie is no longer trusted
2672
+ (#643). Migration: pass `allowEnvConfig: true` to read the cookie on those
2673
+ hosts.
2674
+ - In a development build, `ClientManager` skips query signature verification by
2675
+ default only for a local replica or a Codespaces or Gitpod tunnel to one. A
2676
+ dev page whose agent points at any other host, such as mainnet's
2677
+ `https://icp-api.io`, now verifies signatures, as a production build does,
2678
+ and a web worker follows the same default as its page. Migration: none; an
2679
+ explicit `verifyQuerySignatures` still wins.
2680
+ - On a local host, `initialize()` keeps a root key passed as
2681
+ `agentOptions.rootKey` instead of replacing it with the replica's (#713).
2682
+ Migration: pass the replica's key, or no key.
2683
+ - The args segment of a query key is no longer `JSON.stringify(args)`.
2684
+ `generateKey` sorts plain-object keys and writes `NaN`, `±Infinity` and `-0`
2685
+ behind a U+0000 tag. `generateQueryKey` also writes a blob as tagged hex,
2686
+ gives a `DisplayReactor` value one form whichever form was passed, and tags an
2687
+ argument the reactor would refuse (#515, #761, #762, #765). A query sent
2688
+ through `callConfig.agent`, when that is not the manager's own agent, is keyed
2689
+ with an `{ agent: n }` segment (#642). Migration: build keys with
2690
+ `reactor.generateQueryKey(...)` (or `generateKey`), never by hand or with
2691
+ `JSON.stringify`.
2692
+ - A validator that throws or rejects is reported as a `CallError`
2693
+ ("Failed to validate the arguments of <method>") with the thrown value as
2694
+ `cause`, from `callMethod`, `callMethodWithValidation` and `validate` alike.
2695
+ A thrown `ValidationError` passes through unchanged (#593). Migration: read
2696
+ `error.cause` where code caught the raw error.
2697
+ - `CanisterError.details` is typed as the error value's own `details` field
2698
+ (`CanisterErrorDetails<E>`), e.g. `[] | [Array<[string, string]>]` from a
2699
+ `Reactor`, instead of `Map<string, string>`, which Candid decoding never
2700
+ returns; runtime values are unchanged (#685). `CanisterError` reads an
2701
+ `opt text` message of an API-shaped error. `ApiError`'s `message` and
2702
+ `details` are type parameters that default to `unknown`, and
2703
+ `CanisterError.isApiError()` narrows them to `unknown` (#690). Migration: read
2704
+ `details` in the shape the canister returns it, not as a `Map`, and name the
2705
+ payload types, e.g. `ApiError<[] | [string], [] | [Array<[string, string]>]>`.
2706
+ - `createPollingStrategy` keeps its attempt count, clock and timeout per
2707
+ request, so one instance can serve a whole reactor, and gives up after
2708
+ `timeoutMs` (#563). Migration: `timeoutMs: Infinity` restores polling without
2709
+ a limit.
2710
+ - `DisplayReactor` sends `NaN`, `±Infinity` and `-0` floats back as it read
2711
+ them, instead of refusing a non-finite float (#632). Migration: none; text
2712
+ from a form must still spell a finite number.
2713
+ - `DisplayReactor` displays `opt opt T`'s `some(none)` as `null` and none as
2714
+ `undefined`, so a cleared value differs from one never set, as Internet
2715
+ Identity's and Orbit's config types need (#486). Encoding is unchanged: `null`
2716
+ and `undefined` both send none, and `[null]` sends `some(none)`. Migration: to
2717
+ tell none from `some(none)`, check `=== undefined` and `=== null`; `== null`
2718
+ still covers both.
2719
+ - `hexToUint8Array`, and so a `DisplayReactor` blob argument, refuses hex with
2720
+ an odd number of digits instead of padding it with a leading zero. Padding
2721
+ shifted every byte, so a subaccount or memo that lost its last digit was sent
2722
+ as different, valid-looking bytes. Migration: pass two hex digits per byte.
2723
+ - `jsonToString` writes a `Principal` as its text and a `Uint8Array` as hex,
2724
+ instead of `{"__principal__": ...}` and an object keyed by index. Migration:
2725
+ drop a custom replacer that did this, and update code that parsed the old
2726
+ output.
2727
+
2728
+ #### Fixed
2729
+
2730
+ - Update calls: a v4 response whose certificate has no status for the request
2731
+ now falls back to polling. It used to fail with a `CallError` for a call that
2732
+ could still commit, which invited a retry that ran a transfer twice (#508). An
2733
+ anonymous call submitted before the first sign-in keeps polling as anonymous,
2734
+ so a sign-in during its polling no longer reports a committed call as failed.
2735
+ `pollingOptions.blsVerify` now also checks synchronous (v4) responses, not
2736
+ only polled ones.
2737
+ - On a local replica with no root key supplied, the agent fetches the replica's
2738
+ key before its first request. A call made before `initialize()` finished no
2739
+ longer fails certificate verification, including an update call that had
2740
+ already run on the replica when it was reported as failed.
2741
+ - `ClientManager` host detection: every loopback address (`127.0.0.0/8`,
2742
+ `[::1]`) counts as local (#512). A host without a scheme, such as
2743
+ `127.0.0.1:4943`, is read the way `HttpAgent` reads it instead of as mainnet.
2744
+ A web worker routes like the page that started it instead of falling back to
2745
+ `https://ic0.app`. Construction no longer throws in React Native or on a page
2746
+ with an opaque origin such as `file://` or `about:blank` (#510).
2747
+ - `ClientManager` resolves `agentOptions` into a copy. A frozen options object
2748
+ no longer throws. One options object shared by two managers no longer carries
2749
+ the first manager's resolved settings, such as a root key from the `ic_env`
2750
+ cookie, into the second (#516).
2751
+ - `ClientManager` subscriptions: calling the unsubscribe of a callback
2752
+ registered twice removes only that registration (#513). A subscriber that
2753
+ throws no longer stops later subscribers from being notified. A change made
2754
+ from inside a callback is no longer followed by the older value.
2755
+ - `initializeAgent()` recovers when an agent-state subscriber throws. The failed
2756
+ attempt clears `isInitializing` and `isInitialized`, so the next call starts a
2757
+ fresh attempt instead of returning the old rejection or staying in error for
2758
+ the rest of the session. A subscriber that calls `initializeAgent()` on the
2759
+ `initializing` notification now waits for that attempt (#514).
2760
+ - A sign-in or sign-out sweeps the query cache once instead of once per
2761
+ registered canister. With 1,000 canisters the sweep takes 0.7 ms instead of
2762
+ 166 ms.
2763
+ - Query keys follow the Candid value a call sends, so one call is no longer
2764
+ answered from another's cache entry:
2765
+ - records with the same fields in any order share a key, while `NaN`,
2766
+ `±Infinity` and `-0` no longer share one (#515);
2767
+ - a blob has one key whether it is passed as a `Uint8Array`, a `number[]` or
2768
+ hex text, and a 1 MB blob's key segment shrinks from 13.1 MB to 2.1 MB;
2769
+ - a `DisplayReactor` map object, bare or wrapped `opt`, or variant with or
2770
+ without `_type` is keyed by what it sends (#761, #762), and so is a record
2771
+ or variant given as a class instance or with a non-enumerable field (#768);
2772
+ - an argument the reactor refuses, such as `undefined` for `null` or a bigint
2773
+ for `text`, fails instead of returning a valid call's cached result (#765).
2774
+ - A query sent through `callConfig.agent` (another identity or network) gets a
2775
+ cache entry of its own. It used to share the entry of the same query through
2776
+ the manager's agent, so whichever ran first answered both, such as a
2777
+ `whoami` for the wrong principal (#642).
2778
+ - After `setCanisterId`, a retry or refetch of an existing query fetches the
2779
+ canister its key names. It used to cache the new canister's answer under the
2780
+ old canister's key (#509).
2781
+ - `invalidateQueries()` without a `functionName` targets `callConfig.canisterId`
2782
+ when one is given. With params but no method, it now matches the canister's
2783
+ queries instead of none (#511).
2784
+ - Only a Result variant is unwrapped. A record with an `ok` or `err` field, or
2785
+ with its own `_type` field beside one, is returned whole instead of resolving
2786
+ to that field or throwing a `CanisterError` on success. `OkResult` and
2787
+ `ErrResult` type it the same way (#517).
2788
+ - `JSON.stringify` of a `CanisterError`, `CallError` or `ValidationError` that
2789
+ holds BigInts, such as any ICRC-1 transfer error, no longer throws. Each class
2790
+ has a `toJSON` that writes BigInts as decimal strings and hands a replacer the
2791
+ error's own values (#518).
2792
+ - `isCanisterError`, `isCallError` and `isValidationError` recognise errors
2793
+ thrown by another copy of `@ic-reactor/core` loaded in the same app, so a
2794
+ mutation's `onCanisterError` runs and `reactorRetry` retries their transport
2795
+ failures. Such a `ValidationError` or `CanisterError` also keeps its type
2796
+ through `callMethod`, `callMethodWithValidation` and `validate` (#593).
2797
+ - Validation: a validator for a method that takes no arguments now runs when
2798
+ `args` is omitted. When `callMethod()` refuses an async validator, it no
2799
+ longer leaves an unhandled rejection. `ValidationError.getIssuesForPath` and
2800
+ `hasErrorForPath` find whole-argument issues under `""` and accept a numeric
2801
+ array index, matching `1` and `"1"` alike (#592).
2802
+ - Methods typed by a recursive func alias (`type f = func (f) -> (f)`) work.
2803
+ `callMethod`, `fetchQuery` and `isQueryMethod` no longer fail on them (#608).
2804
+ `DisplayReactor` builds their codecs instead of dropping the codecs of that
2805
+ method and every method after it (#557).
2806
+ - The display types of a type that contains itself through `opt`, `vec`, a tuple
2807
+ or a map, such as Motoko's `List`, now resolve. They used to fail with TS2589
2808
+ in every hook and call on that method (#566).
2809
+ - `DisplayReactor` arguments:
2810
+ - a one-element `opt` value is no longer mistaken for the `[value]` wrapper,
2811
+ so a value the codec decoded encodes back. This fixes the cycles ledger's
2812
+ `opt vec record { Account; nat }` with one balance, `opt vec vec T`,
2813
+ `opt vec blob` given `[bytes]`, `opt opt vec text`, and a text-keyed map
2814
+ passed in wrapper form (#484);
2815
+ - a record field or variant arm named like an `Object.prototype` member
2816
+ (`constructor`, `toString`, ...) is treated as absent when it is left out,
2817
+ even for a value from another realm, instead of sending the inherited
2818
+ function. `getVariantValue` and `getVariantKeyValue` no longer return the
2819
+ inherited function either (#483);
2820
+ - a `Map` given for a `vec record { text; T }` is sent with its entries
2821
+ instead of as an empty vector (#767).
2822
+ - `DisplayReactor` on large recursive values: a recursive type's codec is built
2823
+ once instead of for every node, so 1,000 ICRC-3 blocks display in about 16 ms
2824
+ instead of 470 ms (#485). Long lists such as a Motoko `List` display and
2825
+ encode up to 1,875 elements. They used to fail past about 586 elements and
2826
+ return the raw Candid value.
2827
+ - Under Deno, `reactorRetry` retries nothing, as on any other server and as
2828
+ TanStack Query's own default does. Deno can define `window`, so a failed
2829
+ call during Deno server rendering was retried three times with backoff.
2830
+ `reactorUpdateRetry` and an update method's query retry follow the same
2831
+ rule.
2832
+ - Docs corrected to match the package. `Reactor.fetchQuery()` is cache-first and
2833
+ returns a stale or invalidated entry as is (#595). An empty `opt` displays as
2834
+ `undefined` (#589). The packages need TypeScript 5.7 or later, which their
2835
+ declarations already required (#591).
2836
+
2837
+ ### @ic-reactor/react
2838
+
2839
+ #### Added
2840
+
2841
+ - `defineDisplayReactor(options)` takes `defineReactor`'s options and builds a
2842
+ `DisplayReactor` (#746).
2843
+ - `createReactorProvider(factory, options?)` returns
2844
+ `{ ReactorProvider, useReactor }`. It builds the reactors once per mounted
2845
+ provider, so once per request on a server, and disposes the
2846
+ `AuthenticationManager`s built for its value on unmount (#745). When the
2847
+ value holds one `QueryClient`, it also renders a `QueryClientProvider` for
2848
+ it; `queryClientProvider: false` opts out.
2849
+ - A `react-server` export condition. React Server Components, server actions
2850
+ and route handlers import the core runtime (`Reactor`, `DisplayReactor`,
2851
+ `ClientManager`, the token helpers) from `@ic-reactor/react`; hooks and
2852
+ factories are missing exports there (#679).
2853
+ - `skipToken`, re-exported from TanStack Query, in place of args in
2854
+ `useActorQuery`, `useActorInfiniteQuery` (`getArgs`) and `createQueryFactory`.
2855
+ - `invalidateQueries` on `createMutation`, `.useMutation()`,
2856
+ `useActorMutation` and `useActorMethod` takes query objects, query factories
2857
+ and typed `{ functionName, args? }` descriptors. Query factory functions gain
2858
+ `getQueryKey()` and `invalidate()`. New types: `InvalidationTarget`,
2859
+ `QueryKeySource`, `QueryDescriptor`, `QueryFactoryFn`, `QueryFactoryMethods`.
2860
+ - Query objects, those of the infinite factories included, gain `cancel()`,
2861
+ `reset()` and `optimisticUpdate(updater)`, which resolves with
2862
+ `{ rollback() }`.
2863
+ - `callConfig` in `createQuery`, `createSuspenseQuery` and their factories.
2864
+ - `AuthenticationManager.dispose()` releases the auth client the manager built
2865
+ (#745).
2866
+ - Support for `@icp-sdk/auth` v10 alongside v8. The peer range is
2867
+ `^8.0.0 || ^10.0.0` (v9 is excluded), so npm installs the set without an
2868
+ `overrides` block. `identityProvider` is still a URL. On v10 a custom provider
2869
+ also needs `internetIdentityId`, the canister that mints its delegations, or
2870
+ `login()` throws an error that names the option. Off mainnet, v10's minting
2871
+ agent is given the app's replica host and root key, so local sign-in works.
2872
+ - Two options that only v10 uses: `maxTimeToIdle` for `login()` and
2873
+ `IdentityAttributesManager.request()`, and `disableBrowserActivity` for the
2874
+ auth client. v8 drops both with a one-time warning. On v10, `storage`,
2875
+ `keyType`, `idleOptions` and `identity` are dropped with a one-time warning,
2876
+ and `login({ targets })` warns on every sign-in because v10 ignores `targets`
2877
+ and returns a broader delegation.
2878
+ - `@ic-reactor/react/testing` re-exports `@ic-reactor/core/testing`.
2879
+ `@noble/curves` is a new optional peer dependency here too, used only by
2880
+ this entry.
2881
+
2882
+ #### Changed
2883
+
2884
+ - The `@tanstack/react-query` peer is now `^5.90.2` (was `^5.0.0`); older
2885
+ releases fail to compile against the package or fail its tests (#475).
2886
+ Migration: upgrade `@tanstack/react-query` to 5.90.2 or later.
2887
+ - Every query and mutation hook (raw, bound and factory-made) mounts its
2888
+ reactor's `QueryClient` while it is mounted, as `QueryClientProvider` does,
2889
+ and a suspense hook also while it is suspended. Without a provider,
2890
+ `refetchOnWindowFocus` and `refetchOnReconnect` now fire, and a query or
2891
+ mutation paused while offline resumes when the connection returns (#498).
2892
+ Migration: if the app relied on no refetch on focus or reconnect, set
2893
+ `refetchOnWindowFocus` / `refetchOnReconnect` to `false` on the query or in
2894
+ the QueryClient's defaults.
2895
+ - `useAuth()` and `useUserPrincipal()` report `isAuthenticating: true` until
2896
+ the first session restore settles, in server renders too (#621). Migration:
2897
+ check `isAuthenticating` before redirecting on `!isAuthenticated`.
2898
+ - `createMutation(...).execute()` runs in the QueryClient's MutationCache: the
2899
+ cache's global callbacks, `useIsMutating`, the factory's `retry`,
2900
+ `networkMode`, `onMutate` and `onSettled`, and the QueryClient's mutation
2901
+ defaults now apply to it (#564). Migration: a `mutations.retry` default now
2902
+ retries `execute()` too; use `reactorUpdateRetry` for update methods.
2903
+ - `useActorMethod` awaits `invalidateQueries` before `onSuccess`, and `call()`
2904
+ resolves after the invalidated queries have refetched (#564). Migration:
2905
+ none; `onSuccess` now reads fresh data.
2906
+ - `useActorMethod` applies its `retry`, `retryDelay`, `networkMode` and `meta`
2907
+ to `call()`, for an update method as for a query method's `call(args)` (#564).
2908
+ Migration: replace a numeric `retry` on a hook that calls an update method
2909
+ with `reactorUpdateRetry`.
2910
+ - A query method's `call()` and `refetch()` from `useActorMethod` fetch again
2911
+ for the new principal when a sign-in or sign-out lands mid-call, and resolve
2912
+ with that answer instead of the previous principal's cached data or
2913
+ TanStack's `CancelledError`. When their fetch fails they resolve `undefined`
2914
+ (and report the error to `onError`) instead of the entry's last answer.
2915
+ Migration: treat an `undefined` result as a failed call.
2916
+ - A query object's `prefetch()` fetches again for the new principal when a
2917
+ sign-in or sign-out overtakes it, so the cache never keeps the previous
2918
+ principal's answer. Migration: none.
2919
+ - On `@icp-sdk/auth` v10, `AuthenticationManager` follows sign-outs and account
2920
+ switches made in other tabs and updates the agent (#754). Migration: none;
2921
+ expect `useAuth()` to change when another tab signs out.
2922
+ - `authentication.logout()` on a manager with no client builds one instead of
2923
+ throwing "Authentication module is missing". Migration: remove a `catch`
2924
+ for that error.
2925
+
2926
+ #### Deprecated
2927
+
2928
+ - `defineReactor({ display: true })`, its overload and
2929
+ `DefineDisplayReactorParameters`. Use `defineDisplayReactor(...)`. They will
2930
+ be removed at the next major. Plain `defineReactor` is not deprecated.
2931
+
2932
+ #### Fixed
2933
+
2934
+ - `useActorMethod`:
2935
+ - `call`, `refetch` and `reset` keep one identity for the component's
2936
+ lifetime. An effect that lists `call` no longer runs again after every
2937
+ render, which could call an update method endlessly. They also run with the
2938
+ last committed render's method, args and callbacks (#497);
2939
+ - `call(args)` with the hook's own args runs `onSuccess` / `onError` once
2940
+ instead of twice. A call that settles in the same millisecond as the
2941
+ previous result is still reported (#497);
2942
+ - `onSuccess` no longer runs for `placeholderData`, such as `keepPreviousData`
2943
+ (#501), or for `initialData` that no call returned (#564);
2944
+ - an `undefined` entry of `invalidateQueries` is skipped, as the mutation
2945
+ hooks skip it, instead of invalidating every query in the client.
2946
+ - Hooks and factories:
2947
+ - `useActorInfiniteQuery` and `useActorSuspenseInfiniteQuery` apply
2948
+ `getKeyArgs` as the factories do. An `initialPageParam` that changes on
2949
+ every render, such as `Date.now()`, no longer refetches the first page
2950
+ endlessly (#499);
2951
+ - infinite queries, both hooks and factories, fetch their pages from the
2952
+ canister their key names. Before, a later `setCanisterId` redirected the
2953
+ fetch (#558);
2954
+ - a query object's `fetch()` and `prefetch()`, and the infinite factories'
2955
+ `fetch()`, use the config's `networkMode`, `retry`, `retryDelay` and `meta`,
2956
+ as `useQuery()` does (#502);
2957
+ - `useActorMutation` keys its mutations like `createMutation` and
2958
+ `useActorMethod`, so `useIsMutating({ mutationKey })`, `useMutationState`
2959
+ and `setMutationDefaults` now match them (#564).
2960
+ - Validation helpers: `mapValidationErrors`, `extractValidationErrors` and
2961
+ `handleValidationError` keep issues on fields named `constructor`, `toString`
2962
+ or `__proto__`. Before, those issues were dropped, and with
2963
+ `{ multiple: true }` the call threw. `mapValidationErrors` now also accepts
2964
+ any `{ multiple?: boolean }` value, `{}` or `undefined`, and `getFieldError` /
2965
+ `getFieldErrors` find an issue about the whole argument under `""` (#592).
2966
+ - `AuthenticationManager`:
2967
+ - several `useAuth()` consumers that mount together share one auth client
2968
+ instead of building one each (#479), and the v10 clients a call replaces
2969
+ when its options differ are disposed (#729). On v8 an idle period runs
2970
+ `idleOptions.onIdle` once instead of once per client built;
2971
+ - the session restore runs once per manager instead of once per `useAuth()`,
2972
+ so mounting a consumer no longer flips `isAuthenticating`, and a component
2973
+ that hides consumers while authenticating no longer loops. A restore reads
2974
+ the identity from the client the manager still uses, and a restore of a
2975
+ manager disposed before it finished builds no client (#745);
2976
+ - when `logout()` fails after the client has already dropped the session, as a
2977
+ v10 revoke does while offline, the manager signs out locally: the agent gets
2978
+ the anonymous identity and the signed-out state is published. `logout()`
2979
+ still rejects with the error (#478);
2980
+ - a failed `login()` records its error and clears `isAuthenticating` before
2981
+ `onError` runs, so an `onError` that throws no longer leaves
2982
+ `isAuthenticating: true` (#480);
2983
+ - an `ic_env` cookie that is not valid percent-encoding is ignored. Before, it
2984
+ made `new AuthenticationManager()`, and so `useAuth()`, throw `URIError`
2985
+ (#482);
2986
+ - the unsubscribe that `subscribeAuthState` returns removes only its own
2987
+ registration. A subscriber that throws no longer stops later subscribers,
2988
+ including `useAuth()`, from hearing the change; the first error is rethrown
2989
+ after all of them have run;
2990
+ - when the local Internet Identity canister serves no sign-in page, the error
2991
+ tells a v10 app to serve an II frontend and set `identityProvider` and
2992
+ `internetIdentityId`. Before, it pointed to an II build that only v8 can use
2993
+ (#561).
2994
+ - `useAuth`, `useUserPrincipal` and `useAgentState` render a fixed server
2995
+ snapshot, so a component that hydrates after the session restore no longer
2996
+ causes a hydration mismatch. Each consumer also subscribes once when it
2997
+ mounts, not on every render.
2998
+ - Lapsed and multi-tab sessions no longer leave an identity on the agent that
2999
+ the auth client does not vouch for:
3000
+ - a manager built over a caller's `authClient` whose session has lapsed puts
3001
+ the anonymous identity on the agent;
3002
+ - on v10, a lapsed session is ended in this tab only, without calling the
3003
+ client's `signOut()`, which acts on the whole origin and could end or
3004
+ revoke a sign-in made in another tab;
3005
+ - on v8, a tab whose own delegation has lapsed signs out even after another
3006
+ tab signs in again (#755). A signed-out tab no longer reports signed in as
3007
+ the anonymous principal once another tab signs in.
3008
+ - Identity attributes:
3009
+ - Internet Identity's attribute message is decoded as the ICRC-3 map it is.
3010
+ Emails and names no longer come back with stray length characters, cut short
3011
+ at the first non-ASCII character, or read from the wrong key (#477);
3012
+ - when an attribute request fails but its sign-in completes, the sign-in is
3013
+ kept, and `useIdentityAttributes` shows the error (#481);
3014
+ - a sign-out or account switch made while an attribute request is pending is
3015
+ no longer undone when the request resolves. If the client's session ended
3016
+ meanwhile, for example because another tab signed out, the request signs the
3017
+ manager out. `useIdentityAttributes` no longer shows the attributes after
3018
+ such a sign-out.
3019
+
3020
+ ### @ic-reactor/candid
3021
+
3022
+ #### Added
3023
+
3024
+ - `forCanister(canisterId)` on `CandidReactor`, `CandidDisplayReactor`,
3025
+ `MetadataReactor` and `MetadataDisplayReactor`; the sibling shares the
3026
+ Candid source and adapter.
3027
+ - A `defaultArgs` option (`"display"` or `"candid"`) on `ResultFieldVisitor`.
3028
+
3029
+ #### Changed
3030
+
3031
+ - `MetadataReactor` gives func-record `defaultArgs` in Candid form, so
3032
+ `callMethod({ args: defaultArgs })` on a reactor built from the node's
3033
+ `funcClass` works (#611). `MetadataDisplayReactor` keeps display defaults.
3034
+ Migration: use `MetadataDisplayReactor` where the code expects display
3035
+ defaults.
3036
+ - `CandidFormVisitor` and `FieldVisitor` accept `""` for a `text` field. A text
3037
+ field with a format, a number and a func reference's method name are still
3038
+ required (#611). Migration: none.
3039
+ - `isPrincipalId` calls `isPrincipalText`, so it refuses a principal longer
3040
+ than 29 bytes and the `{"__principal__": ...}` JSON form. Migration: pass
3041
+ principal text.
3042
+
3043
+ #### Deprecated
3044
+
3045
+ - `CandidAdapter.unsubscribe` is a no-op; remove the call (#644).
3046
+
3047
+ #### Fixed
3048
+
3049
+ - `registerMethod`, and `callDynamic`, `queryDynamic` and `fetchQueryDynamic`,
3050
+ which call it, now accept Candid they used to reject: type declarations that
3051
+ do not each start a line, as in the documented indented example (#540); a
3052
+ whole service definition written `service: {…}`, with a line break before the
3053
+ colon, or named (`service greeter : {…}`) (#545); a quoted name that ends in
3054
+ an escaped backslash (#576); and a method name holding a quote, backslash or
3055
+ control character, which is now escaped instead of breaking the service or
3056
+ naming another method (#635). Rejecting an unclosed quoted name now takes
3057
+ milliseconds instead of seconds, and `MetadataReactor.buildForValueType` reads
3058
+ a type that ends in a `//` comment.
3059
+ - The argument forms (`FieldVisitor`, `CandidFormVisitor`) now accept only what
3060
+ the call can send. A func reference inside an argument is a
3061
+ `[principal, method name]` field (`candidType: "func"`) and a service
3062
+ reference is a principal field (`candidType: "service"`); before,
3063
+ `initialize()` threw or the schema refused every valid value (#539). A blob
3064
+ field takes only hex text with two digits per byte (optional `0x`), integers
3065
+ from 0 to 255 or a `Uint8Array`, so odd-length hex is refused instead of being
3066
+ sent shifted by one digit (#543). A float field refuses blank text, non-finite
3067
+ numbers and a float32 overflow (#542). An `empty` field refuses every value,
3068
+ and a variant no longer defaults to an option that holds `empty` (#544). A
3069
+ text field labelled as a URL also accepts a request path starting with `/`,
3070
+ such as `http_request`'s `url`.
3071
+ - A service with a method typed by a recursive func alias
3072
+ (`type f = func (f) -> (f)`) now works. `CandidDisplayReactor.initialize()` no
3073
+ longer fails for the whole service, and a method whose display codec cannot be
3074
+ built is skipped with an error naming it. `MetadataReactor` and
3075
+ `MetadataDisplayReactor` no longer throw "Cannot read properties of undefined
3076
+ (reading 'includes')", and they describe, hydrate and offer candidates for
3077
+ that method (#557).
3078
+ - Names from `Object.prototype`: `getInputMeta` and `getOutputMeta` return
3079
+ `undefined` for a name such as `"toString"`, `"constructor"` or `"__proto__"`
3080
+ that the service does not declare. A method, record field or variant tag named
3081
+ `__proto__` is now kept in the metadata, defaults, schema, result tree and
3082
+ hydration. A left-out field named like an inherited member resolves as
3083
+ missing, and resolving a variant tag that the type does not have throws
3084
+ `Option "<tag>" not found` (#610, #659).
3085
+ - A result node's `resolve()` accepts values already in display form: an `opt`
3086
+ whose display value is an array (`opt vec text` as `["a", "b"]`) and a
3087
+ `vec record { text; V }` shown as an object. A null variant arm now resolves
3088
+ to `null` instead of `undefined` (#546).
3089
+ - `buildMethodVariableCandidates` now opens a recursive result, so an ICRC-3
3090
+ ledger's `icrc3_get_blocks` offers `.log_length`, `.blocks` and
3091
+ `.archived_blocks`. It names a func reference's members by index
3092
+ (`$get_callback.0`, `$get_callback.1`), which the value resolves by, instead
3093
+ of `.canisterId` and `.methodName`, which resolved to `undefined`.
3094
+ - `MetadataReactor` and `MetadataDisplayReactor` now use far less memory and
3095
+ time: a dropped reactor's service types are freed (about 42 KB each were kept
3096
+ before); `registerMethod` describes only the method it adds, and a repeat
3097
+ registration, which every `callDynamic`, `queryDynamic`, `fetchQueryDynamic`
3098
+ and `callDynamicWithMeta` makes, changes nothing (600 registrations went from
3099
+ 11.7 s to 148 ms); and resolving a recursive value builds one result node per
3100
+ type and label instead of one per value (a 2,000-element Motoko List kept 7.6
3101
+ MB).
3102
+ - Candid reactors no longer leave an identity subscription on their
3103
+ `ClientManager` for good, and an assigned `adapter.didjsCanisterId` survives
3104
+ identity changes (#644).
3105
+ - Loading Candid: "Failed to retrieve Candid source by any method" now says why
3106
+ each attempt failed (#541). A rejected query from the tmp-hack fetch or the
3107
+ didjs compile reports "Query failed (reject code N, error code ICnnnn):
3108
+ <message>" instead of "Do not know how to serialize a BigInt". A parse that
3109
+ starts while the parser is still loading now waits for it instead of sending
3110
+ the Candid to the didjs canister, which failed offline and on a local replica.
3111
+ `importCandidDefinition` (used by `CandidReactor` and `CandidDisplayReactor`)
3112
+ no longer rewrites a Candid name that contains `export const` or
3113
+ `export function`, which sent that field under another hash.
3114
+
3115
+ ## codegen, cli, vite-plugin 0.15.1
3116
+
3117
+ Changes since codegen, cli and vite-plugin 0.14.0. Version 0.15.0 was tagged
3118
+ but never published: its release job stopped before publishing, because
3119
+ codegen's tests needed packages that job did not build. 0.15.1 is the first
3120
+ release with these changes.
3121
+
3122
+ ### @ic-reactor/codegen
3123
+
3124
+ #### Added
3125
+
3126
+ - `factories: true` on a canister config (with `target: "react"`) writes a
3127
+ managed `index.factories.generated.ts`: `<method>Query` (`createQuery`, or
3128
+ `createQueryFactory` when the method takes args) per query method and
3129
+ `<method>Mutation` (`createMutation`) per update or oneway method, each call
3130
+ marked `/* @__PURE__ */`. Exports `getFactoryExportNames`,
3131
+ `generateFactoriesFile` and `FACTORIES_FILE_NAME`.
3132
+ - `PipelineResult.warnings`, for problems a successful run could not fix, such
3133
+ as an edited `index.ts` that does not re-export the factories.
3134
+ - `findSharedOutDirs` and `sharedOutDirMessage`, moved here from the CLI.
3135
+
3136
+ #### Changed
3137
+
3138
+ - `index.generated.ts`, and the `index.ts` wrapper that generation creates, are
3139
+ formatted with the project's Prettier and config, like the declarations.
3140
+ `prettier --check` passes right after generation, and a formatted file is no
3141
+ longer rewritten on the next run. Migration: none; commit the reformatted
3142
+ `index.generated.ts` that the next run writes.
3143
+ - The `index.ts` that generation creates once now points agents at the
3144
+ `ic-reactor` Agent Skill published from this repository
3145
+ (`/plugin install ic-reactor@ic-reactor` in Claude Code after
3146
+ `/plugin marketplace add B3Pay/ic-reactor`, or
3147
+ `npx skills add B3Pay/ic-reactor --skill ic-reactor`) instead of the
3148
+ contributor skill in `B3Pay/ic-reactor-skills`. Migration: none; an existing
3149
+ `index.ts` is never rewritten, so edit its comment by hand if you want the
3150
+ new pointer.
3151
+ - A `didFile` inside `<outDir>/declarations` (the generated declarations
3152
+ directory) is refused with an error before anything is written. Before, the
3153
+ first run deleted a `.did` kept in a folder there, and every later run failed
3154
+ with "DID file not found". Migration: move the `.did` out of
3155
+ `<outDir>/declarations` and point `didFile` at it;
3156
+ `src/declarations/<canister>/<canister>.did` still works.
3157
+
3158
+ #### Fixed
3159
+
3160
+ - With `mode` set to `CandidReactor`, `CandidDisplayReactor` or
3161
+ `MetadataDisplayReactor`, the generated `index.generated.ts` now passes
3162
+ `createActorHooks` its type arguments. A service with a recursive type
3163
+ (Motoko's `List`) or one as large as Internet Identity's no longer fails `tsc`
3164
+ with TS2589. The hooks' types are unchanged (#503).
3165
+ - A run now leaves alone every generated file whose bytes are unchanged,
3166
+ `declarations/` included. `tsc --watch`, bundlers and editors no longer wake
3167
+ on a regeneration that changed nothing (#666).
3168
+ - A `.did` saved with a UTF-8 byte order mark (PowerShell, or "UTF-8 with BOM"
3169
+ editors) now generates instead of failing with `Unknown token <U+FEFF>`. The
3170
+ `declarations/` copy keeps the file's bytes.
3171
+
3172
+ ### @ic-reactor/cli
3173
+
3174
+ #### Added
3175
+
3176
+ - `"factories"` per canister in `ic-reactor.json` and `schema.json`. A value
3177
+ that is not a boolean is rejected. `generate` prints codegen's warnings.
3178
+
3179
+ #### Fixed
3180
+
3181
+ - An `ic-reactor.json` saved with a UTF-8 byte order mark (such as PowerShell's
3182
+ `Set-Content -Encoding UTF8` writes) now loads instead of failing every
3183
+ command with "not valid JSON" (#665).
3184
+ - `generate --clean` no longer deletes the output of a configured canister whose
3185
+ `outDir` reaches the global `outDir` through a symlink, or differs from it
3186
+ only in case on a case-insensitive volume. That output includes the
3187
+ hand-written `index.ts` (#505).
3188
+ - `init -y` with an `--out-dir` that is not directly inside `src/` now writes
3189
+ the `clientManagerPath` that reaches `src/clients.ts`, so the first `generate`
3190
+ output compiles instead of failing with TS2307 (#559).
3191
+ - `generate --canister toString` (or `constructor`, `hasOwnProperty`,
3192
+ `__proto__`) now reports "Canister <name> not found in config." instead of an
3193
+ unrelated error. The `--bindgen-only` help now names the files it writes
3194
+ instead of a `.did.d.ts` that is never written (#666).
3195
+
3196
+ ### @ic-reactor/vite-plugin
3197
+
3198
+ #### Added
3199
+
3200
+ - `factories` per canister entry. The plugin prints codegen's warnings to the
3201
+ terminal without failing the build.
3202
+
3203
+ #### Changed
3204
+
3205
+ - An entry that shares its output directory with an earlier one (same `name`
3206
+ and `outDir`) fails, as it does in the CLI; under `vite build` that fails the
3207
+ build (#565). Migration: give each entry its own `outDir`.
3208
+ - `injectEnvironment` sets the `ic_env` cookie on each response. While `icp`
3209
+ reports no network or a configured canister has no id, each page load asks
3210
+ `icp` again, so deploying after `vite dev` started needs only a reload
3211
+ (#664). The `/api` proxy follows the detected network unless the Vite config
3212
+ or another plugin sets its own `/api` proxy. `vite preview` gets the same
3213
+ cookie. Migration: none.
3214
+
3215
+ #### Fixed
3216
+
3217
+ - Under `vite dev`, the plugin now regenerates from the dev server's file
3218
+ watcher. A `.did` created after startup now regenerates, and so does one
3219
+ deleted and written again by a build tool or `git checkout`. Saves with
3220
+ `server.hmr: false` also regenerate now.
3221
+ - Under `vite build --watch`, saving a `.did` now rebuilds with the new
3222
+ bindings, and editing another file no longer starts rebuilds that never end. A
3223
+ watch rebuild regenerates only the entries whose `.did` changed, and it
3224
+ retries an entry that failed.
3225
+ - Two entries with the same canister `name` (for example one `DisplayReactor`
3226
+ and one `Reactor` output in different `outDir`s) now both regenerate when
3227
+ their `.did` is saved. The second entry's failure is still shown to browsers
3228
+ that connect later (#504).
3229
+
3230
+ ## parser 0.6.0
3231
+
3232
+ Changes since parser 0.5.0.
3233
+
3234
+ ### @ic-reactor/parser
3235
+
3236
+ #### Added
3237
+
3238
+ - The package ships `llms.txt`, a usage guide for coding agents, as the other
3239
+ packages do (`node_modules/@ic-reactor/parser/llms.txt`).
3240
+
3241
+ #### Changed
3242
+
3243
+ - `didToJs` and `didToTs` key a record field or variant tag whose name looks
3244
+ like a numeric id (`_0_`) by the hash of its name (`_4735054_`), and
3245
+ `parseDid` names it the same way, matching what a canister expects (#631).
3246
+ Migration: a hand-written `.did` that spells a Motoko numeric field `_0_`
3247
+ must write `0`.
3248
+ - A Candid type named `_SERVICE`, like a TypeScript type keyword (`string`,
3249
+ `number`, `bigint`, ...) or like a type operator (`keyof`, `readonly`,
3250
+ `unique`, `infer`) is renamed with `_` appended, e.g. `string_` (#737).
3251
+ Migration: import the renamed type.
3252
+
3253
+ #### Fixed
3254
+
3255
+ - `didToJs` and `didToTs` output now loads and compiles in more cases. A field,
3256
+ variant tag or method named `__proto__` is kept, printed as `['__proto__']`
3257
+ (#560). U+0000 in a name prints as `\x00` instead of `\0`, which read as an
3258
+ octal escape: it failed in strict mode or changed the field's hash. A type
3259
+ named `IDL`, `Principal`, `ActorMethod`, `Array` or a typed array no longer
3260
+ clashes with the binding's own names. A service whose actor type is named like
3261
+ a JavaScript keyword (`type class = service {…}; service : class`) now refers
3262
+ to the printed `class_` (#738).
3263
+ - `parseDid` returns `service: null`, as its type says, instead of `undefined`
3264
+ for Candid that declares no service (#506). Code that checks `=== undefined`
3265
+ must check `null` instead; `== null` works either way.
3266
+ - Docs: the README now says `validateIDL` returns `true` or throws the parser's
3267
+ error as a string. It never returned `false` as the README had said; use
3268
+ `CandidAdapter.validateCandid` for a boolean.