taskchef 8.6.2 → 8.7.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 (630) hide show
  1. package/.codex-plugin/plugin.json +1 -1
  2. package/README.md +8 -4
  3. package/assets/README.md +19 -0
  4. package/assets/taskchef-app-rounded.png +0 -0
  5. package/assets/taskchef-avatar-square.png +0 -0
  6. package/assets/taskchef-avatar-square.svg +8 -0
  7. package/assets/taskchef-sidebar-dark.svg +6 -0
  8. package/assets/taskchef-sidebar-light.svg +6 -0
  9. package/assets/taskchef-sidebar.svg +6 -0
  10. package/docs/advanced-guide.md +20 -29
  11. package/docs/dashboard-ui.md +15 -0
  12. package/docs/diagrams/taskchef-next-reply-lookup.drawio +38 -0
  13. package/docs/diagrams/taskchef-next-reply-lookup.drawio.png +0 -0
  14. package/docs/diagrams/taskchef-next-storage.drawio +41 -0
  15. package/docs/diagrams/taskchef-next-storage.drawio.png +0 -0
  16. package/docs/images/taskchef-pr-icon-comparison.jpg +0 -0
  17. package/docs/images/taskchef-unified-status-popup.jpg +0 -0
  18. package/docs/mcp-app.md +261 -6
  19. package/docs/spec.md +24 -69
  20. package/docs/taskchef-next-github.md +206 -0
  21. package/docs/taskchef-next-status-research.md +102 -0
  22. package/docs/taskchef-next-storage.md +167 -0
  23. package/docs/workflows.md +4 -2
  24. package/node_modules/@napi-rs/keyring/LICENSE +21 -0
  25. package/node_modules/@napi-rs/keyring/README.md +45 -0
  26. package/node_modules/@napi-rs/keyring/index.d.ts +232 -0
  27. package/node_modules/@napi-rs/keyring/index.js +706 -0
  28. package/node_modules/@napi-rs/keyring/keytar.d.ts +53 -0
  29. package/node_modules/@napi-rs/keyring/keytar.js +26 -0
  30. package/node_modules/@napi-rs/keyring/package.json +126 -0
  31. package/node_modules/@napi-rs/keyring-darwin-arm64/README.md +3 -0
  32. package/node_modules/@napi-rs/keyring-darwin-arm64/keyring.darwin-arm64.node +0 -0
  33. package/node_modules/@napi-rs/keyring-darwin-arm64/package.json +32 -0
  34. package/node_modules/@napi-rs/keyring-darwin-x64/README.md +3 -0
  35. package/node_modules/@napi-rs/keyring-darwin-x64/keyring.darwin-x64.node +0 -0
  36. package/node_modules/@napi-rs/keyring-darwin-x64/package.json +32 -0
  37. package/node_modules/@napi-rs/keyring-linux-arm64-gnu/README.md +3 -0
  38. package/node_modules/@napi-rs/keyring-linux-arm64-gnu/keyring.linux-arm64-gnu.node +0 -0
  39. package/node_modules/@napi-rs/keyring-linux-arm64-gnu/package.json +35 -0
  40. package/node_modules/@napi-rs/keyring-linux-arm64-musl/README.md +3 -0
  41. package/node_modules/@napi-rs/keyring-linux-arm64-musl/keyring.linux-arm64-musl.node +0 -0
  42. package/node_modules/@napi-rs/keyring-linux-arm64-musl/package.json +35 -0
  43. package/node_modules/@napi-rs/keyring-linux-x64-gnu/README.md +3 -0
  44. package/node_modules/@napi-rs/keyring-linux-x64-gnu/keyring.linux-x64-gnu.node +0 -0
  45. package/node_modules/@napi-rs/keyring-linux-x64-gnu/package.json +35 -0
  46. package/node_modules/@napi-rs/keyring-linux-x64-musl/README.md +3 -0
  47. package/node_modules/@napi-rs/keyring-linux-x64-musl/keyring.linux-x64-musl.node +0 -0
  48. package/node_modules/@napi-rs/keyring-linux-x64-musl/package.json +35 -0
  49. package/node_modules/@napi-rs/keyring-win32-arm64-msvc/README.md +3 -0
  50. package/node_modules/@napi-rs/keyring-win32-arm64-msvc/keyring.win32-arm64-msvc.node +0 -0
  51. package/node_modules/@napi-rs/keyring-win32-arm64-msvc/package.json +32 -0
  52. package/node_modules/@napi-rs/keyring-win32-x64-msvc/README.md +3 -0
  53. package/node_modules/@napi-rs/keyring-win32-x64-msvc/keyring.win32-x64-msvc.node +0 -0
  54. package/node_modules/@napi-rs/keyring-win32-x64-msvc/package.json +32 -0
  55. package/node_modules/@types/debug/LICENSE +21 -0
  56. package/node_modules/@types/debug/README.md +69 -0
  57. package/node_modules/@types/debug/index.d.ts +50 -0
  58. package/node_modules/@types/debug/package.json +58 -0
  59. package/node_modules/@types/mdast/LICENSE +21 -0
  60. package/node_modules/@types/mdast/README.md +15 -0
  61. package/node_modules/@types/mdast/index.d.ts +1123 -0
  62. package/node_modules/@types/mdast/package.json +47 -0
  63. package/node_modules/@types/ms/LICENSE +21 -0
  64. package/node_modules/@types/ms/README.md +82 -0
  65. package/node_modules/@types/ms/index.d.ts +63 -0
  66. package/node_modules/@types/ms/package.json +26 -0
  67. package/node_modules/@types/unist/LICENSE +21 -0
  68. package/node_modules/@types/unist/README.md +15 -0
  69. package/node_modules/@types/unist/index.d.ts +119 -0
  70. package/node_modules/@types/unist/package.json +60 -0
  71. package/node_modules/character-entities/index.d.ts +6 -0
  72. package/node_modules/character-entities/index.js +2132 -0
  73. package/node_modules/character-entities/license +22 -0
  74. package/node_modules/character-entities/package.json +78 -0
  75. package/node_modules/character-entities/readme.md +152 -0
  76. package/node_modules/decode-named-character-reference/index.d.ts +13 -0
  77. package/node_modules/decode-named-character-reference/index.d.ts.map +1 -0
  78. package/node_modules/decode-named-character-reference/index.dom.d.ts +6 -0
  79. package/node_modules/decode-named-character-reference/index.dom.d.ts.map +1 -0
  80. package/node_modules/decode-named-character-reference/index.dom.js +32 -0
  81. package/node_modules/decode-named-character-reference/index.js +19 -0
  82. package/node_modules/decode-named-character-reference/license +22 -0
  83. package/node_modules/decode-named-character-reference/package.json +90 -0
  84. package/node_modules/decode-named-character-reference/readme.md +136 -0
  85. package/node_modules/dequal/dist/index.js +86 -0
  86. package/node_modules/dequal/dist/index.min.js +1 -0
  87. package/node_modules/dequal/dist/index.mjs +84 -0
  88. package/node_modules/dequal/index.d.ts +1 -0
  89. package/node_modules/dequal/license +21 -0
  90. package/node_modules/dequal/lite/index.d.ts +1 -0
  91. package/node_modules/dequal/lite/index.js +31 -0
  92. package/node_modules/dequal/lite/index.min.js +1 -0
  93. package/node_modules/dequal/lite/index.mjs +29 -0
  94. package/node_modules/dequal/package.json +57 -0
  95. package/node_modules/dequal/readme.md +112 -0
  96. package/node_modules/devlop/lib/default.js +9 -0
  97. package/node_modules/devlop/lib/development.d.ts +84 -0
  98. package/node_modules/devlop/lib/development.js +238 -0
  99. package/node_modules/devlop/license +22 -0
  100. package/node_modules/devlop/package.json +80 -0
  101. package/node_modules/devlop/readme.md +360 -0
  102. package/node_modules/mdast-util-from-markdown/dev/index.d.ts +23 -0
  103. package/node_modules/mdast-util-from-markdown/dev/index.js +2 -0
  104. package/node_modules/mdast-util-from-markdown/dev/lib/index.d.ts +53 -0
  105. package/node_modules/mdast-util-from-markdown/dev/lib/index.d.ts.map +1 -0
  106. package/node_modules/mdast-util-from-markdown/dev/lib/index.js +1348 -0
  107. package/node_modules/mdast-util-from-markdown/dev/lib/types.d.ts +295 -0
  108. package/node_modules/mdast-util-from-markdown/dev/lib/types.js +2 -0
  109. package/node_modules/mdast-util-from-markdown/index.d.ts +23 -0
  110. package/node_modules/mdast-util-from-markdown/index.js +2 -0
  111. package/node_modules/mdast-util-from-markdown/lib/index.d.ts +53 -0
  112. package/node_modules/mdast-util-from-markdown/lib/index.js +1177 -0
  113. package/node_modules/mdast-util-from-markdown/lib/types.d.ts +295 -0
  114. package/node_modules/mdast-util-from-markdown/lib/types.js +2 -0
  115. package/node_modules/mdast-util-from-markdown/license +22 -0
  116. package/node_modules/mdast-util-from-markdown/package.json +139 -0
  117. package/node_modules/mdast-util-from-markdown/readme.md +537 -0
  118. package/node_modules/mdast-util-to-string/index.d.ts +2 -0
  119. package/node_modules/mdast-util-to-string/index.js +5 -0
  120. package/node_modules/mdast-util-to-string/lib/index.d.ts +31 -0
  121. package/node_modules/mdast-util-to-string/lib/index.js +108 -0
  122. package/node_modules/mdast-util-to-string/license +22 -0
  123. package/node_modules/mdast-util-to-string/package.json +78 -0
  124. package/node_modules/mdast-util-to-string/readme.md +218 -0
  125. package/node_modules/micromark/dev/index.d.ts +82 -0
  126. package/node_modules/micromark/dev/index.d.ts.map +1 -0
  127. package/node_modules/micromark/dev/index.js +68 -0
  128. package/node_modules/micromark/dev/lib/compile.d.ts +16 -0
  129. package/node_modules/micromark/dev/lib/compile.d.ts.map +1 -0
  130. package/node_modules/micromark/dev/lib/compile.js +1162 -0
  131. package/node_modules/micromark/dev/lib/constructs.d.ts +73 -0
  132. package/node_modules/micromark/dev/lib/constructs.d.ts.map +1 -0
  133. package/node_modules/micromark/dev/lib/constructs.js +101 -0
  134. package/node_modules/micromark/dev/lib/create-tokenizer.d.ts +46 -0
  135. package/node_modules/micromark/dev/lib/create-tokenizer.d.ts.map +1 -0
  136. package/node_modules/micromark/dev/lib/create-tokenizer.js +718 -0
  137. package/node_modules/micromark/dev/lib/initialize/content.d.ts +4 -0
  138. package/node_modules/micromark/dev/lib/initialize/content.d.ts.map +1 -0
  139. package/node_modules/micromark/dev/lib/initialize/content.js +99 -0
  140. package/node_modules/micromark/dev/lib/initialize/document.d.ts +10 -0
  141. package/node_modules/micromark/dev/lib/initialize/document.d.ts.map +1 -0
  142. package/node_modules/micromark/dev/lib/initialize/document.js +449 -0
  143. package/node_modules/micromark/dev/lib/initialize/flow.d.ts +4 -0
  144. package/node_modules/micromark/dev/lib/initialize/flow.d.ts.map +1 -0
  145. package/node_modules/micromark/dev/lib/initialize/flow.js +86 -0
  146. package/node_modules/micromark/dev/lib/initialize/text.d.ts +8 -0
  147. package/node_modules/micromark/dev/lib/initialize/text.d.ts.map +1 -0
  148. package/node_modules/micromark/dev/lib/initialize/text.js +248 -0
  149. package/node_modules/micromark/dev/lib/parse.d.ts +10 -0
  150. package/node_modules/micromark/dev/lib/parse.d.ts.map +1 -0
  151. package/node_modules/micromark/dev/lib/parse.js +58 -0
  152. package/node_modules/micromark/dev/lib/postprocess.d.ts +9 -0
  153. package/node_modules/micromark/dev/lib/postprocess.d.ts.map +1 -0
  154. package/node_modules/micromark/dev/lib/postprocess.js +19 -0
  155. package/node_modules/micromark/dev/lib/preprocess.d.ts +13 -0
  156. package/node_modules/micromark/dev/lib/preprocess.d.ts.map +1 -0
  157. package/node_modules/micromark/dev/lib/preprocess.js +139 -0
  158. package/node_modules/micromark/dev/stream.d.ts +35 -0
  159. package/node_modules/micromark/dev/stream.d.ts.map +1 -0
  160. package/node_modules/micromark/dev/stream.js +270 -0
  161. package/node_modules/micromark/index.d.ts +82 -0
  162. package/node_modules/micromark/index.d.ts.map +1 -0
  163. package/node_modules/micromark/index.js +60 -0
  164. package/node_modules/micromark/lib/compile.d.ts +16 -0
  165. package/node_modules/micromark/lib/compile.d.ts.map +1 -0
  166. package/node_modules/micromark/lib/compile.js +1068 -0
  167. package/node_modules/micromark/lib/constructs.d.ts +73 -0
  168. package/node_modules/micromark/lib/constructs.d.ts.map +1 -0
  169. package/node_modules/micromark/lib/constructs.js +85 -0
  170. package/node_modules/micromark/lib/create-tokenizer.d.ts +46 -0
  171. package/node_modules/micromark/lib/create-tokenizer.d.ts.map +1 -0
  172. package/node_modules/micromark/lib/create-tokenizer.js +613 -0
  173. package/node_modules/micromark/lib/initialize/content.d.ts +4 -0
  174. package/node_modules/micromark/lib/initialize/content.d.ts.map +1 -0
  175. package/node_modules/micromark/lib/initialize/content.js +79 -0
  176. package/node_modules/micromark/lib/initialize/document.d.ts +10 -0
  177. package/node_modules/micromark/lib/initialize/document.d.ts.map +1 -0
  178. package/node_modules/micromark/lib/initialize/document.js +370 -0
  179. package/node_modules/micromark/lib/initialize/flow.d.ts +4 -0
  180. package/node_modules/micromark/lib/initialize/flow.d.ts.map +1 -0
  181. package/node_modules/micromark/lib/initialize/flow.js +58 -0
  182. package/node_modules/micromark/lib/initialize/text.d.ts +8 -0
  183. package/node_modules/micromark/lib/initialize/text.d.ts.map +1 -0
  184. package/node_modules/micromark/lib/initialize/text.js +216 -0
  185. package/node_modules/micromark/lib/parse.d.ts +10 -0
  186. package/node_modules/micromark/lib/parse.d.ts.map +1 -0
  187. package/node_modules/micromark/lib/parse.js +56 -0
  188. package/node_modules/micromark/lib/postprocess.d.ts +9 -0
  189. package/node_modules/micromark/lib/postprocess.d.ts.map +1 -0
  190. package/node_modules/micromark/lib/postprocess.js +18 -0
  191. package/node_modules/micromark/lib/preprocess.d.ts +13 -0
  192. package/node_modules/micromark/lib/preprocess.d.ts.map +1 -0
  193. package/node_modules/micromark/lib/preprocess.js +112 -0
  194. package/node_modules/micromark/license +22 -0
  195. package/node_modules/micromark/package.json +87 -0
  196. package/node_modules/micromark/readme.md +488 -0
  197. package/node_modules/micromark/stream.d.ts +35 -0
  198. package/node_modules/micromark/stream.d.ts.map +1 -0
  199. package/node_modules/micromark/stream.js +256 -0
  200. package/node_modules/micromark-core-commonmark/dev/index.d.ts +23 -0
  201. package/node_modules/micromark-core-commonmark/dev/index.d.ts.map +1 -0
  202. package/node_modules/micromark-core-commonmark/dev/index.js +22 -0
  203. package/node_modules/micromark-core-commonmark/dev/lib/attention.d.ts +4 -0
  204. package/node_modules/micromark-core-commonmark/dev/lib/attention.d.ts.map +1 -0
  205. package/node_modules/micromark-core-commonmark/dev/lib/attention.js +294 -0
  206. package/node_modules/micromark-core-commonmark/dev/lib/autolink.d.ts +4 -0
  207. package/node_modules/micromark-core-commonmark/dev/lib/autolink.d.ts.map +1 -0
  208. package/node_modules/micromark-core-commonmark/dev/lib/autolink.js +269 -0
  209. package/node_modules/micromark-core-commonmark/dev/lib/blank-line.d.ts +4 -0
  210. package/node_modules/micromark-core-commonmark/dev/lib/blank-line.d.ts.map +1 -0
  211. package/node_modules/micromark-core-commonmark/dev/lib/blank-line.js +62 -0
  212. package/node_modules/micromark-core-commonmark/dev/lib/block-quote.d.ts +4 -0
  213. package/node_modules/micromark-core-commonmark/dev/lib/block-quote.d.ts.map +1 -0
  214. package/node_modules/micromark-core-commonmark/dev/lib/block-quote.js +162 -0
  215. package/node_modules/micromark-core-commonmark/dev/lib/character-escape.d.ts +4 -0
  216. package/node_modules/micromark-core-commonmark/dev/lib/character-escape.d.ts.map +1 -0
  217. package/node_modules/micromark-core-commonmark/dev/lib/character-escape.js +69 -0
  218. package/node_modules/micromark-core-commonmark/dev/lib/character-reference.d.ts +4 -0
  219. package/node_modules/micromark-core-commonmark/dev/lib/character-reference.d.ts.map +1 -0
  220. package/node_modules/micromark-core-commonmark/dev/lib/character-reference.js +166 -0
  221. package/node_modules/micromark-core-commonmark/dev/lib/code-fenced.d.ts +4 -0
  222. package/node_modules/micromark-core-commonmark/dev/lib/code-fenced.d.ts.map +1 -0
  223. package/node_modules/micromark-core-commonmark/dev/lib/code-fenced.js +472 -0
  224. package/node_modules/micromark-core-commonmark/dev/lib/code-indented.d.ts +4 -0
  225. package/node_modules/micromark-core-commonmark/dev/lib/code-indented.d.ts.map +1 -0
  226. package/node_modules/micromark-core-commonmark/dev/lib/code-indented.js +178 -0
  227. package/node_modules/micromark-core-commonmark/dev/lib/code-text.d.ts +4 -0
  228. package/node_modules/micromark-core-commonmark/dev/lib/code-text.d.ts.map +1 -0
  229. package/node_modules/micromark-core-commonmark/dev/lib/code-text.js +257 -0
  230. package/node_modules/micromark-core-commonmark/dev/lib/content.d.ts +7 -0
  231. package/node_modules/micromark-core-commonmark/dev/lib/content.d.ts.map +1 -0
  232. package/node_modules/micromark-core-commonmark/dev/lib/content.js +185 -0
  233. package/node_modules/micromark-core-commonmark/dev/lib/definition.d.ts +4 -0
  234. package/node_modules/micromark-core-commonmark/dev/lib/definition.d.ts.map +1 -0
  235. package/node_modules/micromark-core-commonmark/dev/lib/definition.js +295 -0
  236. package/node_modules/micromark-core-commonmark/dev/lib/hard-break-escape.d.ts +4 -0
  237. package/node_modules/micromark-core-commonmark/dev/lib/hard-break-escape.d.ts.map +1 -0
  238. package/node_modules/micromark-core-commonmark/dev/lib/hard-break-escape.js +65 -0
  239. package/node_modules/micromark-core-commonmark/dev/lib/heading-atx.d.ts +4 -0
  240. package/node_modules/micromark-core-commonmark/dev/lib/heading-atx.d.ts.map +1 -0
  241. package/node_modules/micromark-core-commonmark/dev/lib/heading-atx.js +229 -0
  242. package/node_modules/micromark-core-commonmark/dev/lib/html-flow.d.ts +4 -0
  243. package/node_modules/micromark-core-commonmark/dev/lib/html-flow.d.ts.map +1 -0
  244. package/node_modules/micromark-core-commonmark/dev/lib/html-flow.js +938 -0
  245. package/node_modules/micromark-core-commonmark/dev/lib/html-text.d.ts +4 -0
  246. package/node_modules/micromark-core-commonmark/dev/lib/html-text.d.ts.map +1 -0
  247. package/node_modules/micromark-core-commonmark/dev/lib/html-text.js +783 -0
  248. package/node_modules/micromark-core-commonmark/dev/lib/label-end.d.ts +4 -0
  249. package/node_modules/micromark-core-commonmark/dev/lib/label-end.d.ts.map +1 -0
  250. package/node_modules/micromark-core-commonmark/dev/lib/label-end.js +658 -0
  251. package/node_modules/micromark-core-commonmark/dev/lib/label-start-image.d.ts +4 -0
  252. package/node_modules/micromark-core-commonmark/dev/lib/label-start-image.d.ts.map +1 -0
  253. package/node_modules/micromark-core-commonmark/dev/lib/label-start-image.js +119 -0
  254. package/node_modules/micromark-core-commonmark/dev/lib/label-start-link.d.ts +4 -0
  255. package/node_modules/micromark-core-commonmark/dev/lib/label-start-link.d.ts.map +1 -0
  256. package/node_modules/micromark-core-commonmark/dev/lib/label-start-link.js +71 -0
  257. package/node_modules/micromark-core-commonmark/dev/lib/line-ending.d.ts +4 -0
  258. package/node_modules/micromark-core-commonmark/dev/lib/line-ending.d.ts.map +1 -0
  259. package/node_modules/micromark-core-commonmark/dev/lib/line-ending.js +34 -0
  260. package/node_modules/micromark-core-commonmark/dev/lib/list.d.ts +4 -0
  261. package/node_modules/micromark-core-commonmark/dev/lib/list.d.ts.map +1 -0
  262. package/node_modules/micromark-core-commonmark/dev/lib/list.js +306 -0
  263. package/node_modules/micromark-core-commonmark/dev/lib/partial-non-lazy-continuation.d.ts +4 -0
  264. package/node_modules/micromark-core-commonmark/dev/lib/partial-non-lazy-continuation.d.ts.map +1 -0
  265. package/node_modules/micromark-core-commonmark/dev/lib/partial-non-lazy-continuation.js +67 -0
  266. package/node_modules/micromark-core-commonmark/dev/lib/setext-underline.d.ts +4 -0
  267. package/node_modules/micromark-core-commonmark/dev/lib/setext-underline.d.ts.map +1 -0
  268. package/node_modules/micromark-core-commonmark/dev/lib/setext-underline.js +213 -0
  269. package/node_modules/micromark-core-commonmark/dev/lib/thematic-break.d.ts +4 -0
  270. package/node_modules/micromark-core-commonmark/dev/lib/thematic-break.d.ts.map +1 -0
  271. package/node_modules/micromark-core-commonmark/dev/lib/thematic-break.js +120 -0
  272. package/node_modules/micromark-core-commonmark/index.d.ts +23 -0
  273. package/node_modules/micromark-core-commonmark/index.d.ts.map +1 -0
  274. package/node_modules/micromark-core-commonmark/index.js +22 -0
  275. package/node_modules/micromark-core-commonmark/lib/attention.d.ts +4 -0
  276. package/node_modules/micromark-core-commonmark/lib/attention.d.ts.map +1 -0
  277. package/node_modules/micromark-core-commonmark/lib/attention.js +231 -0
  278. package/node_modules/micromark-core-commonmark/lib/autolink.d.ts +4 -0
  279. package/node_modules/micromark-core-commonmark/lib/autolink.d.ts.map +1 -0
  280. package/node_modules/micromark-core-commonmark/lib/autolink.js +233 -0
  281. package/node_modules/micromark-core-commonmark/lib/blank-line.d.ts +4 -0
  282. package/node_modules/micromark-core-commonmark/lib/blank-line.d.ts.map +1 -0
  283. package/node_modules/micromark-core-commonmark/lib/blank-line.js +61 -0
  284. package/node_modules/micromark-core-commonmark/lib/block-quote.d.ts +4 -0
  285. package/node_modules/micromark-core-commonmark/lib/block-quote.d.ts.map +1 -0
  286. package/node_modules/micromark-core-commonmark/lib/block-quote.js +143 -0
  287. package/node_modules/micromark-core-commonmark/lib/character-escape.d.ts +4 -0
  288. package/node_modules/micromark-core-commonmark/lib/character-escape.d.ts.map +1 -0
  289. package/node_modules/micromark-core-commonmark/lib/character-escape.js +64 -0
  290. package/node_modules/micromark-core-commonmark/lib/character-reference.d.ts +4 -0
  291. package/node_modules/micromark-core-commonmark/lib/character-reference.d.ts.map +1 -0
  292. package/node_modules/micromark-core-commonmark/lib/character-reference.js +149 -0
  293. package/node_modules/micromark-core-commonmark/lib/code-fenced.d.ts +4 -0
  294. package/node_modules/micromark-core-commonmark/lib/code-fenced.d.ts.map +1 -0
  295. package/node_modules/micromark-core-commonmark/lib/code-fenced.js +422 -0
  296. package/node_modules/micromark-core-commonmark/lib/code-indented.d.ts +4 -0
  297. package/node_modules/micromark-core-commonmark/lib/code-indented.d.ts.map +1 -0
  298. package/node_modules/micromark-core-commonmark/lib/code-indented.js +159 -0
  299. package/node_modules/micromark-core-commonmark/lib/code-text.d.ts +4 -0
  300. package/node_modules/micromark-core-commonmark/lib/code-text.d.ts.map +1 -0
  301. package/node_modules/micromark-core-commonmark/lib/code-text.js +225 -0
  302. package/node_modules/micromark-core-commonmark/lib/content.d.ts +7 -0
  303. package/node_modules/micromark-core-commonmark/lib/content.d.ts.map +1 -0
  304. package/node_modules/micromark-core-commonmark/lib/content.js +163 -0
  305. package/node_modules/micromark-core-commonmark/lib/definition.d.ts +4 -0
  306. package/node_modules/micromark-core-commonmark/lib/definition.d.ts.map +1 -0
  307. package/node_modules/micromark-core-commonmark/lib/definition.js +254 -0
  308. package/node_modules/micromark-core-commonmark/lib/hard-break-escape.d.ts +4 -0
  309. package/node_modules/micromark-core-commonmark/lib/hard-break-escape.d.ts.map +1 -0
  310. package/node_modules/micromark-core-commonmark/lib/hard-break-escape.js +60 -0
  311. package/node_modules/micromark-core-commonmark/lib/heading-atx.d.ts +4 -0
  312. package/node_modules/micromark-core-commonmark/lib/heading-atx.d.ts.map +1 -0
  313. package/node_modules/micromark-core-commonmark/lib/heading-atx.js +191 -0
  314. package/node_modules/micromark-core-commonmark/lib/html-flow.d.ts +4 -0
  315. package/node_modules/micromark-core-commonmark/lib/html-flow.d.ts.map +1 -0
  316. package/node_modules/micromark-core-commonmark/lib/html-flow.js +827 -0
  317. package/node_modules/micromark-core-commonmark/lib/html-text.d.ts +4 -0
  318. package/node_modules/micromark-core-commonmark/lib/html-text.d.ts.map +1 -0
  319. package/node_modules/micromark-core-commonmark/lib/html-text.js +678 -0
  320. package/node_modules/micromark-core-commonmark/lib/label-end.d.ts +4 -0
  321. package/node_modules/micromark-core-commonmark/lib/label-end.d.ts.map +1 -0
  322. package/node_modules/micromark-core-commonmark/lib/label-end.js +561 -0
  323. package/node_modules/micromark-core-commonmark/lib/label-start-image.d.ts +4 -0
  324. package/node_modules/micromark-core-commonmark/lib/label-start-image.d.ts.map +1 -0
  325. package/node_modules/micromark-core-commonmark/lib/label-start-image.js +110 -0
  326. package/node_modules/micromark-core-commonmark/lib/label-start-link.d.ts +4 -0
  327. package/node_modules/micromark-core-commonmark/lib/label-start-link.d.ts.map +1 -0
  328. package/node_modules/micromark-core-commonmark/lib/label-start-link.js +63 -0
  329. package/node_modules/micromark-core-commonmark/lib/line-ending.d.ts +4 -0
  330. package/node_modules/micromark-core-commonmark/lib/line-ending.d.ts.map +1 -0
  331. package/node_modules/micromark-core-commonmark/lib/line-ending.js +33 -0
  332. package/node_modules/micromark-core-commonmark/lib/list.d.ts +4 -0
  333. package/node_modules/micromark-core-commonmark/lib/list.d.ts.map +1 -0
  334. package/node_modules/micromark-core-commonmark/lib/list.js +213 -0
  335. package/node_modules/micromark-core-commonmark/lib/partial-non-lazy-continuation.d.ts +4 -0
  336. package/node_modules/micromark-core-commonmark/lib/partial-non-lazy-continuation.d.ts.map +1 -0
  337. package/node_modules/micromark-core-commonmark/lib/partial-non-lazy-continuation.js +61 -0
  338. package/node_modules/micromark-core-commonmark/lib/setext-underline.d.ts +4 -0
  339. package/node_modules/micromark-core-commonmark/lib/setext-underline.d.ts.map +1 -0
  340. package/node_modules/micromark-core-commonmark/lib/setext-underline.js +187 -0
  341. package/node_modules/micromark-core-commonmark/lib/thematic-break.d.ts +4 -0
  342. package/node_modules/micromark-core-commonmark/lib/thematic-break.d.ts.map +1 -0
  343. package/node_modules/micromark-core-commonmark/lib/thematic-break.js +102 -0
  344. package/node_modules/micromark-core-commonmark/license +22 -0
  345. package/node_modules/micromark-core-commonmark/package.json +62 -0
  346. package/node_modules/micromark-core-commonmark/readme.md +171 -0
  347. package/node_modules/micromark-factory-destination/dev/index.d.ts +42 -0
  348. package/node_modules/micromark-factory-destination/dev/index.d.ts.map +1 -0
  349. package/node_modules/micromark-factory-destination/dev/index.js +255 -0
  350. package/node_modules/micromark-factory-destination/index.d.ts +42 -0
  351. package/node_modules/micromark-factory-destination/index.d.ts.map +1 -0
  352. package/node_modules/micromark-factory-destination/index.js +206 -0
  353. package/node_modules/micromark-factory-destination/license +22 -0
  354. package/node_modules/micromark-factory-destination/package.json +57 -0
  355. package/node_modules/micromark-factory-destination/readme.md +234 -0
  356. package/node_modules/micromark-factory-label/dev/index.d.ts +37 -0
  357. package/node_modules/micromark-factory-label/dev/index.d.ts.map +1 -0
  358. package/node_modules/micromark-factory-label/dev/index.js +172 -0
  359. package/node_modules/micromark-factory-label/index.d.ts +37 -0
  360. package/node_modules/micromark-factory-label/index.d.ts.map +1 -0
  361. package/node_modules/micromark-factory-label/index.js +148 -0
  362. package/node_modules/micromark-factory-label/license +22 -0
  363. package/node_modules/micromark-factory-label/package.json +60 -0
  364. package/node_modules/micromark-factory-label/readme.md +224 -0
  365. package/node_modules/micromark-factory-space/dev/index.d.ts +60 -0
  366. package/node_modules/micromark-factory-space/dev/index.d.ts.map +1 -0
  367. package/node_modules/micromark-factory-space/dev/index.js +122 -0
  368. package/node_modules/micromark-factory-space/index.d.ts +60 -0
  369. package/node_modules/micromark-factory-space/index.d.ts.map +1 -0
  370. package/node_modules/micromark-factory-space/index.js +116 -0
  371. package/node_modules/micromark-factory-space/license +22 -0
  372. package/node_modules/micromark-factory-space/package.json +46 -0
  373. package/node_modules/micromark-factory-space/readme.md +225 -0
  374. package/node_modules/micromark-factory-title/dev/index.d.ts +36 -0
  375. package/node_modules/micromark-factory-title/dev/index.d.ts.map +1 -0
  376. package/node_modules/micromark-factory-title/dev/index.js +169 -0
  377. package/node_modules/micromark-factory-title/index.d.ts +36 -0
  378. package/node_modules/micromark-factory-title/index.d.ts.map +1 -0
  379. package/node_modules/micromark-factory-title/index.js +158 -0
  380. package/node_modules/micromark-factory-title/license +22 -0
  381. package/node_modules/micromark-factory-title/package.json +58 -0
  382. package/node_modules/micromark-factory-title/readme.md +229 -0
  383. package/node_modules/micromark-factory-whitespace/dev/index.d.ts +22 -0
  384. package/node_modules/micromark-factory-whitespace/dev/index.d.ts.map +1 -0
  385. package/node_modules/micromark-factory-whitespace/dev/index.js +53 -0
  386. package/node_modules/micromark-factory-whitespace/index.d.ts +22 -0
  387. package/node_modules/micromark-factory-whitespace/index.d.ts.map +1 -0
  388. package/node_modules/micromark-factory-whitespace/index.js +44 -0
  389. package/node_modules/micromark-factory-whitespace/license +22 -0
  390. package/node_modules/micromark-factory-whitespace/package.json +57 -0
  391. package/node_modules/micromark-factory-whitespace/readme.md +205 -0
  392. package/node_modules/micromark-util-character/dev/index.d.ts +195 -0
  393. package/node_modules/micromark-util-character/dev/index.d.ts.map +1 -0
  394. package/node_modules/micromark-util-character/dev/index.js +252 -0
  395. package/node_modules/micromark-util-character/index.d.ts +195 -0
  396. package/node_modules/micromark-util-character/index.d.ts.map +1 -0
  397. package/node_modules/micromark-util-character/index.js +246 -0
  398. package/node_modules/micromark-util-character/license +22 -0
  399. package/node_modules/micromark-util-character/package.json +57 -0
  400. package/node_modules/micromark-util-character/readme.md +446 -0
  401. package/node_modules/micromark-util-chunked/dev/index.d.ts +41 -0
  402. package/node_modules/micromark-util-chunked/dev/index.d.ts.map +1 -0
  403. package/node_modules/micromark-util-chunked/dev/index.js +89 -0
  404. package/node_modules/micromark-util-chunked/index.d.ts +41 -0
  405. package/node_modules/micromark-util-chunked/index.d.ts.map +1 -0
  406. package/node_modules/micromark-util-chunked/index.js +81 -0
  407. package/node_modules/micromark-util-chunked/license +22 -0
  408. package/node_modules/micromark-util-chunked/package.json +57 -0
  409. package/node_modules/micromark-util-chunked/readme.md +219 -0
  410. package/node_modules/micromark-util-classify-character/dev/index.d.ts +18 -0
  411. package/node_modules/micromark-util-classify-character/dev/index.d.ts.map +1 -0
  412. package/node_modules/micromark-util-classify-character/dev/index.js +38 -0
  413. package/node_modules/micromark-util-classify-character/index.d.ts +18 -0
  414. package/node_modules/micromark-util-classify-character/index.d.ts.map +1 -0
  415. package/node_modules/micromark-util-classify-character/index.js +27 -0
  416. package/node_modules/micromark-util-classify-character/license +22 -0
  417. package/node_modules/micromark-util-classify-character/package.json +59 -0
  418. package/node_modules/micromark-util-classify-character/readme.md +205 -0
  419. package/node_modules/micromark-util-combine-extensions/index.d.ts +22 -0
  420. package/node_modules/micromark-util-combine-extensions/index.d.ts.map +1 -0
  421. package/node_modules/micromark-util-combine-extensions/index.js +143 -0
  422. package/node_modules/micromark-util-combine-extensions/license +22 -0
  423. package/node_modules/micromark-util-combine-extensions/package.json +52 -0
  424. package/node_modules/micromark-util-combine-extensions/readme.md +201 -0
  425. package/node_modules/micromark-util-decode-numeric-character-reference/dev/index.d.ts +16 -0
  426. package/node_modules/micromark-util-decode-numeric-character-reference/dev/index.d.ts.map +1 -0
  427. package/node_modules/micromark-util-decode-numeric-character-reference/dev/index.js +42 -0
  428. package/node_modules/micromark-util-decode-numeric-character-reference/index.d.ts +16 -0
  429. package/node_modules/micromark-util-decode-numeric-character-reference/index.d.ts.map +1 -0
  430. package/node_modules/micromark-util-decode-numeric-character-reference/index.js +32 -0
  431. package/node_modules/micromark-util-decode-numeric-character-reference/license +22 -0
  432. package/node_modules/micromark-util-decode-numeric-character-reference/package.json +59 -0
  433. package/node_modules/micromark-util-decode-numeric-character-reference/readme.md +184 -0
  434. package/node_modules/micromark-util-decode-string/dev/index.d.ts +14 -0
  435. package/node_modules/micromark-util-decode-string/dev/index.d.ts.map +1 -0
  436. package/node_modules/micromark-util-decode-string/dev/index.js +53 -0
  437. package/node_modules/micromark-util-decode-string/index.d.ts +14 -0
  438. package/node_modules/micromark-util-decode-string/index.d.ts.map +1 -0
  439. package/node_modules/micromark-util-decode-string/index.js +45 -0
  440. package/node_modules/micromark-util-decode-string/license +22 -0
  441. package/node_modules/micromark-util-decode-string/package.json +63 -0
  442. package/node_modules/micromark-util-decode-string/readme.md +179 -0
  443. package/node_modules/micromark-util-edit-map/dev/index.d.ts +78 -0
  444. package/node_modules/micromark-util-edit-map/dev/index.d.ts.map +1 -0
  445. package/node_modules/micromark-util-edit-map/dev/index.js +237 -0
  446. package/node_modules/micromark-util-edit-map/index.d.ts +78 -0
  447. package/node_modules/micromark-util-edit-map/index.d.ts.map +1 -0
  448. package/node_modules/micromark-util-edit-map/index.js +225 -0
  449. package/node_modules/micromark-util-edit-map/license +22 -0
  450. package/node_modules/micromark-util-edit-map/package.json +48 -0
  451. package/node_modules/micromark-util-edit-map/readme.md +234 -0
  452. package/node_modules/micromark-util-encode/index.d.ts +14 -0
  453. package/node_modules/micromark-util-encode/index.d.ts.map +1 -0
  454. package/node_modules/micromark-util-encode/index.js +33 -0
  455. package/node_modules/micromark-util-encode/license +22 -0
  456. package/node_modules/micromark-util-encode/package.json +47 -0
  457. package/node_modules/micromark-util-encode/readme.md +176 -0
  458. package/node_modules/micromark-util-html-tag-name/index.d.ts +30 -0
  459. package/node_modules/micromark-util-html-tag-name/index.d.ts.map +1 -0
  460. package/node_modules/micromark-util-html-tag-name/index.js +93 -0
  461. package/node_modules/micromark-util-html-tag-name/license +22 -0
  462. package/node_modules/micromark-util-html-tag-name/package.json +47 -0
  463. package/node_modules/micromark-util-html-tag-name/readme.md +193 -0
  464. package/node_modules/micromark-util-normalize-identifier/dev/index.d.ts +21 -0
  465. package/node_modules/micromark-util-normalize-identifier/dev/index.d.ts.map +1 -0
  466. package/node_modules/micromark-util-normalize-identifier/dev/index.js +38 -0
  467. package/node_modules/micromark-util-normalize-identifier/index.d.ts +21 -0
  468. package/node_modules/micromark-util-normalize-identifier/index.d.ts.map +1 -0
  469. package/node_modules/micromark-util-normalize-identifier/index.js +33 -0
  470. package/node_modules/micromark-util-normalize-identifier/license +22 -0
  471. package/node_modules/micromark-util-normalize-identifier/package.json +58 -0
  472. package/node_modules/micromark-util-normalize-identifier/readme.md +187 -0
  473. package/node_modules/micromark-util-resolve-all/index.d.ts +22 -0
  474. package/node_modules/micromark-util-resolve-all/index.d.ts.map +1 -0
  475. package/node_modules/micromark-util-resolve-all/index.js +32 -0
  476. package/node_modules/micromark-util-resolve-all/license +22 -0
  477. package/node_modules/micromark-util-resolve-all/package.json +48 -0
  478. package/node_modules/micromark-util-resolve-all/readme.md +238 -0
  479. package/node_modules/micromark-util-sanitize-uri/dev/index.d.ts +36 -0
  480. package/node_modules/micromark-util-sanitize-uri/dev/index.d.ts.map +1 -0
  481. package/node_modules/micromark-util-sanitize-uri/dev/index.js +124 -0
  482. package/node_modules/micromark-util-sanitize-uri/index.d.ts +36 -0
  483. package/node_modules/micromark-util-sanitize-uri/index.d.ts.map +1 -0
  484. package/node_modules/micromark-util-sanitize-uri/index.js +107 -0
  485. package/node_modules/micromark-util-sanitize-uri/license +22 -0
  486. package/node_modules/micromark-util-sanitize-uri/package.json +59 -0
  487. package/node_modules/micromark-util-sanitize-uri/readme.md +214 -0
  488. package/node_modules/micromark-util-subtokenize/dev/index.d.ts +12 -0
  489. package/node_modules/micromark-util-subtokenize/dev/index.d.ts.map +1 -0
  490. package/node_modules/micromark-util-subtokenize/dev/index.js +284 -0
  491. package/node_modules/micromark-util-subtokenize/dev/lib/splice-buffer.d.ts +161 -0
  492. package/node_modules/micromark-util-subtokenize/dev/lib/splice-buffer.d.ts.map +1 -0
  493. package/node_modules/micromark-util-subtokenize/dev/lib/splice-buffer.js +288 -0
  494. package/node_modules/micromark-util-subtokenize/index.d.ts +12 -0
  495. package/node_modules/micromark-util-subtokenize/index.d.ts.map +1 -0
  496. package/node_modules/micromark-util-subtokenize/index.js +228 -0
  497. package/node_modules/micromark-util-subtokenize/lib/splice-buffer.d.ts +161 -0
  498. package/node_modules/micromark-util-subtokenize/lib/splice-buffer.d.ts.map +1 -0
  499. package/node_modules/micromark-util-subtokenize/lib/splice-buffer.js +251 -0
  500. package/node_modules/micromark-util-subtokenize/license +22 -0
  501. package/node_modules/micromark-util-subtokenize/package.json +60 -0
  502. package/node_modules/micromark-util-subtokenize/readme.md +181 -0
  503. package/node_modules/micromark-util-symbol/lib/codes.d.ts +139 -0
  504. package/node_modules/micromark-util-symbol/lib/codes.d.ts.map +1 -0
  505. package/node_modules/micromark-util-symbol/lib/codes.js +158 -0
  506. package/node_modules/micromark-util-symbol/lib/constants.d.ts +37 -0
  507. package/node_modules/micromark-util-symbol/lib/constants.d.ts.map +1 -0
  508. package/node_modules/micromark-util-symbol/lib/constants.js +44 -0
  509. package/node_modules/micromark-util-symbol/lib/default.d.ts +5 -0
  510. package/node_modules/micromark-util-symbol/lib/default.d.ts.map +1 -0
  511. package/node_modules/micromark-util-symbol/lib/default.js +4 -0
  512. package/node_modules/micromark-util-symbol/lib/types.d.ts +106 -0
  513. package/node_modules/micromark-util-symbol/lib/types.d.ts.map +1 -0
  514. package/node_modules/micromark-util-symbol/lib/types.js +453 -0
  515. package/node_modules/micromark-util-symbol/lib/values.d.ts +102 -0
  516. package/node_modules/micromark-util-symbol/lib/values.d.ts.map +1 -0
  517. package/node_modules/micromark-util-symbol/lib/values.js +109 -0
  518. package/node_modules/micromark-util-symbol/license +22 -0
  519. package/node_modules/micromark-util-symbol/package.json +43 -0
  520. package/node_modules/micromark-util-symbol/readme.md +168 -0
  521. package/node_modules/micromark-util-types/index.d.ts +1315 -0
  522. package/node_modules/micromark-util-types/index.js +2 -0
  523. package/node_modules/micromark-util-types/license +22 -0
  524. package/node_modules/micromark-util-types/package.json +37 -0
  525. package/node_modules/micromark-util-types/readme.md +151 -0
  526. package/node_modules/smol-toml/LICENSE +24 -0
  527. package/node_modules/smol-toml/README.md +605 -0
  528. package/node_modules/smol-toml/dist/date.d.ts +41 -0
  529. package/node_modules/smol-toml/dist/date.js +156 -0
  530. package/node_modules/smol-toml/dist/error.d.ts +37 -0
  531. package/node_modules/smol-toml/dist/error.js +67 -0
  532. package/node_modules/smol-toml/dist/extract.js +289 -0
  533. package/node_modules/smol-toml/dist/index.cjs +770 -0
  534. package/node_modules/smol-toml/dist/index.d.ts +44 -0
  535. package/node_modules/smol-toml/dist/index.js +34 -0
  536. package/node_modules/smol-toml/dist/parse.d.ts +73 -0
  537. package/node_modules/smol-toml/dist/parse.js +164 -0
  538. package/node_modules/smol-toml/dist/primitive.js +151 -0
  539. package/node_modules/smol-toml/dist/stringify.d.ts +32 -0
  540. package/node_modules/smol-toml/dist/stringify.js +219 -0
  541. package/node_modules/smol-toml/dist/struct.js +156 -0
  542. package/node_modules/smol-toml/dist/util.d.ts +43 -0
  543. package/node_modules/smol-toml/dist/util.js +57 -0
  544. package/node_modules/smol-toml/package.json +68 -0
  545. package/node_modules/unist-util-stringify-position/index.d.ts +1 -0
  546. package/node_modules/unist-util-stringify-position/index.js +1 -0
  547. package/node_modules/unist-util-stringify-position/lib/index.d.ts +61 -0
  548. package/node_modules/unist-util-stringify-position/lib/index.js +84 -0
  549. package/node_modules/unist-util-stringify-position/license +22 -0
  550. package/node_modules/unist-util-stringify-position/package.json +80 -0
  551. package/node_modules/unist-util-stringify-position/readme.md +206 -0
  552. package/package.json +27 -8
  553. package/scripts/prepare-github-keyring.js +29 -0
  554. package/src/codex-app.js +20 -2
  555. package/src/codex-pricing.js +79 -0
  556. package/src/codex-projects.js +90 -0
  557. package/src/codex-session-scanner.js +748 -0
  558. package/src/codex-session-usage.js +332 -0
  559. package/src/dashboard/app.js +2 -2
  560. package/src/dashboard/dist/app.js +135 -60
  561. package/src/dashboard/dist/app.js.map +1 -1
  562. package/src/dashboard/dist/styles.css +1 -1
  563. package/src/dashboard/react/components/AnimatedUsage.test.tsx +78 -0
  564. package/src/dashboard/react/components/CardStatusIcons.test.tsx +146 -0
  565. package/src/dashboard/react/components/CardStatusIcons.tsx +138 -0
  566. package/src/dashboard/react/components/ImageGallery.tsx +22 -0
  567. package/src/dashboard/react/components/ModelSettings.test.tsx +4 -4
  568. package/src/dashboard/react/components/ModelSettings.tsx +1 -1
  569. package/src/dashboard/react/components/OpenChatButton.test.tsx +19 -0
  570. package/src/dashboard/react/components/OpenChatButton.tsx +2 -1
  571. package/src/dashboard/react/components/PrGlyph.tsx +35 -0
  572. package/src/dashboard/react/components/PullRequestBadges.tsx +18 -0
  573. package/src/dashboard/react/components/QueuedPrompts.test.tsx +42 -0
  574. package/src/dashboard/react/components/QueuedPrompts.tsx +34 -0
  575. package/src/dashboard/react/components/RelativeTime.test.tsx +100 -0
  576. package/src/dashboard/react/components/RelativeTime.tsx +46 -11
  577. package/src/dashboard/react/components/ReplyCover.test.tsx +30 -0
  578. package/src/dashboard/react/components/ReplyCover.tsx +33 -0
  579. package/src/dashboard/react/components/ReplyImagePreview.test.tsx +90 -0
  580. package/src/dashboard/react/components/ReplyImagePreview.tsx +34 -0
  581. package/src/dashboard/react/components/ReplyMarkdown.test.tsx +108 -0
  582. package/src/dashboard/react/components/ReplyMarkdown.tsx +112 -0
  583. package/src/dashboard/react/components/SavedUsageFormat.test.tsx +11 -0
  584. package/src/dashboard/react/components/SavedUsageFormat.tsx +68 -0
  585. package/src/dashboard/react/components/StatusBadge.tsx +2 -2
  586. package/src/dashboard/react/components/TaskBoard.test.tsx +256 -1
  587. package/src/dashboard/react/components/TaskBoard.tsx +136 -28
  588. package/src/dashboard/react/components/TaskCard.tsx +7 -4
  589. package/src/dashboard/react/components/TaskDetail.test.tsx +92 -1
  590. package/src/dashboard/react/components/TaskDetail.tsx +37 -10
  591. package/src/dashboard/react/components/TurnContent.test.tsx +44 -0
  592. package/src/dashboard/react/components/TurnContent.tsx +53 -0
  593. package/src/dashboard/react/components/UsagePanel.tsx +2 -2
  594. package/src/dashboard/react/image-cache.test.ts +23 -0
  595. package/src/dashboard/react/image-cache.ts +33 -0
  596. package/src/dashboard/react/presentation.test.tsx +31 -0
  597. package/src/dashboard/react/styles.css +126 -2
  598. package/src/dashboard/react/types.ts +55 -1
  599. package/src/dashboard/time.js +39 -0
  600. package/src/dashboard.js +4 -3
  601. package/src/data/codex-pricing.json +537 -0
  602. package/src/mcp-app/dist/index.html +216 -112
  603. package/src/mcp-app/react/AppearancePicker.test.tsx +50 -0
  604. package/src/mcp-app/react/AppearancePicker.tsx +56 -0
  605. package/src/mcp-app/react/GitHubConnection.test.tsx +78 -0
  606. package/src/mcp-app/react/GitHubConnection.tsx +78 -0
  607. package/src/mcp-app/react/NextNotificationCenter.test.tsx +121 -0
  608. package/src/mcp-app/react/NextNotificationCenter.tsx +94 -0
  609. package/src/mcp-app/react/NextTaskDetail.test.tsx +124 -0
  610. package/src/mcp-app/react/NextTaskDetail.tsx +147 -0
  611. package/src/mcp-app/react/ProjectPicker.tsx +49 -0
  612. package/src/mcp-app/react/main.test.tsx +499 -49
  613. package/src/mcp-app/react/main.tsx +359 -86
  614. package/src/mcp-app/react/styles.css +186 -9
  615. package/src/mcp-app/react/themes.test.ts +34 -0
  616. package/src/mcp-app/react/themes.ts +112 -0
  617. package/src/mcp-app.js +238 -120
  618. package/src/mcp.js +8 -1
  619. package/src/next-cache.js +16 -0
  620. package/src/next-github.js +273 -0
  621. package/src/next-notifications.js +90 -0
  622. package/src/recorded-usage-tracker.js +92 -0
  623. package/src/reply-image.js +65 -0
  624. package/src/usage-tracker.js +7 -6
  625. package/src/usage.js +7 -355
  626. package/src/user-prompt.js +55 -0
  627. package/src/version.js +0 -1
  628. package/node_modules/proper-lockfile/CHANGELOG.md +0 -108
  629. package/node_modules/which/CHANGELOG.md +0 -166
  630. package/src/resolve-ccusage.js +0 -17
@@ -4,95 +4,204 @@ import { act, fireEvent, render, screen, waitFor, within, cleanup } from "@testi
4
4
  import { useState } from "react";
5
5
  import type { Task } from "../../dashboard/react/types";
6
6
 
7
- const server = vi.hoisted(() => ({ call: vi.fn() }));
7
+ const server = vi.hoisted(() => ({ call: vi.fn(), openLink: vi.fn(), displayMode: "unknown", hostContextChanged: undefined as undefined | ((context: { displayMode: string }) => void), teardown: undefined as undefined | (() => object) }));
8
+ vi.mock("./ProjectPicker", () => ({ ProjectPicker: ({ data, value, onChange }: { data: Array<{label: string; value: string}>; value: string; onChange: (value: string) => void }) => <select aria-label="Project" value={value} onChange={(event) => onChange(event.target.value)}>{data.map((item) => <option key={item.value} value={item.value}>{item.label}</option>)}</select> }));
8
9
  vi.mock("@mantine/core", async () => {
9
10
  const React = await import("react");
10
11
  const wrap = (tag: string) => ({ children, component, ...props }: Record<string, unknown>) => React.createElement(component as string || tag, { "aria-label": props["aria-label"], role: props.role, id: props.id }, children as React.ReactNode);
11
12
  return {
13
+ createTheme: (theme: unknown) => theme,
12
14
  ActionIcon: ({ children, onClick, ...props }: Record<string, unknown>) => React.createElement("button", { "aria-label": props["aria-label"], onClick }, children as React.ReactNode),
13
- Alert: wrap("div"), Box: wrap("div"), Button: wrap("button"), Group: wrap("div"),
15
+ Modal: ({ opened, children }: { opened: boolean; children: React.ReactNode }) => opened ? <div role="dialog">{children}</div> : null,
16
+ Alert: wrap("div"), Box: wrap("div"), Button: ({ children, onClick, disabled }: {children: React.ReactNode; onClick?: () => void; disabled?: boolean}) => <button onClick={onClick} disabled={disabled}>{children}</button>, Group: wrap("div"),
14
17
  MantineProvider: ({ children }: { children: React.ReactNode }) => children,
18
+ TextInput: ({ value, onChange, ...props }: { value: string; onChange: React.ChangeEventHandler<HTMLInputElement>; "aria-label": string }) => <input aria-label={props["aria-label"]} value={value} onChange={onChange} />,
15
19
  Paper: wrap("div"), Stack: wrap("div"), Text: wrap("p"), Title: wrap("h2"),
16
20
  SegmentedControl: ({ data, onChange, value, ...props }: { data: Array<{ label: string; value: string }>; onChange: (value: string) => void; value: string; "aria-label": string }) => React.createElement("div", { role: "radiogroup", "aria-label": props["aria-label"] }, data.map((item) => React.createElement("button", { key: item.value, role: "radio", "aria-checked": item.value === value, onClick: () => onChange(item.value) }, item.label))),
21
+ Switch: ({ label, checked, onChange }: { label: string; checked: boolean; onChange: (event: React.ChangeEvent<HTMLInputElement>) => void }) => <label>{label}<input type="checkbox" checked={checked} onChange={onChange} /></label>,
17
22
  Select: ({ data, onChange, value, ...props }: { data: Array<{ label: string; value: string }>; onChange: (value: string) => void; value: string; "aria-label": string }) => React.createElement("select", { "aria-label": props["aria-label"], value, onChange: (event: React.ChangeEvent<HTMLSelectElement>) => onChange(event.target.value) }, data.map((item) => React.createElement("option", { key: item.value, value: item.value }, item.label))),
18
23
  };
19
24
  });
20
25
  vi.mock("@modelcontextprotocol/ext-apps", () => ({
21
26
  App: class {
27
+ openLink(input: { url: string }) { return server.openLink(input); }
22
28
  connect() { return Promise.resolve(); }
29
+ getHostContext() { return { displayMode: server.displayMode }; }
23
30
  callServerTool(input: { name: string; arguments: Record<string, unknown> }) { return server.call(input); }
31
+ set onhostcontextchanged(handler: (context: { displayMode: string }) => void) { server.hostContextChanged = handler; }
32
+ set onteardown(handler: () => object) { server.teardown = handler; }
24
33
  },
25
34
  }));
26
35
  vi.mock("../../dashboard/react/components/TaskCard", () => ({
27
36
  TaskCard: ({ task, onOpenCodex, onOpenDetail }: { task: Task; onOpenCodex: (task: Task) => void; onOpenDetail: (task: Task) => void }) => <article><button onClick={() => onOpenDetail(task)}>{task.title}</button><button onClick={() => onOpenCodex(task)}>Open chat for {task.title}</button></article>,
28
37
  }));
29
38
  vi.mock("../../dashboard/react/components/TaskBoard", () => ({
30
- TaskBoard: ({ tasks, onOpenDetail }: { tasks: Task[]; onOpenDetail: (task: Task) => void }) => <section aria-label="Task board">{tasks.map((task) => <button key={task.id} onClick={() => onOpenDetail(task)}>{task.title}</button>)}</section>,
39
+ TaskBoard: ({ showCardUsage, tasks, onOpenDetail, onOpenCodex, doneNotice, onVisibleTasksChange }: { showCardUsage?: boolean; onVisibleTasksChange?: (ids: string[]) => void; doneNotice?: React.ReactNode; tasks: Task[]; onOpenDetail: (task: Task) => void; onOpenCodex: (task: Task) => void }) => <section aria-label="Task board" data-card-usage={String(showCardUsage)}><button onClick={() => onVisibleTasksChange?.([tasks[0].id])}>Report card in view</button>{doneNotice}{tasks.map((task) => <article key={task.id}><button onClick={() => onOpenDetail(task)}>{task.title}</button><button onClick={() => onOpenCodex(task)}>Open chat for {task.title}</button></article>)}</section>,
31
40
  }));
32
- vi.mock("../../dashboard/react/components/TaskDetail", () => ({
33
- TaskDetail: ({ task, opened, onClose, onTransition }: {
34
- task: Task | null; opened: boolean; onClose: () => void;
41
+ vi.mock("./NextTaskDetail", () => ({
42
+ NextTaskDetail: ({ task, opened, usageLoading, onClose, onTransition, extraActions }: {
43
+ task: Task | null; opened: boolean; usageLoading?: boolean; onClose: () => void; extraActions?: React.ReactNode;
35
44
  onTransition: (status: "completed" | "failed", actionId: string) => Promise<unknown>;
36
45
  }) => {
37
46
  const [confirm, setConfirm] = useState(false);
38
- return opened && task ? <section aria-label="Task detail"><h2>{task.title}</h2><p>{task.status}</p><button onClick={onClose}>Close</button><button onClick={() => setConfirm(true)}>Mark completed</button>{confirm && <button onClick={() => { void onTransition("completed", crypto.randomUUID()); setConfirm(false); }}>Confirm</button>}</section> : null;
47
+ return opened && task ? <section aria-label="Task detail"><h2>{task.title}</h2><p>{task.status}</p><section aria-label="Usage">{task.sessionUsage ? "Usage loaded" : usageLoading ? "Loading…" : "Usage unavailable."}</section>{extraActions}<button onClick={onClose}>Close</button><button onClick={() => setConfirm(true)}>Mark completed</button>{confirm && <button onClick={() => { void onTransition("completed", crypto.randomUUID()); setConfirm(false); }}>Confirm</button>}</section> : null;
39
48
  },
40
49
  }));
41
50
 
42
51
  import { TaskChefApp } from "./main";
43
52
 
44
- const task = (id: string, status: Task["status"] = "working", updatedAt = "2026-10-01T00:00:00Z", project = "Example"): Task => ({
53
+ const task = (id: string, status: Task["status"] = "working", updatedAt = new Date().toISOString(), project = "Example"): Task => ({
45
54
  id, title: `Task ${id}`, instruction: "Do the work", status, updatedAt, createdAt: updatedAt,
46
55
  project: { name: project, path: `/${project}`, githubRepos: [] }, summary: "Initial summary",
47
56
  threadId: null, turnRef: null, turnId: null, lastResult: null, latestTurn: null,
48
57
  });
49
58
 
59
+ let notificationState = { revision: 0, items: [] as import("./NextNotificationCenter").NextNotification[] };
60
+ let visibility: {showExec: boolean; showCli: boolean; showArchived: boolean; showCardUsage?: boolean} = { showExec: false, showCli: false, showArchived: false };
50
61
  let tasks: Task[];
62
+ let registeredProjects: Array<{id: string; name: string; path: string; githubRepos: string[]}>;
51
63
  let details: Map<string, Task>;
52
64
  let transition: () => Promise<unknown>;
53
65
  let detailFailure: string | null;
54
66
  beforeEach(() => {
67
+ server.displayMode = "unknown";
68
+ server.hostContextChanged = undefined;
69
+ server.teardown = undefined;
55
70
  window.localStorage.clear();
71
+ server.openLink.mockResolvedValue({ isError: false });
72
+ visibility = { showExec: false, showCli: false, showArchived: false };
73
+ notificationState = { revision: 0, items: [] };
56
74
  tasks = [task("one")];
75
+ registeredProjects = [];
57
76
  details = new Map(tasks.map((item) => [item.id, item]));
58
77
  transition = async () => ({ structuredContent: { task: task("one", "completed") } });
59
78
  detailFailure = null;
60
79
  server.call.mockImplementation(({ name, arguments: args }) => {
61
- if (name === "taskchef_app_snapshot") return Promise.resolve({ structuredContent: { snapshot: { tasks, healthy: true } } });
80
+ if (name === "taskchef_app_snapshot") return Promise.resolve({ structuredContent: { notifications: notificationState, settingsUrl: "codex://plugins/taskchef-next?marketplacePath=%2Fexample", settings: visibility, snapshot: { tasks, projects: registeredProjects, healthy: true } } });
62
81
  if (name === "taskchef_app_task" && detailFailure) return Promise.resolve({ isError: true, content: [{ type: "text", text: detailFailure }] });
63
82
  if (name === "taskchef_app_task") return Promise.resolve({ structuredContent: { task: details.get(args.taskId as string) } });
83
+ if (name === "taskchef_app_notifications") {
84
+ if (args.action === "error") notificationState = { revision: notificationState.revision + 1, items: [{ id: `n${notificationState.revision}`, taskId: args.taskId ?? null, title: args.operation === "settings" ? "Could not open plugin settings" : "Could not open Task one", detail: String(args.error), read: false, timestamp: new Date().toISOString(), kind: "error" }, ...notificationState.items] };
85
+ if (args.action === "clear") notificationState = { revision: notificationState.revision + 1, items: [] };
86
+ if (args.action === "read_all" || args.action === "read") notificationState = { revision: notificationState.revision + 1, items: notificationState.items.map((item) => args.action === "read_all" || item.id === args.id ? { ...item, read: true } : item) };
87
+ return Promise.resolve({ structuredContent: { notifications: notificationState } });
88
+ }
64
89
  if (name === "taskchef_app_transition") return transition();
90
+ if (name === "taskchef_app_open_settings") return Promise.resolve({ structuredContent: { message: "Requested settings." } });
65
91
  if (name === "taskchef_app_open_chat") return Promise.resolve({ isError: true, content: [{ type: "text", text: "Codex could not be opened." }] });
66
92
  throw new Error(`Unexpected tool: ${name}`);
67
93
  });
68
94
  });
69
- afterEach(() => { cleanup(); server.call.mockReset(); });
95
+ afterEach(() => { cleanup(); server.call.mockReset(); vi.restoreAllMocks(); });
70
96
  function mount() { render(<TaskChefApp />); }
71
97
 
72
- test("switches list and board and filters by project, date, and status with contextual counts", async () => {
73
- tasks = [task("one", "working", new Date().toISOString(), "Alpha"), task("two", "completed", "2026-01-01T00:00:00Z", "Alpha"), task("three", "failed", new Date().toISOString(), "Beta")];
74
- details = new Map(tasks.map((item) => [item.id, item]));
98
+ test("lifecycle events log readable single strings without suppressing repeats", () => {
99
+ const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
100
+ const { unmount } = render(<TaskChefApp />);
101
+ const details = JSON.stringify({ visibility: document.visibilityState, displayMode: "unknown" });
102
+ expect(warn).toHaveBeenCalledWith(`[TaskChef] TaskChef lifecycle: mounted ${details}`);
103
+ warn.mockClear();
104
+ fireEvent(document, new Event("visibilitychange"));
105
+ expect(warn).toHaveBeenCalledWith(`[TaskChef] TaskChef lifecycle: visibilitychange ${details}`);
106
+ fireEvent(window, new Event("pagehide"));
107
+ fireEvent(window, new Event("pagehide"));
108
+ expect(warn.mock.calls.filter(([message]) => message === `[TaskChef] TaskChef lifecycle: pagehide ${details}`)).toHaveLength(2);
109
+ act(() => server.hostContextChanged?.({ displayMode: "fullscreen" }));
110
+ expect(warn).toHaveBeenCalledWith(`[TaskChef] TaskChef lifecycle: host context changed ${JSON.stringify({ visibility: document.visibilityState, displayMode: "fullscreen" })}`);
111
+ act(() => server.teardown?.());
112
+ expect(warn).toHaveBeenCalledWith(`[TaskChef] TaskChef lifecycle: host teardown ${details}`);
113
+ unmount();
114
+ expect(warn).toHaveBeenCalledWith(`[TaskChef] TaskChef lifecycle: unmounted ${details}`);
115
+ expect(warn.mock.calls.every((call) => call.length === 1 && typeof call[0] === "string" && !call[0].includes("[object Object]"))).toBe(true);
116
+ warn.mockClear();
117
+ fireEvent(window, new Event("pagehide"));
118
+ expect(warn).not.toHaveBeenCalled();
119
+ });
120
+
121
+ test("shows only Board, ignores a saved List choice, and filters project and date", async () => {
122
+ window.localStorage.setItem("taskchef.app.view", "list");
123
+ tasks = [task("one", "working", new Date().toISOString(), "Alpha"), task("two", "completed", "2026-01-01T00:00:00Z", "Alpha"), task("three", "interrupted", new Date().toISOString(), "Beta")];
75
124
  mount();
76
- expect(await screen.findByRole("button", { name: "Task one" })).toBeVisible();
77
- expect(screen.getByText("Tasks: 3 of 3")).toBeVisible();
78
- fireEvent.change(screen.getByRole("combobox", { name: "Project" }), { target: { value: "Alpha" } });
79
- expect(screen.getByText("Tasks: 2 of 3")).toBeVisible();
80
- expect(screen.getByRole("radio", { name: "All 2" })).toBeVisible();
81
- fireEvent.change(screen.getByRole("combobox", { name: "Updated" }), { target: { value: "24h" } });
82
- expect(screen.getByText("Tasks: 1 of 3")).toBeVisible();
83
- fireEvent.change(screen.getByRole("combobox", { name: "Updated" }), { target: { value: "all" } });
84
- fireEvent.change(screen.getByRole("combobox", { name: "Project" }), { target: { value: "" } });
85
- fireEvent.click(screen.getByRole("radio", { name: /Failed/ }));
86
- expect(screen.getByText("Tasks: 1 of 3")).toBeVisible();
87
- expect(screen.queryByRole("button", { name: "Task one" })).not.toBeInTheDocument();
88
- fireEvent.click(screen.getByRole("radio", { name: "Board" }));
89
- expect(within(screen.getByRole("region", { name: "Task board" })).getAllByRole("button")).toHaveLength(3);
125
+ await screen.findByRole("button", { name: "Task one" });
126
+ expect(screen.getByRole("region", { name: "Task board" })).toBeVisible();
127
+ expect(screen.queryByRole("radiogroup", { name: "View" })).not.toBeInTheDocument();
90
128
  expect(screen.queryByRole("radiogroup", { name: "Status" })).not.toBeInTheDocument();
91
- fireEvent.click(screen.getByRole("radio", { name: "List" }));
92
- expect(screen.getByRole("button", { name: "Task three" })).toBeVisible();
129
+ fireEvent.change(screen.getByRole("combobox", { name: "Project" }), { target: { value: "/Alpha" } });
130
+ expect(screen.queryByRole("button", { name: "Task three" })).not.toBeInTheDocument();
131
+ fireEvent.change(screen.getByRole("combobox", { name: "Updated" }), { target: { value: "24h" } });
93
132
  expect(screen.queryByRole("button", { name: "Task two" })).not.toBeInTheDocument();
94
133
  });
95
134
 
135
+ test("search finds chat text, projects, PR titles and repositories, combines filters, and keeps the query after refresh", async () => {
136
+ tasks = [task("one", "working", new Date().toISOString(), "Alpha"), task("two", "needs_input", new Date().toISOString(), "Beta"), task("old", "completed", "2026-01-01T00:00:00Z", "Alpha")];
137
+ tasks[0].title = "Fix login";
138
+ tasks[0].replyExcerpt = "First line\nSecond line\n**Database** migration passed";
139
+ tasks[1].replyExcerpt = "Database migration passed";
140
+ tasks[2].replyExcerpt = "Database migration passed";
141
+ tasks[0].project.githubRepos = ["acme/login-service"];
142
+ tasks[1].pullRequests = [{ url: "https://github.com/example/search-engine/pull/12", title: "Improve indexing", state: "open", checks: "unknown" }];
143
+ mount();
144
+ await screen.findByRole("button", { name: "Fix login" });
145
+ const input = screen.getByLabelText("Search Chats");
146
+ const callsBeforeTyping = server.call.mock.calls.length;
147
+ fireEvent.change(input, { target: { value: " LOGIN database " } });
148
+ expect(screen.getByRole("button", { name: "Fix login" })).toBeVisible();
149
+ expect(screen.queryByRole("button", { name: "Task two" })).not.toBeInTheDocument();
150
+ expect(server.call.mock.calls.length).toBe(callsBeforeTyping);
151
+ for (const query of ["Alpha", "login-service"]) {
152
+ fireEvent.change(input, { target: { value: query } });
153
+ expect(screen.getByRole("button", { name: "Fix login" })).toBeVisible();
154
+ expect(screen.queryByRole("button", { name: "Task two" })).not.toBeInTheDocument();
155
+ }
156
+ for (const query of ["Beta", "INDEXING", "search-engine", "example/search-engine", "beta indexing search-engine"]) {
157
+ fireEvent.change(input, { target: { value: query } });
158
+ expect(screen.getByRole("button", { name: "Task two" })).toBeVisible();
159
+ expect(screen.queryByRole("button", { name: "Fix login" })).not.toBeInTheDocument();
160
+ }
161
+ expect(server.call.mock.calls.length).toBe(callsBeforeTyping);
162
+ fireEvent.change(input, { target: { value: "migration" } });
163
+ expect(screen.getByRole("button", { name: "Task two" })).toBeVisible();
164
+ fireEvent.change(screen.getByRole("combobox", { name: "Project" }), { target: { value: "/Alpha" } });
165
+ expect(screen.queryByRole("button", { name: "Task two" })).not.toBeInTheDocument();
166
+ fireEvent.change(screen.getByRole("combobox", { name: "Updated" }), { target: { value: "24h" } });
167
+ expect(screen.queryByRole("button", { name: "Task old" })).not.toBeInTheDocument();
168
+ await act(async () => { fireEvent.click(screen.getByRole("button", { name: "Refresh" })); });
169
+ expect(input).toHaveValue("migration");
170
+ fireEvent.change(input, { target: { value: "not-a-match" } });
171
+ expect(within(screen.getByRole("region", { name: "Task board" })).queryAllByRole("article")).toHaveLength(0);
172
+ fireEvent.change(input, { target: { value: "" } });
173
+ expect(screen.getByRole("button", { name: "Fix login" })).toBeVisible();
174
+ });
175
+
176
+ test("Settings opens the installed plugin details link and reports failure", async () => {
177
+ mount(); await screen.findByRole("button", { name: "Task one" });
178
+ fireEvent.click(screen.getByRole("button", { name: "Settings" }));
179
+ await waitFor(() => expect(server.call).toHaveBeenCalledWith({ name: "taskchef_app_open_settings", arguments: {} }));
180
+ const original = server.call.getMockImplementation()!;
181
+ server.call.mockImplementation((input) => input.name === "taskchef_app_open_settings"
182
+ ? Promise.resolve({ isError: true, content: [{ type: "text", text: "Desktop opener failed." }] }) : original(input));
183
+ fireEvent.click(screen.getByRole("button", { name: "Settings" }));
184
+ expect(await screen.findByText("Could not open plugin settings")).toBeVisible();
185
+ expect(screen.queryByRole("alert")).not.toBeInTheDocument();
186
+ });
187
+
188
+ test("filters projects with the same name by directory", async () => {
189
+ const first = task("one", null, new Date().toISOString(), "project");
190
+ const second = task("two", null, new Date().toISOString(), "project");
191
+ first.project.path = "/work/first/project";
192
+ second.project.path = "/work/second/project";
193
+ tasks = [first, second];
194
+ mount();
195
+ expect(await screen.findByRole("button", { name: "Task one" })).toBeVisible();
196
+ const picker = screen.getByRole("combobox", { name: "Project" });
197
+ expect(within(picker).getByRole("option", { name: "project (/work/first/project)" })).toBeVisible();
198
+ expect(within(picker).getByRole("option", { name: "project (/work/second/project)" })).toBeVisible();
199
+ fireEvent.change(picker, { target: { value: "/work/second/project" } });
200
+ expect(screen.queryByRole("button", { name: "Task one" })).not.toBeInTheDocument();
201
+ expect(screen.getByRole("button", { name: "Task two" })).toBeVisible();
202
+ expect(within(screen.getByRole("region", { name: "Task board" })).getAllByRole("button", { name: /^Task / })).toHaveLength(1);
203
+ });
204
+
96
205
  test("refresh updates detail and removes a disappeared selection", async () => {
97
206
  mount();
98
207
  fireEvent.click(await screen.findByRole("button", { name: "Task one" }));
@@ -109,8 +218,8 @@ test("refresh sends the last revision and keeps selection detail current on unch
109
218
  let revision = 1;
110
219
  server.call.mockImplementation(({ name, arguments: args }) => {
111
220
  if (name === "taskchef_app_snapshot") return Promise.resolve({ structuredContent: args.revision === revision
112
- ? { unchanged: true, revision }
113
- : { snapshot: { tasks, healthy: true, revision } } });
221
+ ? { unchanged: true, revision, settings: visibility }
222
+ : { settings: visibility, snapshot: { tasks, healthy: true, revision } } });
114
223
  if (name === "taskchef_app_task") return Promise.resolve({ structuredContent: { task: details.get(args.taskId as string) } });
115
224
  throw new Error(`Unexpected tool: ${name}`);
116
225
  });
@@ -119,7 +228,7 @@ test("refresh sends the last revision and keeps selection detail current on unch
119
228
  details.set("one", task("one", "needs_input"));
120
229
  fireEvent.click(screen.getByRole("button", { name: "Refresh" }));
121
230
  await waitFor(() => expect(screen.getByRole("region", { name: "Task detail" })).toHaveTextContent("needs_input"));
122
- expect(server.call).toHaveBeenCalledWith({ name: "taskchef_app_snapshot", arguments: { revision: 1 } });
231
+ expect(server.call).toHaveBeenCalledWith({ name: "taskchef_app_snapshot", arguments: { revision: 1, force: true, date: "all", project: "", visibleTaskIds: [] } });
123
232
  tasks = [task("two")];
124
233
  revision = 2;
125
234
  fireEvent.click(screen.getByRole("button", { name: "Refresh" }));
@@ -132,7 +241,7 @@ test("an older selection detail cannot replace a newer refresh detail", async ()
132
241
  const first = new Promise((resolve) => { releaseFirst = resolve; });
133
242
  let detailCalls = 0;
134
243
  server.call.mockImplementation(({ name }) => {
135
- if (name === "taskchef_app_snapshot") return Promise.resolve({ structuredContent: { snapshot: { tasks, healthy: true } } });
244
+ if (name === "taskchef_app_snapshot") return Promise.resolve({ structuredContent: { notifications: notificationState, settingsUrl: "codex://plugins/taskchef-next?marketplacePath=%2Fexample", settings: visibility, snapshot: { tasks, projects: registeredProjects, healthy: true } } });
136
245
  if (name === "taskchef_app_task") return ++detailCalls === 1
137
246
  ? first
138
247
  : Promise.resolve({ structuredContent: { task: task("one", "needs_input") } });
@@ -147,32 +256,22 @@ test("an older selection detail cannot replace a newer refresh detail", async ()
147
256
  expect(screen.getByRole("region", { name: "Task detail" })).toHaveTextContent("needs_input");
148
257
  });
149
258
 
150
- test("shows a card Open chat failure without opening detail", async () => {
259
+ test("shows a card Open chat failure without hiding cards or opening detail", async () => {
151
260
  mount();
152
261
  fireEvent.click(await screen.findByRole("button", { name: "Open chat for Task one" }));
153
- expect(await screen.findByRole("alert")).toHaveTextContent("Codex could not be opened.");
262
+ expect(await screen.findByText(/Codex could not be opened/)).toBeVisible();
263
+ expect(screen.queryByRole("alert")).not.toBeInTheDocument();
264
+ expect(screen.getByRole("button", { name: "Task one" })).toBeVisible();
265
+ expect(screen.getByRole("button", { name: "Open chat for Task one" })).toBeVisible();
154
266
  expect(screen.queryByRole("region", { name: "Task detail" })).not.toBeInTheDocument();
155
267
  });
156
268
 
157
- test("transition conflict refreshes the current task", async () => {
158
- mount();
159
- fireEvent.click(await screen.findByRole("button", { name: "Task one" }));
160
- await screen.findByRole("region", { name: "Task detail" });
161
- transition = async () => {
162
- details.set("one", task("one", "failed", "2026-10-02T00:00:00Z"));
163
- throw new Error("Task changed concurrently.");
164
- };
165
- fireEvent.click(screen.getByRole("button", { name: "Mark completed" }));
166
- fireEvent.click(screen.getByRole("button", { name: "Confirm" }));
167
- await waitFor(() => expect(screen.getByRole("region", { name: "Task detail" })).toHaveTextContent("failed"));
168
- });
169
-
170
269
  test("an older selection response cannot replace the current task", async () => {
171
270
  tasks = [task("one"), task("two")];
172
271
  let release!: (value: unknown) => void;
173
272
  const first = new Promise((resolve) => { release = resolve; });
174
273
  server.call.mockImplementation(({ name, arguments: args }) => {
175
- if (name === "taskchef_app_snapshot") return Promise.resolve({ structuredContent: { snapshot: { tasks, healthy: true } } });
274
+ if (name === "taskchef_app_snapshot") return Promise.resolve({ structuredContent: { notifications: notificationState, settingsUrl: "codex://plugins/taskchef-next?marketplacePath=%2Fexample", settings: visibility, snapshot: { tasks, projects: registeredProjects, healthy: true } } });
176
275
  if (name === "taskchef_app_task") return args.taskId === "one" ? first : Promise.resolve({ structuredContent: { task: task("two") } });
177
276
  throw new Error(`Unexpected tool: ${name}`);
178
277
  });
@@ -183,3 +282,354 @@ test("an older selection response cannot replace the current task", async () =>
183
282
  release({ structuredContent: { task: task("one") } });
184
283
  await waitFor(() => expect(screen.getByRole("region", { name: "Task detail" })).toHaveTextContent("Task two"));
185
284
  });
285
+
286
+ test("keeps scan diagnostics out of the board", async () => {
287
+ server.call.mockImplementation(({ name }) => {
288
+ if (name === "taskchef_app_snapshot") return Promise.resolve({ structuredContent: { notifications: notificationState, settingsUrl: "codex://plugins/taskchef-next?marketplacePath=%2Fexample", settings: visibility, snapshot: {
289
+ tasks: [], healthy: true, revision: 1,
290
+ scan: { source: "database", mode: "database", checkedAt: "2026-10-05T00:00:00Z", intervalSeconds: 5,
291
+ indexedFiles: 8620, visibleFiles: 300, unreadFiles: 8320, errors: 0 },
292
+ } } });
293
+ throw new Error(`Unexpected tool: ${name}`);
294
+ });
295
+ mount();
296
+ await screen.findByRole("region", { name: "Task board" });
297
+ expect(screen.queryByText(/Read-only database/)).not.toBeInTheDocument();
298
+ expect(screen.queryByText(/shown of.*top-level chats/)).not.toBeInTheDocument();
299
+ expect(screen.queryByText(/checks every 5s/)).not.toBeInTheDocument();
300
+ expect(screen.queryByText(/title \(may contain user text\)/)).not.toBeInTheDocument();
301
+ });
302
+
303
+ test("shows an initial inventory failure without undefined scan counts", async () => {
304
+ server.call.mockImplementation(({ name }) => {
305
+ if (name === "taskchef_app_snapshot") return Promise.resolve({ structuredContent: { notifications: notificationState, settingsUrl: "codex://plugins/taskchef-next?marketplacePath=%2Fexample", settings: visibility, snapshot: {
306
+ tasks: [], healthy: false, revision: 0,
307
+ scan: { mode: "error", checkedAt: "2026-10-05T00:00:00Z", error: "EACCES" },
308
+ } } });
309
+ throw new Error(`Unexpected tool: ${name}`);
310
+ });
311
+ mount();
312
+ expect(await screen.findByRole("alert")).toHaveTextContent("cannot read the Codex databases");
313
+ expect(screen.queryByText(/Read-only database/)).not.toBeInTheDocument();
314
+ expect(screen.queryByText(/undefined/)).not.toBeInTheDocument();
315
+ expect(screen.queryByText(/shown of/)).not.toBeInTheDocument();
316
+ });
317
+
318
+ test("inline mode shows a compact recent-chat view", async () => {
319
+ server.displayMode = "inline";
320
+ tasks = [task("one"), task("two"), task("three"), task("four")];
321
+ mount();
322
+ expect(await screen.findByText("4 eligible top-level chats")).toBeVisible();
323
+ expect(screen.getByRole("button", { name: "Task one" })).toBeVisible();
324
+ expect(screen.queryByRole("button", { name: "Task four" })).not.toBeInTheDocument();
325
+ expect(screen.queryByRole("region", { name: "Task board" })).not.toBeInTheDocument();
326
+ expect(screen.queryByText(/View:.*document:/)).not.toBeInTheDocument();
327
+ });
328
+
329
+ test("inline mode loads once and begins polling when expanded", async () => {
330
+ server.displayMode = "inline";
331
+ const setInterval = vi.spyOn(window, "setInterval");
332
+ const clearInterval = vi.spyOn(window, "clearInterval");
333
+ const snapshotCalls = () => server.call.mock.calls.filter(([input]) => input.name === "taskchef_app_snapshot").length;
334
+ mount();
335
+ expect(await screen.findByText("1 eligible top-level chats")).toBeVisible();
336
+ expect(snapshotCalls()).toBe(1);
337
+ expect(setInterval.mock.calls.filter(([, delay]) => delay === 5000)).toHaveLength(0);
338
+ fireEvent.click(screen.getByRole("button", { name: "Refresh" }));
339
+ await waitFor(() => expect(snapshotCalls()).toBe(2));
340
+ server.displayMode = "fullscreen";
341
+ act(() => server.hostContextChanged?.({ displayMode: "fullscreen" }));
342
+ await waitFor(() => expect(snapshotCalls()).toBe(3));
343
+ const pollIndex = setInterval.mock.calls.findIndex(([, delay]) => delay === 5000);
344
+ expect(pollIndex).toBeGreaterThanOrEqual(0);
345
+ await act(async () => { (setInterval.mock.calls[pollIndex][0] as () => void)(); });
346
+ expect(snapshotCalls()).toBe(4);
347
+ server.displayMode = "inline";
348
+ act(() => server.hostContextChanged?.({ displayMode: "inline" }));
349
+ expect(clearInterval).toHaveBeenCalledWith(setInterval.mock.results[pollIndex].value);
350
+ });
351
+
352
+ test("shows the Node requirement when the scanner rejects its runtime", async () => {
353
+ server.call.mockResolvedValue({ structuredContent: { notifications: notificationState, settingsUrl: "codex://plugins/taskchef-next?marketplacePath=%2Fexample", settings: visibility, snapshot: {
354
+ tasks: [], healthy: false, revision: 0,
355
+ scan: { mode: "error", checkedAt: "2026-10-05T00:00:00Z", error: "TaskChef requires Node.js 22.18.0 or later for read-only SQLite (current: 22.17.9)." },
356
+ } } });
357
+ mount();
358
+ expect(await screen.findByRole("alert")).toHaveTextContent("TaskChef requires Node.js 22.18.0 or later");
359
+ });
360
+
361
+ test("a database failure after a healthy snapshot removes stale tasks", async () => {
362
+ mount();
363
+ expect(await screen.findByRole("button", { name: "Task one" })).toBeVisible();
364
+ server.call.mockImplementation(({ name }) => {
365
+ if (name === "taskchef_app_snapshot") return Promise.resolve({ structuredContent: { notifications: notificationState, settingsUrl: "codex://plugins/taskchef-next?marketplacePath=%2Fexample", settings: visibility, snapshot: {
366
+ tasks: [], healthy: false, revision: 2,
367
+ scan: { source: "database", mode: "error", checkedAt: new Date().toISOString() },
368
+ } } });
369
+ throw new Error(`Unexpected tool: ${name}`);
370
+ });
371
+ fireEvent.click(screen.getByRole("button", { name: "Refresh" }));
372
+ expect(await screen.findByRole("alert")).toHaveTextContent("cannot read the Codex databases");
373
+ expect(screen.queryByRole("button", { name: "Task one" })).not.toBeInTheDocument();
374
+ });
375
+
376
+
377
+ test("Done control calls the local mark tool with the displayed turn and refreshes", async () => {
378
+ const current = { ...task("one", "needs_input"), turnId: "turn-one", observed: { archive: false, lastTurnEvent: "completed", lastTurnEventAt: null, recentFileActivity: false } };
379
+ tasks = [current]; details = new Map([[current.id, current]]);
380
+ server.call.mockImplementation(async ({ name, arguments: args }) => {
381
+ if (name === "taskchef_app_set_done") {
382
+ const next = { ...current, status: args.done ? "completed" : "needs_input", manualDone: args.done } as Task;
383
+ tasks = [next]; details.set(current.id, next);
384
+ return { structuredContent: { task: next } };
385
+ }
386
+ if (name === "taskchef_app_snapshot") return { structuredContent: { notifications: notificationState, settingsUrl: "codex://plugins/taskchef-next?marketplacePath=%2Fexample", settings: visibility, snapshot: { tasks, projects: registeredProjects, healthy: true } } };
387
+ if (name === "taskchef_app_task") return { structuredContent: { task: details.get(current.id) } };
388
+ throw new Error(name);
389
+ });
390
+ mount();
391
+ fireEvent.click(await screen.findByRole("button", { name: "Task one" }));
392
+ fireEvent.click(await screen.findByRole("button", { name: "Mark Done" }));
393
+ await waitFor(() => expect(within(screen.getByRole("region", { name: "Task detail" })).getByText("completed")).toBeVisible());
394
+ expect(screen.queryByRole("button", { name: "Reopen" })).not.toBeInTheDocument();
395
+ expect(screen.queryByRole("button", { name: "Mark Done" })).not.toBeInTheDocument();
396
+ expect(server.call).toHaveBeenCalledWith({ name: "taskchef_app_set_done", arguments: { taskId: "one", expectedTurnId: "turn-one", done: true } });
397
+
398
+ });
399
+
400
+
401
+ test("native visibility settings update counts and survive view reloads", async () => {
402
+ tasks = [task("desktop"), ...["exec", "cli"].map((source) => ({ ...task(source), observed: { source, archive: false, lastTurnEvent: "completed", lastTurnEventAt: null, recentFileActivity: false } })), { ...task("archive", "archived"), observed: { source: "vscode", archive: true, lastTurnEvent: "completed", lastTurnEventAt: null, recentFileActivity: false } }];
403
+ details = new Map(tasks.map((item) => [item.id, item]));
404
+ const first = render(<TaskChefApp />);
405
+ expect(await screen.findByRole("button", { name: "Task desktop" })).toBeVisible();
406
+ expect(within(screen.getByRole("region", { name: "Task board" })).getAllByRole("button", { name: /^Task / })).toHaveLength(1);
407
+ visibility = { showExec: true, showCli: true, showArchived: true };
408
+ fireEvent.click(screen.getByRole("button", { name: "Refresh" }));
409
+ expect(await screen.findByRole("button", { name: "Task archive" })).toBeVisible();
410
+ expect(within(screen.getByRole("region", { name: "Task board" })).getAllByRole("button", { name: /^Task / })).toHaveLength(4);
411
+ first.unmount(); mount();
412
+ expect(await screen.findByRole("button", { name: "Task exec" })).toBeVisible();
413
+ visibility = { showExec: false, showCli: false, showArchived: false };
414
+ fireEvent.click(screen.getByRole("button", { name: "Refresh" }));
415
+ await waitFor(() => expect(screen.queryByRole("button", { name: "Task exec" })).not.toBeInTheDocument());
416
+ expect(screen.queryByRole("button", { name: "Task cli" })).not.toBeInTheDocument();
417
+ expect(screen.queryByRole("button", { name: "Task archive" })).not.toBeInTheDocument();
418
+ });
419
+
420
+ test("a selected CLI chat closes when its source becomes hidden during refresh", async () => {
421
+ visibility.showCli = true;
422
+ const cli = { ...task("cli"), observed: { source: "cli", archive: false, lastTurnEvent: "inProgress", lastTurnEventAt: null, recentFileActivity: false } };
423
+ tasks = [cli]; details = new Map([[cli.id, cli]]);
424
+ mount();
425
+ fireEvent.click(await screen.findByRole("button", { name: "Task cli" }));
426
+ expect(await screen.findByRole("region", { name: "Task detail" })).toBeVisible();
427
+ // Another visible client, or updated metadata, can make this chat archived.
428
+ const archived = { ...cli, status: "archived" as const, observed: { ...cli.observed, archive: true } };
429
+ tasks = [archived]; details.set(cli.id, archived);
430
+ fireEvent.click(screen.getByRole("button", { name: "Refresh" }));
431
+ await waitFor(() => expect(screen.queryByRole("region", { name: "Task detail" })).not.toBeInTheDocument());
432
+ expect(screen.queryByRole("button", { name: "Task cli" })).not.toBeInTheDocument();
433
+ });
434
+
435
+ test("inline mode follows the exec, CLI and archive visibility settings", async () => {
436
+ server.displayMode = "inline";
437
+ tasks = [task("desktop"), { ...task("exec"), observed: { source: "exec", archive: false, lastTurnEvent: "completed", lastTurnEventAt: null, recentFileActivity: false } }, { ...task("cli"), observed: { source: "cli", archive: false, lastTurnEvent: "completed", lastTurnEventAt: null, recentFileActivity: false } }, { ...task("archive", "archived"), observed: { archive: true, lastTurnEvent: "completed", lastTurnEventAt: null, recentFileActivity: false } }];
438
+ mount();
439
+ expect(await screen.findByText("1 eligible top-level chats")).toBeVisible();
440
+ expect(screen.queryByRole("button", { name: "Task cli" })).not.toBeInTheDocument();
441
+ expect(screen.queryByRole("button", { name: "Task archive" })).not.toBeInTheDocument();
442
+ });
443
+
444
+ test("notification center restores saved history quietly and shares read/clear actions", async () => {
445
+ notificationState = { revision: 1, items: [{ id: "saved", taskId: "one", title: "Saved notification", detail: "", kind: "ready", read: false, timestamp: new Date().toISOString() }] };
446
+ mount();
447
+ await screen.findByRole("button", { name: "Task one" });
448
+ expect(screen.queryByText("Saved notification")).not.toBeInTheDocument();
449
+ expect(screen.getByRole("button", { name: "Notifications, unread" })).toBeVisible();
450
+ expect(screen.getByRole("button", { name: "Notifications, unread" })).not.toHaveTextContent(/\d/);
451
+ fireEvent.click(screen.getByRole("button", { name: /^Notifications/ }));
452
+ const center = screen.getByRole("region", { name: "Notification center" });
453
+ expect(within(center).getByText("Saved notification")).toBeVisible();
454
+ fireEvent.click(within(center).getByRole("button", { name: "Mark all read" }));
455
+ await waitFor(() => expect(screen.queryByRole("button", { name: "Notifications, unread" })).not.toBeInTheDocument());
456
+ fireEvent.click(within(center).getByRole("button", { name: "Unread" }));
457
+ expect(within(center).getByText("No unread notifications")).toBeVisible();
458
+ fireEvent.click(within(center).getByRole("button", { name: "All" }));
459
+ fireEvent.click(within(center).getByRole("button", { name: /Saved notification/ }));
460
+ expect(await screen.findByRole("region", { name: "Task detail" })).toBeVisible();
461
+ expect(screen.queryByRole("region", { name: "Notification center" })).not.toBeInTheDocument();
462
+ fireEvent.click(screen.getByRole("button", { name: "Close" }));
463
+ fireEvent.click(screen.getByRole("button", { name: /^Notifications/ }));
464
+ fireEvent.click(screen.getByRole("button", { name: "Clear all" }));
465
+ expect(await screen.findByText("No notifications yet")).toBeVisible();
466
+ });
467
+
468
+ test("new notifications toast once for five seconds without replacing the board", async () => {
469
+ mount();
470
+ await screen.findByRole("button", { name: "Task one" });
471
+ vi.useFakeTimers();
472
+ try {
473
+ notificationState = { revision: 1, items: [{ id: "new", taskId: "one", title: "Task one is ready for input or review", detail: "", kind: "ready", read: false, timestamp: new Date().toISOString() }] };
474
+ await act(async () => { fireEvent.click(screen.getByRole("button", { name: "Refresh" })); });
475
+ expect(screen.getByText("Task one is ready for input or review")).toBeVisible();
476
+ expect(screen.getByRole("region", { name: "Task board" })).toBeVisible();
477
+ await act(async () => { vi.advanceTimersByTime(4000); });
478
+ expect(screen.getByText("Task one is ready for input or review")).toBeVisible();
479
+ await act(async () => { vi.advanceTimersByTime(1000); });
480
+ expect(screen.queryByText("Task one is ready for input or review")).not.toBeInTheDocument();
481
+ await act(async () => { fireEvent.click(screen.getByRole("button", { name: "Refresh" })); });
482
+ expect(screen.queryByText("Task one is ready for input or review")).not.toBeInTheDocument();
483
+ fireEvent.click(screen.getByRole("button", { name: /^Notifications/ }));
484
+ expect(screen.getByText("Task one is ready for input or review")).toBeVisible();
485
+ fireEvent.keyDown(document, { key: "Escape" });
486
+ expect(screen.queryByRole("region", { name: "Notification center" })).not.toBeInTheDocument();
487
+ expect(screen.getByRole("button", { name: /^Notifications/ })).toHaveFocus();
488
+ } finally { vi.useRealTimers(); }
489
+ });
490
+
491
+
492
+ test("a saved notification can open Details for a chat hidden by Settings", async () => {
493
+ const hidden = { ...task("hidden", "archived"), observed: { source: "cli", archive: true } } as Task;
494
+ tasks.push(hidden);
495
+ details.set(hidden.id, hidden);
496
+ notificationState = { revision: 1, items: [{ id: "saved", taskId: "hidden", title: "Task hidden is ready for input or review", detail: "", kind: "ready", read: false, timestamp: new Date().toISOString() }] };
497
+ mount();
498
+ await screen.findByRole("button", { name: "Task one" });
499
+ expect(screen.queryByRole("button", { name: "Task hidden" })).not.toBeInTheDocument();
500
+ fireEvent.click(screen.getByRole("button", { name: /^Notifications/ }));
501
+ fireEvent.click(screen.getByRole("button", { name: /Task hidden is ready/ }));
502
+ expect(await screen.findByRole("region", { name: "Task detail" })).toHaveTextContent("Task hidden");
503
+ fireEvent.click(screen.getByRole("button", { name: "Refresh" }));
504
+ await waitFor(() => expect(screen.getByRole("region", { name: "Task detail" })).toHaveTextContent("Task hidden"));
505
+ expect(screen.queryByRole("button", { name: "Notifications, unread" })).not.toBeInTheDocument();
506
+ });
507
+
508
+
509
+ test("saved project selection groups original and worktree chats and includes empty projects", async () => {
510
+ tasks = [task("original"), task("worktree"), task("plain")];
511
+ tasks[0].project = { id: "saved", name: "My project", path: "/repo", githubRepos: [] };
512
+ tasks[1].project = { id: "saved", name: "My project", path: "/worktree", githubRepos: [] };
513
+ tasks[2].project = { id: "projectless", name: "No project", path: "/Documents/Codex/date/task", githubRepos: [] };
514
+ registeredProjects = [{ id: "saved", name: "My project", path: "/repo", githubRepos: [] }, { id: "empty", name: "Empty project", path: "/empty", githubRepos: [] }];
515
+ mount(); await screen.findByRole("button", { name: "Task original" });
516
+ fireEvent.change(screen.getByRole("combobox", { name: "Project" }), { target: { value: "saved" } });
517
+ expect(screen.getByRole("button", { name: "Task worktree" })).toBeVisible();
518
+ expect(screen.queryByRole("button", { name: "Task plain" })).not.toBeInTheDocument();
519
+ fireEvent.change(screen.getByRole("combobox", { name: "Project" }), { target: { value: "projectless" } });
520
+ expect(screen.getByRole("button", { name: "Task plain" })).toBeVisible();
521
+ expect(screen.queryByRole("button", { name: "Task original" })).not.toBeInTheDocument();
522
+ fireEvent.change(screen.getByRole("combobox", { name: "Project" }), { target: { value: "empty" } });
523
+ expect(within(screen.getByRole("region", { name: "Task board" })).queryAllByRole("article")).toHaveLength(0);
524
+ });
525
+
526
+ test("active scheduled chat hides Mark Done in Details", async () => {
527
+ const current = { ...task("one", "needs_input"), scheduled: true, turnId: "turn-one", observed: { archive: false, lastTurnEvent: "completed", lastTurnEventAt: null, recentFileActivity: false } };
528
+ tasks = [current]; details = new Map([[current.id, current]]);
529
+ mount();
530
+ fireEvent.click(await screen.findByRole("button", { name: "Task one" }));
531
+ expect(await screen.findByRole("region", { name: "Task detail" })).toBeVisible();
532
+ expect(screen.queryByRole("button", { name: "Mark Done" })).not.toBeInTheDocument();
533
+ });
534
+
535
+
536
+ test("Done connection link opens GitHub controls without leaving the board", async () => {
537
+ mount();
538
+ const link = await screen.findByRole("button", {name:"Connect GitHub"});
539
+ fireEvent.click(link);
540
+ expect(await screen.findByRole("button", {name:"Open plugin settings"})).toBeInTheDocument();
541
+ expect(screen.getByRole("region", {name:"Task board"})).toBeVisible();
542
+ });
543
+
544
+
545
+ test("All time loads local records first and passes visible card IDs on the next request", async () => {
546
+ render(<TaskChefApp />);
547
+ await screen.findByRole("button", { name: "Report card in view" });
548
+ expect(server.call).toHaveBeenCalledWith({name:"taskchef_app_snapshot",arguments:{project:"",date:"all",visibleTaskIds:[]}});
549
+ server.call.mockClear();
550
+ fireEvent.click(screen.getByRole("button", { name: "Report card in view" }));
551
+ await waitFor(() => expect(server.call).toHaveBeenCalledWith({name:"taskchef_app_snapshot",arguments:{project:"",date:"all",visibleTaskIds:["one"]}}));
552
+ });
553
+
554
+
555
+ test("returning to a visible panel reuses PR cache; manual Refresh forces a check",async()=>{
556
+ vi.spyOn(document,"visibilityState","get").mockReturnValue("visible");
557
+ mount(); await screen.findByRole("button",{name:"Task one"});
558
+ server.call.mockClear();
559
+ fireEvent(document,new Event("visibilitychange"));
560
+ await waitFor(()=>expect(server.call).toHaveBeenCalledWith(expect.objectContaining({name:"taskchef_app_snapshot"})));
561
+ expect(server.call.mock.calls.filter(([input])=>input.name==="taskchef_app_snapshot").every(([input])=>input.arguments.force !== true)).toBe(true);
562
+ server.call.mockClear();
563
+ fireEvent.click(screen.getByRole("button",{name:"Refresh"}));
564
+ await waitFor(()=>expect(server.call).toHaveBeenCalledWith(expect.objectContaining({name:"taskchef_app_snapshot",arguments:expect.objectContaining({force:true})})));
565
+ });
566
+
567
+ test("appearance selection changes colors and survives reopening the view", async () => {
568
+ const first = render(<TaskChefApp />);
569
+ fireEvent.click(screen.getByRole("button", { name: "Appearance" }));
570
+ expect(screen.getByRole("radio", { name: "NIGHT" })).toHaveAttribute("aria-checked", "true");
571
+ fireEvent.click(screen.getByRole("radio", { name: "DAY" }));
572
+ expect(document.documentElement.dataset.taskchefTheme).toBe("light");
573
+ expect(window.localStorage.getItem("taskchef.app.theme")).toBe("light");
574
+ fireEvent.click(screen.getByRole("radio", { name: "Catppuccin" }));
575
+ expect(window.localStorage.getItem("taskchef.app.visualTheme.light")).toBe("catppuccin");
576
+ fireEvent.click(screen.getByRole("radio", { name: "NIGHT" }));
577
+ fireEvent.click(screen.getByRole("radio", { name: "Nord" }));
578
+ expect(document.documentElement.dataset.taskchefPalette).toBe("nord");
579
+ fireEvent.click(screen.getByRole("radio", { name: "DAY" }));
580
+ expect(document.documentElement.dataset.taskchefPalette).toBe("catppuccin");
581
+ first.unmount();
582
+ render(<TaskChefApp />);
583
+ fireEvent.click(screen.getByRole("button", { name: "Appearance" }));
584
+ expect(screen.getByRole("radio", { name: "DAY" })).toHaveAttribute("aria-checked", "true");
585
+ expect(screen.getByRole("radio", { name: "Catppuccin" })).toHaveAttribute("aria-checked", "true");
586
+ expect(document.documentElement.dataset.taskchefTheme).toBe("light");
587
+ });
588
+
589
+
590
+ test("opening and reopening Details use cached PRs; explicit Refresh requests new status", async () => {
591
+ render(<TaskChefApp />);
592
+ await screen.findByRole("button", {name:"Task one"});
593
+ fireEvent.click(screen.getByRole("button", {name:"Task one"}));
594
+ await screen.findByRole("region", {name:"Task detail"});
595
+ await waitFor(()=>expect(server.call).toHaveBeenCalledWith({name:"taskchef_app_task",arguments:{taskId:"one"}}));
596
+ fireEvent.click(screen.getByRole("button", {name:"Close"}));
597
+ fireEvent.click(screen.getByRole("button", {name:"Task one"}));
598
+ await screen.findByRole("region", {name:"Task detail"});
599
+ expect(server.call.mock.calls.filter(([request])=>request.name==="taskchef_app_task").every(([request])=>!request.arguments.refreshGithub)).toBe(true);
600
+ fireEvent.click(screen.getByRole("button", {name:"Refresh"}));
601
+ await waitFor(()=>expect(server.call).toHaveBeenCalledWith({name:"taskchef_app_task",arguments:{taskId:"one",refreshGithub:true}}));
602
+ });
603
+
604
+ test("a slow Details read finishes across successful and failed board refreshes", async () => {
605
+ const original=server.call.getMockImplementation()!;
606
+ let finish!: (value: unknown) => void, failSnapshot=false;
607
+ server.call.mockImplementation((args)=>{
608
+ if(args.name === "taskchef_app_task") return new Promise(resolve=>{finish=resolve;});
609
+ if(args.name === "taskchef_app_snapshot" && failSnapshot) return Promise.reject(new Error("Snapshot failed"));
610
+ return original(args);
611
+ });
612
+ mount();fireEvent.click(await screen.findByRole("button",{name:"Task one"}));
613
+ expect(await screen.findByText("Loading…")).toBeVisible();
614
+ await act(async()=>{fireEvent(document,new Event("visibilitychange"));});
615
+ expect(server.call.mock.calls.filter(([args])=>args.name === "taskchef_app_task")).toHaveLength(1);
616
+ failSnapshot=true;
617
+ await act(async()=>{fireEvent(document,new Event("visibilitychange"));});
618
+ expect(screen.getByText("Loading…")).toBeVisible();
619
+ const value={tokens:{input_tokens:100,cached_input_tokens:0,cache_write_input_tokens:0,output_tokens:10,reasoning_output_tokens:0,total_tokens:110},samples:1,costUsd:0.1,partial:false,costPartial:false};
620
+ await act(async()=>finish({structuredContent:{task:{...task("one"),sessionUsage:{latest:value,total:value,subagents:0,pricingDate:"2026-10-10"}}}}));
621
+ expect(within(screen.getByRole("region",{name:"Usage"})).getByText("Usage loaded")).toBeVisible();
622
+ expect(screen.queryByText("Loading…")).toBeNull();
623
+ });
624
+
625
+
626
+ test("native card usage setting defaults on and changes without hiding detail usage", async () => {
627
+ render(<TaskChefApp />);
628
+ const board = await screen.findByRole("region", {name:"Task board"});
629
+ expect(board).toHaveAttribute("data-card-usage", "true");
630
+ visibility = {...visibility, showCardUsage:false};
631
+ fireEvent.click(screen.getByRole("button", {name:"Refresh"}));
632
+ await waitFor(() => expect(board).toHaveAttribute("data-card-usage", "false"));
633
+ fireEvent.click(screen.getByRole("button", {name:"Task one"}));
634
+ expect(await screen.findByRole("region", {name:"Usage"})).toBeInTheDocument();
635
+ });