stitchkit 0.100.1 → 0.100.3

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 (233) hide show
  1. package/CHANGELOG.md +99 -0
  2. package/README.md +2 -1
  3. package/dist/application/process-lifecycle-transitions.d.ts +13 -2
  4. package/dist/application/process-lifecycle-transitions.d.ts.map +1 -1
  5. package/dist/application/process-lifecycle.d.ts +2 -1
  6. package/dist/application/process-lifecycle.d.ts.map +1 -1
  7. package/dist/entrypoints/agent-runtime/browser.js +119 -119
  8. package/dist/entrypoints/agent-runtime/coding-tools.js +15 -20
  9. package/dist/entrypoints/agent-runtime/harness.js +55 -63
  10. package/dist/entrypoints/agent-runtime/openrouter.js +6 -6
  11. package/dist/entrypoints/agent-runtime/sandbox.js +20 -25
  12. package/dist/entrypoints/agent-runtime/sqlite/bun.js +11 -13
  13. package/dist/entrypoints/agent-runtime/sqlite/node.js +11 -13
  14. package/dist/entrypoints/agent-runtime/testing.js +32 -34
  15. package/dist/entrypoints/agent-runtime.js +395 -402
  16. package/dist/entrypoints/application/diagnostic-journal.js +26 -27
  17. package/dist/entrypoints/application/directory-inbox.js +16 -17
  18. package/dist/entrypoints/application/grammy.js +13 -13
  19. package/dist/entrypoints/application/opentelemetry.js +13 -13
  20. package/dist/entrypoints/application/schemas.js +24 -24
  21. package/dist/entrypoints/application.d.ts +1 -1
  22. package/dist/entrypoints/application.d.ts.map +1 -1
  23. package/dist/entrypoints/application.js +218 -214
  24. package/dist/entrypoints/bin/upgrade-cli.js +2 -2
  25. package/dist/entrypoints/cli.js +81 -84
  26. package/dist/entrypoints/contract.js +54 -57
  27. package/dist/entrypoints/declaration.js +22 -22
  28. package/dist/entrypoints/files.js +18 -21
  29. package/dist/entrypoints/geo.js +7 -8
  30. package/dist/entrypoints/google.js +2 -2
  31. package/dist/entrypoints/index.js +150 -154
  32. package/dist/entrypoints/live.js +45 -47
  33. package/dist/entrypoints/node.js +68 -74
  34. package/dist/entrypoints/oauth.js +6 -6
  35. package/dist/entrypoints/observability.d.ts +1 -0
  36. package/dist/entrypoints/observability.d.ts.map +1 -1
  37. package/dist/entrypoints/observability.js +115 -87
  38. package/dist/entrypoints/primitives.js +35 -35
  39. package/dist/entrypoints/react/keyboard.d.ts +4 -0
  40. package/dist/entrypoints/react/keyboard.d.ts.map +1 -0
  41. package/dist/entrypoints/react/keyboard.js +397 -0
  42. package/dist/entrypoints/react.js +10 -10
  43. package/dist/entrypoints/release.js +7 -7
  44. package/dist/entrypoints/remote.js +15 -17
  45. package/dist/entrypoints/server.js +198 -200
  46. package/dist/entrypoints/telegram/html.d.ts +5 -0
  47. package/dist/entrypoints/telegram/html.d.ts.map +1 -0
  48. package/dist/entrypoints/telegram/html.js +26 -0
  49. package/dist/entrypoints/telegram/screens.d.ts +14 -0
  50. package/dist/entrypoints/telegram/screens.d.ts.map +1 -0
  51. package/dist/entrypoints/telegram/screens.js +2028 -0
  52. package/dist/entrypoints/telegram.d.ts +5 -1
  53. package/dist/entrypoints/telegram.d.ts.map +1 -1
  54. package/dist/entrypoints/telegram.js +548 -156
  55. package/dist/entrypoints/testing.js +75 -79
  56. package/dist/entrypoints/tools/connections.js +30 -32
  57. package/dist/entrypoints/tools/contract.js +18 -20
  58. package/dist/entrypoints/tools/invoker.js +12 -17
  59. package/dist/entrypoints/tools.js +223 -229
  60. package/dist/entrypoints/tracking/server.js +6 -6
  61. package/dist/entrypoints/tracking.js +40 -42
  62. package/dist/entrypoints/voice.js +3 -3
  63. package/dist/{index-eaysmdtq.js → index-0ndk4555.js} +56 -56
  64. package/dist/{index-2bxbyab0.js → index-1bx1xv1k.js} +1 -1
  65. package/dist/{index-p5r7qpdd.js → index-1eeqpadm.js} +4 -4
  66. package/dist/{index-hadmp193.js → index-3ct7hhq6.js} +210 -210
  67. package/dist/{index-e01r054k.js → index-45tdvqa3.js} +26 -26
  68. package/dist/{index-dce5wxcq.js → index-4s0fep5q.js} +8 -8
  69. package/dist/index-50ejfz75.js +9 -0
  70. package/dist/{index-m9zn6cpq.js → index-5107q189.js} +12 -12
  71. package/dist/{index-sbdmyz75.js → index-5cc6r990.js} +3 -3
  72. package/dist/index-6k1svmjh.js +141 -0
  73. package/dist/{index-c6bxbsnc.js → index-7kxw1awn.js} +2 -2
  74. package/dist/{index-0r0fbk7z.js → index-7z9ym7t8.js} +28 -28
  75. package/dist/{index-p7hgdyhb.js → index-814xpr6d.js} +2 -2
  76. package/dist/{index-gtj3zc53.js → index-8kzct1d6.js} +99 -99
  77. package/dist/{index-1zbbzgfh.js → index-9pw0a51a.js} +4 -4
  78. package/dist/{index-79z7m7dt.js → index-a9pd417t.js} +35 -35
  79. package/dist/{index-ham5sz8m.js → index-aaj97r4y.js} +45 -45
  80. package/dist/{index-s812ycat.js → index-aan6hqb9.js} +3 -3
  81. package/dist/{index-10x2hbx0.js → index-adhgd4pe.js} +44 -44
  82. package/dist/{index-2gxzjvyr.js → index-afjvjyck.js} +23 -23
  83. package/dist/{index-qmy6zq75.js → index-ahvsksw5.js} +16 -16
  84. package/dist/{index-hr4tzbwq.js → index-ap3gqe56.js} +3 -3
  85. package/dist/{index-znfs20ss.js → index-b2bz0kdm.js} +16 -16
  86. package/dist/{index-r066jx76.js → index-c6aagrbd.js} +148 -120
  87. package/dist/{index-k30h66yd.js → index-ck71tbcf.js} +9 -9
  88. package/dist/{index-eqqvgdb3.js → index-ct75c3at.js} +2 -2
  89. package/dist/{index-gw8bmyaj.js → index-djjhhgjk.js} +41 -41
  90. package/dist/{index-8qt2m66q.js → index-dvwj1f43.js} +43 -43
  91. package/dist/{index-fhwqwxe0.js → index-ej331hah.js} +1 -1
  92. package/dist/{index-9t3dffv2.js → index-ewbaywk5.js} +5 -5
  93. package/dist/{index-7p1f0vp2.js → index-fyphpknh.js} +25 -25
  94. package/dist/{index-83bdg97s.js → index-g149e9pk.js} +1 -1
  95. package/dist/{index-ngfhvfpd.js → index-g6a3na27.js} +4 -4
  96. package/dist/{index-qwhynv6x.js → index-h124epcf.js} +20 -20
  97. package/dist/{index-h6eeys1v.js → index-h97tqqp1.js} +9 -9
  98. package/dist/{index-zcgf3gqf.js → index-hby4qhax.js} +10 -10
  99. package/dist/{index-5jrd0yng.js → index-j8aw2c0e.js} +4 -4
  100. package/dist/{index-d8m8zpd8.js → index-jzczzcsb.js} +14 -14
  101. package/dist/index-kqnd6a85.js +26 -0
  102. package/dist/{index-35mh5bhj.js → index-kw1ws0cs.js} +12 -12
  103. package/dist/{index-yw4fzmdz.js → index-kzz6rq0w.js} +28 -17
  104. package/dist/{index-4gcnakhx.js → index-m38ej7e5.js} +2 -2
  105. package/dist/{index-ndddxtyx.js → index-m79yr938.js} +19 -19
  106. package/dist/{index-5p2m7we1.js → index-m9fndta6.js} +11 -11
  107. package/dist/index-mnkk1ffz.js +150 -0
  108. package/dist/index-n15jny5b.js +4 -0
  109. package/dist/{index-2vtcmrj9.js → index-n53mswt3.js} +20 -20
  110. package/dist/{index-bt14acb5.js → index-n5yqxd3d.js} +21 -21
  111. package/dist/{index-bm5rx7n6.js → index-n9tyz8z3.js} +12 -12
  112. package/dist/index-nmaqxdx7.js +463 -0
  113. package/dist/{index-mqv3b5rh.js → index-nx6eazt0.js} +30 -30
  114. package/dist/{index-3z73fh2c.js → index-q0th8q0z.js} +9 -9
  115. package/dist/{index-v260df89.js → index-qjeggp8w.js} +20 -20
  116. package/dist/{index-zfrhfn82.js → index-r1z0rd9c.js} +20 -20
  117. package/dist/{index-wqgjfstn.js → index-r5c4fedv.js} +3 -3
  118. package/dist/{index-cyktc4bp.js → index-r5hsgkz5.js} +118 -118
  119. package/dist/{index-0b1ess5e.js → index-r7a7r7ha.js} +1 -1
  120. package/dist/{index-1j8pm5t0.js → index-rfkh44ss.js} +15 -15
  121. package/dist/index-rz3z13e9.js +47 -0
  122. package/dist/{index-k6gpggb9.js → index-s80awwsw.js} +35 -35
  123. package/dist/{index-vqr7f7v8.js → index-ssmd41kh.js} +124 -124
  124. package/dist/{index-ensd0r74.js → index-v14qqmys.js} +41 -41
  125. package/dist/{index-paz6sgdp.js → index-v4a2fjtk.js} +36 -36
  126. package/dist/{index-d9pk3td4.js → index-w0eqdhyd.js} +14 -14
  127. package/dist/{index-5qn1kx34.js → index-w575evhq.js} +29 -29
  128. package/dist/{index-3gye9wzb.js → index-w5s3xftz.js} +26 -26
  129. package/dist/{index-tm88avnj.js → index-wykebpn9.js} +3 -3
  130. package/dist/{index-sh3s86na.js → index-xkhk5d6g.js} +17 -17
  131. package/dist/{index-6k1937bx.js → index-xqhede6x.js} +4 -4
  132. package/dist/{index-f4fx2tza.js → index-zs4ms3th.js} +181 -181
  133. package/dist/{index-k5hsfc3j.js → index-zsvzhjtk.js} +1 -1
  134. package/dist/observability/bounded-logger.d.ts +2 -0
  135. package/dist/observability/bounded-logger.d.ts.map +1 -1
  136. package/dist/observability/sanitize.d.ts +6 -0
  137. package/dist/observability/sanitize.d.ts.map +1 -1
  138. package/dist/observability/secret-values.d.ts +25 -0
  139. package/dist/observability/secret-values.d.ts.map +1 -0
  140. package/dist/react/keyboard/hooks.d.ts +71 -0
  141. package/dist/react/keyboard/hooks.d.ts.map +1 -0
  142. package/dist/react/keyboard/layers.d.ts +98 -0
  143. package/dist/react/keyboard/layers.d.ts.map +1 -0
  144. package/dist/react/keyboard/zone.d.ts +139 -0
  145. package/dist/react/keyboard/zone.d.ts.map +1 -0
  146. package/dist/telegram/crypto.d.ts +16 -0
  147. package/dist/telegram/crypto.d.ts.map +1 -0
  148. package/dist/telegram/html/check.d.ts +26 -0
  149. package/dist/telegram/html/check.d.ts.map +1 -0
  150. package/dist/telegram/html/nodes.d.ts +70 -0
  151. package/dist/telegram/html/nodes.d.ts.map +1 -0
  152. package/dist/telegram/html/parse.d.ts +35 -0
  153. package/dist/telegram/html/parse.d.ts.map +1 -0
  154. package/dist/telegram/html/render.d.ts +39 -0
  155. package/dist/telegram/html/render.d.ts.map +1 -0
  156. package/dist/telegram/init-data.d.ts.map +1 -1
  157. package/dist/telegram/operator-channel.d.ts +36 -3
  158. package/dist/telegram/operator-channel.d.ts.map +1 -1
  159. package/dist/telegram/screens/callback-codec.d.ts +34 -0
  160. package/dist/telegram/screens/callback-codec.d.ts.map +1 -0
  161. package/dist/telegram/screens/dispatch.d.ts +22 -0
  162. package/dist/telegram/screens/dispatch.d.ts.map +1 -0
  163. package/dist/telegram/screens/display.d.ts +70 -0
  164. package/dist/telegram/screens/display.d.ts.map +1 -0
  165. package/dist/telegram/screens/execute.d.ts +57 -0
  166. package/dist/telegram/screens/execute.d.ts.map +1 -0
  167. package/dist/telegram/screens/html.d.ts +36 -0
  168. package/dist/telegram/screens/html.d.ts.map +1 -0
  169. package/dist/telegram/screens/outcome.d.ts +74 -0
  170. package/dist/telegram/screens/outcome.d.ts.map +1 -0
  171. package/dist/telegram/screens/path.d.ts +29 -0
  172. package/dist/telegram/screens/path.d.ts.map +1 -0
  173. package/dist/telegram/screens/reconcile.d.ts +67 -0
  174. package/dist/telegram/screens/reconcile.d.ts.map +1 -0
  175. package/dist/telegram/screens/registry.d.ts +23 -0
  176. package/dist/telegram/screens/registry.d.ts.map +1 -0
  177. package/dist/telegram/screens/render.d.ts +70 -0
  178. package/dist/telegram/screens/render.d.ts.map +1 -0
  179. package/dist/telegram/screens/runtime.d.ts +133 -0
  180. package/dist/telegram/screens/runtime.d.ts.map +1 -0
  181. package/dist/telegram/screens/screen-types.d.ts +130 -0
  182. package/dist/telegram/screens/screen-types.d.ts.map +1 -0
  183. package/dist/telegram/screens/screen.d.ts +138 -0
  184. package/dist/telegram/screens/screen.d.ts.map +1 -0
  185. package/dist/telegram/screens/state.d.ts +115 -0
  186. package/dist/telegram/screens/state.d.ts.map +1 -0
  187. package/dist/telegram/screens/telegram-screens.d.ts +18 -0
  188. package/dist/telegram/screens/telegram-screens.d.ts.map +1 -0
  189. package/dist/telegram/screens/test-chat-telegram.d.ts +47 -0
  190. package/dist/telegram/screens/test-chat-telegram.d.ts.map +1 -0
  191. package/dist/telegram/screens/test-chat.d.ts +66 -0
  192. package/dist/telegram/screens/test-chat.d.ts.map +1 -0
  193. package/dist/telegram/screens/view.d.ts +89 -0
  194. package/dist/telegram/screens/view.d.ts.map +1 -0
  195. package/dist/telegram/send-failure.d.ts +19 -0
  196. package/dist/telegram/send-failure.d.ts.map +1 -1
  197. package/dist/telegram/update-intake.d.ts +78 -0
  198. package/dist/telegram/update-intake.d.ts.map +1 -0
  199. package/dist/telegram/update-store-sqlite.d.ts +26 -0
  200. package/dist/telegram/update-store-sqlite.d.ts.map +1 -0
  201. package/dist/telegram/update-store.d.ts +67 -0
  202. package/dist/telegram/update-store.d.ts.map +1 -0
  203. package/dist/telegram/webhook.d.ts +105 -0
  204. package/dist/telegram/webhook.d.ts.map +1 -0
  205. package/dist/tools/cli/args-fields.d.ts +8 -0
  206. package/dist/tools/cli/args-fields.d.ts.map +1 -1
  207. package/dist/tools/cli/args-route.d.ts.map +1 -1
  208. package/dist/tools/cli/args.d.ts.map +1 -1
  209. package/dist/tools/cli/help.d.ts.map +1 -1
  210. package/llms/application.2.txt +26 -1
  211. package/llms/application.txt +18 -27
  212. package/llms/cli.txt +8 -2
  213. package/llms/observability.txt +2 -1
  214. package/llms/react-keyboard.txt +193 -0
  215. package/llms/stitchkit.txt +7 -1
  216. package/llms/telegram-html.txt +35 -0
  217. package/llms/telegram-screens.txt +365 -0
  218. package/llms/telegram.txt +160 -4
  219. package/llms-full.txt +755 -11
  220. package/llms.txt +11 -6
  221. package/package.json +17 -2
  222. package/dist/index-2k4yrqkc.js +0 -9
  223. package/dist/index-crykmqkr.js +0 -26
  224. package/dist/index-y64k3s0h.js +0 -150
  225. package/dist/index-y91ry9h2.js +0 -4
  226. /package/dist/{index-hffjpmq9.js → index-9jfdm5tg.js} +0 -0
  227. /package/dist/{index-zjjyynm3.js → index-d4pa2bgs.js} +0 -0
  228. /package/dist/{index-rxfy4cq7.js → index-dwfv9a85.js} +0 -0
  229. /package/dist/{index-5jm36zv1.js → index-gfk7nrht.js} +0 -0
  230. /package/dist/{index-b9aefrqd.js → index-n1dqr7gf.js} +0 -0
  231. /package/dist/{index-q3pyb9n0.js → index-sykb5ag7.js} +0 -0
  232. /package/dist/{index-48ffdxgk.js → index-xhzq2qvj.js} +0 -0
  233. /package/dist/{index-kzfs85xp.js → index-zsarb5xp.js} +0 -0
@@ -876,7 +876,8 @@ ever became ready:
876
876
  | none | — | — | `first-boot` | — |
877
877
  | recorded `stoppedAt` | — | — | `clean` / `forced` / `startup-failed` / `abnormal` as recorded | unchanged |
878
878
  | still open | yes | — | `hot-reload` | `hot-reload`, closed at the new start |
879
- | still open | no | changed | `handoff` | stays open; it records its own stop later |
879
+ | still open | no | changed | `handoff` (default) | stays open; it records its own stop later |
880
+ | still open | no | changed, `versionChangeOverlap: 'abnormal'` | `abnormal` / `startup-failed` | closed at the new start, as for the same version |
880
881
  | still open, was ready | no | same | `abnormal` (default) | `abnormal`, closed at the new start — an upper bound, the crash time is unknown |
881
882
  | still open, never ready | no | same | `startup-failed` (default) | `startup-failed`, closed at the new start |
882
883
 
@@ -918,7 +919,22 @@ zero-downtime reload of the same build — pass `sameVersionOverlap: 'handoff'`
918
919
  to the ledger, and every open run stays open until it records its own
919
920
  shutdown. The cost of that setting is symmetric: a real crash under it is
920
921
  reported as a handoff and the dead run stays open in the ledger until
921
- retention drops it. The list is kept in the order the transitions wrote it —
922
+ retention drops it.
923
+
924
+ A version change is the other axis, and its default is the opposite:
925
+ `handoff`, because a release that starts the new build before stopping the
926
+ old one is the common zero-downtime shape. Where builds never overlap — one
927
+ process per deployment (a pm2 fork, a systemd unit, one container), the old
928
+ one stopped before the new one starts — pass `versionChangeOverlap:
929
+ 'abnormal'`. An open predecessor of another build then died without
930
+ recording its exit, most often killed during the release itself: it is closed
931
+ as `abnormal` (or `startup-failed`) at the new start, `downtimeMs` is `null`
932
+ instead of `0`, and `previousVersion` / `versionChanged` still describe the
933
+ release. A predecessor that did record its stop is read as before, and its
934
+ downtime is measured. The two settings compose: `sameVersionOverlap:
935
+ 'handoff'` keeps open only the runs of a build allowed to overlap the new one.
936
+
937
+ The list is kept in the order the transitions wrote it —
922
938
  every write goes through one atomic update, so that order is the causal one,
923
939
  and a successor whose clock lags its predecessor still finds it at the head;
924
940
  `startedAt` is data, not the sort key. Retention (`retain`, default 20) drops
@@ -991,28 +1007,3 @@ so a scheduler stall on a loaded host cannot pull a live lock from under its
991
1007
  holder — after which the heartbeat wins and the lock is abandoned. Temporary files a crashed writer left beside the state are swept on
992
1008
  the store's first update. Ledger corruption may be declared reconstructable;
993
1009
  an outbox must fail closed rather than silently discard pending delivery.
994
-
995
- ## Recovering change subscriptions
996
-
997
- `changeSubscriptionResource` is an optional resource over a consumer-owned driver.
998
- It uses a finite declared key set, subscribes before reading, reconciles after
999
- reconnect and periodically repairs lost hints. `reconcileIntervalMs` is explicit;
1000
- notifications alone cannot prove freshness. Within a key, hints coalesce into one
1001
- trailing read and a failed read retries with backoff. Use the supplied
1002
- `context.commit(() => publish(value))` after awaits to reject an obsolete connection.
1003
-
1004
- Drivers implement `connect(signal)` and return `subscribe({ signal, hint,
1005
- disconnected })` plus `close()`. Subscribe must establish the notification boundary
1006
- before resolving. A database LISTEN driver owns its connection, channels, decoding
1007
- and transaction ordering; no SQL or database driver is included here. The resource
1008
- owns reconnect, deadlines and shutdown. Drivers must honor abort. If one ignores it,
1009
- its slot remains occupied until settlement; deadlines cannot safely manufacture
1010
- successful cleanup or permit an overlapping connection.
1011
-
1012
- For watched reads, set `reconcileIntervalMs` on the existing `createWatchHub` rather
1013
- than building a second read loop. Its per-key machinery is shared with managed
1014
- subscriptions. Limits default to 1024 sources and 1024 subscribers per hub; set
1015
- explicit finite values for larger workloads. Supply a server-verified admission
1016
- scope to `attach` and abort it on logout, permission revocation or session replacement.
1017
- The read callback receives the trusted scope as its third argument. A client owner
1018
- ID in arguments is not authentication.
package/llms/cli.txt CHANGED
@@ -194,12 +194,18 @@ string, the schema says what it should be:
194
194
  | -------------------- | ---------------------------------------------- |
195
195
  | `z.string()` | `--name "box"` or a positional |
196
196
  | `z.number()` | `--count 3` → `3` |
197
- | `z.boolean()` | `--active` (presence) / `--no-active` |
197
+ | `z.boolean()` | `--active` (presence) / `--active false` / `--no-active` |
198
198
  | `z.enum([...])` | `--size large` |
199
199
  | `z.array(z.string())`| `--tag a --tag b` → `["a","b"]` |
200
200
  | `z.object({...})` | `--opts '{"k":"v"}'` (JSON) or `--opts.k v` |
201
201
  | `.optional()` / `.default()` | not required |
202
202
 
203
+ After a bare boolean flag, the next token is its value when it is exactly
204
+ `true` or `false` — `--active false` turns it off, as it reads. Any other word
205
+ there stays a positional, and `--` makes a literal `false` one too. The same
206
+ holds for a short alias, a framework flag such as `--json false` and an
207
+ application option.
208
+
203
209
  Without presentation configuration, positional arguments fill non-boolean
204
210
  fields in declaration order, so `myapp generate "a fox"` is
205
211
  `--prompt "a fox"`. A piped value fills the first required unset field:
@@ -258,7 +264,7 @@ framework globals may precede that explicit command. Top-level `--help`, `-h`
258
264
  and `--version` never execute the default, and top-level help marks it.
259
265
 
260
266
  Aliases are command-local, one ASCII letter and validated against the resolved
261
- command schema. `-f` / `-f=false` are boolean forms; values accept `-n 100` and
267
+ command schema. `-f` / `-f=false` / `-f false` are boolean forms; values accept `-n 100` and
262
268
  `-n=100`. Arrays accumulate across short and long forms. `-h` is reserved,
263
269
  bundles such as `-fn`, attached values such as `-n100`, `--no-f` and unknown
264
270
  short flags are rejected. Canonical `--no-follow` remains available.
@@ -835,7 +835,8 @@ audit event. See the [Observability guide](../guide/observability.md).
835
835
  | `createBoundedLogger` | function | decorate a `StitchLogger` with request context, shared sanitisation, redaction and total bounds |
836
836
  | `DEFAULT_REDACT_PATHS` | constant | baseline credential/token paths added to the sanitizer's sensitive-key policy |
837
837
  | `createJsonLogger` | function | the process journal: one JSON line per call in pino's shape (numeric `level`, epoch `time`, `msg`) on standard output, through `createBoundedLogger` — an `Error` in any field keeps name, message, stack and cause; secrets masked; `level` threshold, static `fields`, injectable `write` |
838
- | `JsonLoggerOptions` / `JsonLogLevel` / `JSON_LOG_LEVELS` | _type_ / constant | the bounded-logger options plus `level`, `fields`, `write`, `now`; pino's numbers `debug` 20 … `error` 50, which a line reader matches on |
838
+ | `JsonLoggerOptions` / `JsonLogLevel` / `JSON_LOG_LEVELS` | _type_ / constant | the bounded-logger options plus `level`, `fields`, `write`, `now`; pino's numbers `debug` 20 … `error` 50, which a line reader matches on; `sensitiveValues` masks exact secrets in every string |
839
+ | `secretValuesFromEnv` / `SecretValuesOptions` | function / _type_ | every secret an environment holds, for `sensitiveValues`: variables named as a token, secret, key, password or credential, the password and secret query values in any URL variable, each URL-encoded too, longest first; values under `minLength` (8) skipped |
839
840
  | `BoundedLoggerBounds` / `BoundedLoggerOptions` | _type_ | per-value and total record ceilings (`stringLengthByKey` raises the string bound for named keys such as `stack`) plus sink/redaction configuration |
840
841
  | `RequestEvent` | _type_ | the normalised audit event handed to the sink; opt-in HTTP cancellation rows carry `outcome: 'cancelled'` |
841
842
  | `runUnitOfWork` / `RunUnitOfWorkOptions` | function / _type_ | run work that did not arrive over a transport — an agent loop, a scheduled broadcast — inside one context, with the same completion record a request gets |
@@ -0,0 +1,193 @@
1
+ # stitchkit/react/keyboard — stitchkit docs slice
2
+
3
+ > Generated from the `docs/` tree by `bun run gen:llms`. The index of all slices is
4
+ > `llms.txt` in this package.
5
+
6
+ Contains: Guide: Keyboard layers and list zones (docs/guide/keyboard.md) · API reference: `stitchkit/react/keyboard` (docs/api/reference.md)
7
+
8
+ ==============================================================================
9
+ # Guide: Keyboard layers and list zones (docs/guide/keyboard.md)
10
+ ==============================================================================
11
+
12
+ ---
13
+ title: Keyboard layers and list zones
14
+ description: One listener decides which part of a screen gets a key; lists move real focus with the arrows
15
+ type: guide
16
+ status: active
17
+ created: 2026-09-29
18
+ updated: 2026-09-29
19
+ ---
20
+
21
+ # Keyboard layers and list zones
22
+
23
+ `stitchkit/react/keyboard` answers one question for a React screen: **who gets
24
+ this key?** It is headless — three hooks and no component, no styles, no
25
+ router. The application keeps its URL, its sections and its markup; the hooks
26
+ decide which part of the screen hears a key and move focus through lists.
27
+
28
+ ```bash
29
+ bun add react # the only peer
30
+ ```
31
+
32
+ ## Why layers
33
+
34
+ A screen with a menu, a dialog, a list and section shortcuts usually grows a
35
+ `keydown` listener per concern, and they run in the order they mounted:
36
+ Escape closes the dialog *and* the page behind it, an arrow in a dialog also
37
+ switches the section underneath, a hidden panel still reacts. Here every
38
+ concern is a layer on one stack, and the stack asks them in a fixed order until
39
+ one takes the key:
40
+
41
+ | Kind | What it is | Example |
42
+ |---|---|---|
43
+ | `overlay` | something modal; while one is live nothing below hears any key | a dialog |
44
+ | `local` | a component with the user's attention, not modal | an open command menu |
45
+ | `route` | the page's "up one level" | Escape closes the open entity |
46
+ | `zone` | a list the arrows move through (see below) | the chat list |
47
+ | `global` | shortcuts of the whole screen | ← / → switch sections |
48
+
49
+ Within a kind the innermost layer wins: of two layers whose elements nest, the
50
+ inner one, otherwise the one activated last. One press of Escape closes one
51
+ level, and holding Escape does not close several.
52
+
53
+ The stack listens on the window, in the bubbling phase. Everything closer to the
54
+ key hears it first — a widget's own `onKeyDown`, a text field, a popover that
55
+ closes on Escape — and keeps it with `preventDefault()` or `stopPropagation()`.
56
+ A layer only gets what nobody nearer wanted, so third-party components keep
57
+ working unchanged.
58
+
59
+ ## Shortcuts and Escape
60
+
61
+ ```tsx
62
+ import { useRef } from 'react'
63
+ import { useEscapeLayer, useKeyLayer } from 'stitchkit/react/keyboard'
64
+
65
+ function Workspace({ next, previous, children }) {
66
+ useKeyLayer({
67
+ kind: 'global',
68
+ onKey: (event) => {
69
+ if (event.key === 'ArrowRight') next()
70
+ else if (event.key === 'ArrowLeft') previous()
71
+ else return false // not taken: the key keeps its default
72
+ return true
73
+ },
74
+ })
75
+ return children
76
+ }
77
+
78
+ function EntityPanel({ close }) {
79
+ const panel = useRef<HTMLElement>(null)
80
+ useEscapeLayer({ kind: 'route', scope: panel, onEscape: close })
81
+ return <section ref={panel}>…</section>
82
+ }
83
+ ```
84
+
85
+ `onKey` returns `true` when it took the key: its default is prevented and no
86
+ layer below hears it. `onEscape` returns `false` when there was nothing to close,
87
+ and the next layer is asked.
88
+
89
+ **`scope`** ties a layer to an element. A scoped layer is heard only while the
90
+ element is mounted, visible and outside any `inert` subtree — a panel kept
91
+ mounted in the background stops hearing keys without an `active` flag — and it
92
+ outranks layers of its kind whose elements contain it. React runs a child's
93
+ effects before its parent's, so when a page and the panel inside it mount
94
+ together, only `scope` tells the stack which one is inside.
95
+
96
+ **`active`** (default `true`) switches a layer off without unmounting it;
97
+ becoming active again puts it on top of its kind.
98
+
99
+ **Fields keep their keys.** Keys pressed in an input, a text area, a select, an
100
+ editable region, a combobox, a slider or any area marked `data-local-keys` do not
101
+ reach layers — except Escape, which a field that wants it keeps with
102
+ `preventDefault()`. `fields: 'include'` lets a layer hear them anyway.
103
+ `isKeyFieldTarget(event.target)` is the same test, for a handler of your own.
104
+
105
+ ## List zones
106
+
107
+ A zone is a list the arrows move through with **real focus**: one item is the
108
+ tab stop (roving `tabIndex`), Tab enters the list at it and leaves the list, the
109
+ arrows move DOM focus between items. Screen readers follow it, `:focus-visible`
110
+ draws it, the browser scrolls to it.
111
+
112
+ ```tsx
113
+ import { useNavZone } from 'stitchkit/react/keyboard'
114
+
115
+ function ChatList({ chats, openId, open }) {
116
+ const zone = useNavZone({
117
+ items: chats.map((chat) => chat.id),
118
+ value: openId,
119
+ onChange: (id) => open(id),
120
+ label: 'Chats',
121
+ })
122
+ return (
123
+ <ul {...zone.zoneProps}>
124
+ {chats.map((chat) => (
125
+ <li key={chat.id} {...zone.itemProps(chat.id)}>
126
+ {chat.title}
127
+ </li>
128
+ ))}
129
+ </ul>
130
+ )
131
+ }
132
+ ```
133
+
134
+ | Option | Default | Meaning |
135
+ |---|---|---|
136
+ | `items`, `value`, `onChange` | — | the ids in order, the chosen one, and the report of a choice; the zone never holds the value |
137
+ | `mode` | `'select'` | `'select'`: an arrow chooses (`onChange(id, 'arrow')`). `'highlight'`: an arrow only moves focus, Enter chooses (`onChange(id, 'confirm')`) |
138
+ | `orientation` | `'vertical'` | which arrows move; the other axis enters and exits |
139
+ | `role` | `'listbox'` | `'listbox'` of options or `'tablist'` of tabs, marked `aria-selected`; `'none'` keeps the items' own semantics (links, buttons) and marks the chosen one `aria-current` |
140
+ | `loop` | `false` | wrap past the ends instead of calling `onBoundary` |
141
+ | `onEnter(id)` | — | → in a vertical zone (↓ in a horizontal one), and Enter in select mode: go into what the item opens |
142
+ | `onExit()` | — | ← in a vertical zone (↑ in a horizontal one) |
143
+ | `onBoundary(side)` | — | a move past `'start'` or `'end'`: hand focus to the neighbouring zone |
144
+ | `active` | `true` | whether arrows pressed outside every zone land here |
145
+ | `label` | — | the zone's accessible name |
146
+ | `onReveal(id)` | — | focus went to an item with no element yet — a row of a virtualized list outside the rendered range; scroll it in, and focus lands on it when it mounts |
147
+
148
+ The callbacks return `false` to leave the key to the page, anything else takes
149
+ it. A key the zone does not take bubbles on to the layers — in a vertical list
150
+ ← and → are not the list's keys, so the section switch above hears them.
151
+
152
+ Each zone keeps its own focused item; two zones never share one. When focus is
153
+ in no zone at all — right after the page loads — the first arrow on the zone's
154
+ axis puts focus on the active zone's current item. To move between zones
155
+ (sidebar → table), call `zone.focus(id?)` of the other zone from `onEnter` or
156
+ `onBoundary`. `zone.element` is the zone's element, for a layer scoped to it.
157
+
158
+ ## What stays in the application
159
+
160
+ The URL, the router, sections and which screen is open. A layer calls the
161
+ application's own functions — `router.push`, `close()` — and nothing here reads
162
+ or writes a location. Keep entity coordinates in the path and filters in the
163
+ query, parse and build them in one pure module with tests, and use explicit
164
+ `push` / `replace` rather than a positional flag.
165
+
166
+ ==============================================================================
167
+ # API reference: `stitchkit/react/keyboard` (docs/api/reference.md)
168
+ ==============================================================================
169
+
170
+ ## `stitchkit/react/keyboard`
171
+
172
+ Browser-only, headless keyboard handling for React: which part of a screen gets a
173
+ key, and lists the arrows move through with real focus. Needs the `react` peer;
174
+ no component, no styles, no router — see the [keyboard guide](../guide/keyboard.md).
175
+
176
+ | Export | Kind | Summary |
177
+ |--------|------|---------|
178
+ | `useKeyLayer` | hook | put a layer on the window's one key stack — asked `overlay` → `local` → `route` → `zone` → `global`, innermost first within a kind |
179
+ | `useEscapeLayer` | hook | an `overlay`, `local` or `route` layer for Escape: one press closes one level |
180
+ | `useNavZone` | hook | a list zone — roving tab stop, arrows move DOM focus, select or highlight mode, cross-axis enter/exit, boundaries |
181
+ | `nextZoneIndex` | function | where a move lands in a list of a given length: an index, `'before-start'` or `'after-end'` |
182
+ | `isKeyFieldTarget` | function | whether a key was pressed in a field or a `data-local-keys` area, the test layers use |
183
+ | `KeyLayerOptions` | _type_ | `kind`, `onKey`, `active`, `scope`, `fields` |
184
+ | `KeyLayerKind` | _type_ | `'overlay' \| 'local' \| 'route' \| 'zone' \| 'global'` |
185
+ | `EscapeLayerOptions` | _type_ | `kind`, `onEscape`, `active`, `scope` |
186
+ | `NavZoneOptions` | _type_ | `items`, `value`, `onChange`, `mode`, `orientation`, `role`, `loop`, `onEnter`, `onExit`, `onBoundary`, `active`, `label`, `onReveal` |
187
+ | `NavZone` | _type_ | what `useNavZone` returns — `zoneProps`, `itemProps(id)`, `element`, `current`, `focus(id?)` |
188
+ | `NavZoneProps` / `NavZoneItemProps` | _type_ | the props spread on the zone's element and on each item |
189
+ | `NextZoneIndexInput` | _type_ | `length`, `index`, `move`, `loop` |
190
+ | `ZoneItemId` | _type_ | an item id — `string \| number` |
191
+ | `ZoneChangeCause` | _type_ | `'arrow' \| 'confirm'` |
192
+ | `ZoneMove` | _type_ | `'next' \| 'previous' \| 'first' \| 'last'` |
193
+ | `ZoneOrientation` | _type_ | `'vertical' \| 'horizontal'` |
@@ -69,7 +69,9 @@ own, recorded as an ADR.
69
69
  | `stitchkit/cli` | server | stable | `createCli` — the CLI transport, light (no MCP SDK / `ai`) |
70
70
  | `stitchkit/remote` | browser **and** server | stable | peer-free `implementRemote` for thin HTTP proxy processes |
71
71
  | `stitchkit/files` | server (Bun or Node) | stable | peer-free managed local-file boundary |
72
- | `stitchkit/telegram` | server (Bun or Node) | evolving | peer-free Telegram platform primitives — Mini App `initData` verification, send-failure classification, resumable broadcasts, the operator channel and local Bot API files |
72
+ | `stitchkit/telegram` | server (Bun or Node) | evolving | peer-free Telegram platform primitives — Mini App `initData` verification, send-failure classification, resumable broadcasts, the operator channel with dedupe, local Bot API files, webhook ownership and durable webhook update intake |
73
+ | `stitchkit/telegram/html` | browser and server | evolving | Telegram's HTML as a tree — clean any markup to what Telegram accepts, cut long text into valid parts by what the reader sees, check markup before it is sent |
74
+ | `stitchkit/telegram/screens` | server (Bun or Node) | evolving | a bot's menus as declared screens over grammY — typed paths, actions and input, message reconciliation, state in any `StorageAdapter`, a test chat |
73
75
  | `stitchkit/tracking` | browser **and** server | evolving | visitor-tracking mechanics — `createTrackingClient`, the tab-shared outbox, the page-leave beacon, attribution, the contract factory; no event vocabulary, no React |
74
76
  | `stitchkit/tracking/server` | server (Bun or Node) | evolving | the decisions a tracking backend makes — dispositions, visit lease over an application-owned store, active intervals, presence; no database |
75
77
  | `stitchkit/release` | browser **and** server | evolving | a page follows the release it was built for — `createReleaseMarker` on the server, `createReleaseWatcher` in the browser, the `X-Build-Id` header and a socket event between them |
@@ -81,6 +83,7 @@ own, recorded as an ADR.
81
83
  | `stitchkit/testing` | tests on Bun or Node | stable | in-process generated clients over a real Fetch handler, plus the managed-resource conformance kit |
82
84
  | `stitchkit/declaration` | browser + build and deployment tooling (Bun or Node) | evolving | `ProjectDeclarationSchema` — the one machine-readable statement a repository makes about itself |
83
85
  | `stitchkit/react` | browser + server rendering | stable | `createCursorQuery`, `createCacheBridge`, QueryClient and `ApiError` retry policy |
86
+ | `stitchkit/react/keyboard` | browser | evolving | which part of a screen gets a key — ordered layers, one Escape per level, list zones with real focus; see [keyboard guide](keyboard.md) |
84
87
  | `stitchkit/agent-runtime` | server | evolving<br>_redefined in 22 of the 45 minors since 0.56.2, most recently 0.97.0_ | optional durable conversation/run loop, history, models, prompts, fencing and events |
85
88
  | `stitchkit/agent-runtime/sandbox` | server | evolving | optional Linux Bubblewrap sessions, durable workspaces and a host HTTP credential gateway; see [sandbox guide](sandbox.md) |
86
89
  | `stitchkit/agent-runtime/testing` | tests on Bun or Node | evolving | credential-free replay, scripted provider faults, deterministic race controls and the agent-store conformance kit |
@@ -203,6 +206,7 @@ map — feature → packages:
203
206
  | MCP host/client tests | `@modelcontextprotocol/client` |
204
207
  | MCP Apps UI widgets | `@modelcontextprotocol/ext-apps` |
205
208
  | React data layer (`stitchkit/react`) | `@tanstack/react-query` `react-query-kit` |
209
+ | Keyboard layers and list zones (`stitchkit/react/keyboard`) | `react` |
206
210
  | MaxMind GeoIP (`stitchkit/geo`) | `maxmind` |
207
211
  | Browser OAuth client (`stitchkit/oauth`) | — |
208
212
  | Google OIDC verifier (`stitchkit/google`) | `google-auth-library` |
@@ -211,6 +215,8 @@ map — feature → packages:
211
215
  | Socket.IO client | `socket.io-client` (runtime peer; unrelated root declarations remain peer-free) |
212
216
  | grammY lifecycle adapters (`stitchkit/application/grammy`) | `grammy` |
213
217
  | Telegram platform primitives (`stitchkit/telegram`) | — (peer-free) |
218
+ | Telegram message markup (`stitchkit/telegram/html`) | — (peer-free) |
219
+ | Telegram screens (`stitchkit/telegram/screens`) | `grammy` (types only; the bot brings it) |
214
220
  | OpenTelemetry gauges (`stitchkit/application/opentelemetry`) | `@opentelemetry/api` |
215
221
 
216
222
  ```bash
@@ -0,0 +1,35 @@
1
+ # stitchkit/telegram/html — stitchkit docs slice
2
+
3
+ > Generated from the `docs/` tree by `bun run gen:llms`. The index of all slices is
4
+ > `llms.txt` in this package.
5
+
6
+ Contains: API reference: `stitchkit/telegram/html` (docs/api/reference.md)
7
+
8
+ Also read:
9
+ - see `llms/telegram.txt` for the Telegram bots guide
10
+
11
+ ==============================================================================
12
+ # API reference: `stitchkit/telegram/html` (docs/api/reference.md)
13
+ ==============================================================================
14
+
15
+ ## `stitchkit/telegram/html`
16
+
17
+ Telegram's HTML parse mode as a tree: any markup in, markup Telegram accepts
18
+ out, cut to its limits by what the reader sees. No peer and no DOM — the same
19
+ parse serves a server send and a browser preview. → ADR 0213 —
20
+ [guide](../guide/telegram.md#message-markup)
21
+
22
+ | Export | Kind | Summary |
23
+ |--------|------|---------|
24
+ | `parseTelegramHtml` | function | markup into nodes Telegram accepts: synonyms renamed (`strong` → `b`), block tags into line breaks, a link only with an `http`/`https`/`tg`/`mailto` `href`, an element where Telegram forbids it kept as text, a closing tag closing what is open inside it; never throws |
25
+ | `TelegramHtmlNode` / `TelegramHtmlElement` / `TelegramHtmlTextNode` / `TelegramHtmlTag` / `TelegramHtmlStyle` | _type_ | text nodes hold decoded text; elements `b` `i` `u` `s` `tg-spoiler` `code`, `a` with `href`, `pre` with `language`, `blockquote` with `expandable`, `tg-emoji` with `emojiId`, `tg-time` with `unix` and `format` |
26
+ | `renderTelegramHtml` | function | nodes back to markup Telegram parses into exactly them |
27
+ | `sanitizeTelegramHtml` | function | any markup as markup Telegram accepts — `renderTelegramHtml(parseTelegramHtml(markup))` |
28
+ | `splitTelegramHtml` / `TelegramHtmlLimitOptions` | function / _type_ | parts within `limit` visible characters (default 4096), cut at a paragraph, a line, a space, else anywhere but inside a character or a custom emoji; an element across a cut is closed and reopened with its attributes; nothing visible is no parts |
29
+ | `truncateTelegramHtml` / `TelegramHtmlTruncateOptions` | function / _type_ | one part: cut to `limit` with `ellipsis` (`…`) inside it, still valid |
30
+ | `telegramHtmlText` | function | the text a reader sees, entities decoded |
31
+ | `checkTelegramHtml` / `TelegramHtmlCheck` / `TelegramHtmlProblem` | function / _type_ | the first thing Telegram would refuse, unrepaired — `bare-character` / `unsupported-tag` / `unmatched-end-tag` / `unclosed-tag` — and the visible text before it |
32
+ | `escapeTelegramHtml` | function | `&`, `<`, `>` and `"` escaped for Telegram's HTML |
33
+ | `TELEGRAM_TEXT_LIMIT` / `TELEGRAM_CAPTION_LIMIT` | constant | 4096 and 1024, counted after the markup is parsed, in UTF-16 code units |
34
+
35
+ ---