@seanmars/tospec 0.23.0 → 0.26.0-beta

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 (358) hide show
  1. package/CHANGELOG.md +281 -0
  2. package/README.md +27 -9
  3. package/assets/claude-mod/tospec-dashboard/.claude-plugin/plugin.json +7 -0
  4. package/assets/claude-mod/tospec-dashboard/hooks/hooks.json +1 -0
  5. package/assets/claude-mod/tospec-dashboard/hooks/register.ts +695 -0
  6. package/assets/claude-mod/tospec-dashboard/tests/register.test.ts +448 -0
  7. package/assets/claude-mod/tospec-dashboard/types/index.d.ts +8 -0
  8. package/assets/dashboard/app.js +218 -1180
  9. package/assets/dashboard/index.html +31 -22
  10. package/assets/dashboard/lib/charts.js +355 -0
  11. package/assets/dashboard/lib/derive.js +375 -0
  12. package/assets/dashboard/lib/dom.js +121 -0
  13. package/assets/dashboard/lib/routes.js +52 -0
  14. package/assets/dashboard/lib/store.js +326 -0
  15. package/assets/dashboard/style.css +3103 -664
  16. package/assets/dashboard/views/alerts.js +170 -0
  17. package/assets/dashboard/views/change.js +234 -0
  18. package/assets/dashboard/views/docs.js +214 -0
  19. package/assets/dashboard/views/home.js +357 -0
  20. package/assets/dashboard/views/project.js +168 -0
  21. package/assets/dashboard/views/session.js +270 -0
  22. package/assets/dashboard/views/sidebar.js +131 -0
  23. package/dist/cli/index.d.ts.map +1 -1
  24. package/dist/cli/index.js +116 -102
  25. package/dist/cli/index.js.map +1 -1
  26. package/dist/commands/config.d.ts.map +1 -1
  27. package/dist/commands/config.js +213 -100
  28. package/dist/commands/config.js.map +1 -1
  29. package/dist/commands/dashboard-relay.d.ts +11 -0
  30. package/dist/commands/dashboard-relay.d.ts.map +1 -0
  31. package/dist/commands/dashboard-relay.js +88 -0
  32. package/dist/commands/dashboard-relay.js.map +1 -0
  33. package/dist/commands/dashboard.d.ts +69 -100
  34. package/dist/commands/dashboard.d.ts.map +1 -1
  35. package/dist/commands/dashboard.js +420 -598
  36. package/dist/commands/dashboard.js.map +1 -1
  37. package/dist/commands/decision.d.ts +38 -1
  38. package/dist/commands/decision.d.ts.map +1 -1
  39. package/dist/commands/decision.js +303 -27
  40. package/dist/commands/decision.js.map +1 -1
  41. package/dist/commands/metrics.d.ts +3 -4
  42. package/dist/commands/metrics.d.ts.map +1 -1
  43. package/dist/commands/metrics.js +3 -5
  44. package/dist/commands/metrics.js.map +1 -1
  45. package/dist/commands/shared-output.d.ts +24 -0
  46. package/dist/commands/shared-output.d.ts.map +1 -1
  47. package/dist/commands/shared-output.js +21 -2
  48. package/dist/commands/shared-output.js.map +1 -1
  49. package/dist/commands/show.d.ts.map +1 -1
  50. package/dist/commands/show.js +12 -9
  51. package/dist/commands/show.js.map +1 -1
  52. package/dist/commands/validate.d.ts +19 -0
  53. package/dist/commands/validate.d.ts.map +1 -1
  54. package/dist/commands/validate.js +243 -64
  55. package/dist/commands/validate.js.map +1 -1
  56. package/dist/commands/workflow/instructions.d.ts +7 -1
  57. package/dist/commands/workflow/instructions.d.ts.map +1 -1
  58. package/dist/commands/workflow/instructions.js +299 -81
  59. package/dist/commands/workflow/instructions.js.map +1 -1
  60. package/dist/commands/workflow/new-change.d.ts.map +1 -1
  61. package/dist/commands/workflow/new-change.js +26 -17
  62. package/dist/commands/workflow/new-change.js.map +1 -1
  63. package/dist/commands/workflow/prompt-escape.d.ts +25 -0
  64. package/dist/commands/workflow/prompt-escape.d.ts.map +1 -0
  65. package/dist/commands/workflow/prompt-escape.js +62 -0
  66. package/dist/commands/workflow/prompt-escape.js.map +1 -0
  67. package/dist/commands/workflow/schemas.d.ts.map +1 -1
  68. package/dist/commands/workflow/schemas.js +34 -16
  69. package/dist/commands/workflow/schemas.js.map +1 -1
  70. package/dist/commands/workflow/shared.d.ts +46 -10
  71. package/dist/commands/workflow/shared.d.ts.map +1 -1
  72. package/dist/commands/workflow/shared.js +66 -30
  73. package/dist/commands/workflow/shared.js.map +1 -1
  74. package/dist/commands/workflow/status.d.ts +5 -2
  75. package/dist/commands/workflow/status.d.ts.map +1 -1
  76. package/dist/commands/workflow/status.js +62 -28
  77. package/dist/commands/workflow/status.js.map +1 -1
  78. package/dist/core/archive.d.ts +38 -0
  79. package/dist/core/archive.d.ts.map +1 -1
  80. package/dist/core/archive.js +492 -75
  81. package/dist/core/archive.js.map +1 -1
  82. package/dist/core/artifact-graph/index.d.ts +2 -2
  83. package/dist/core/artifact-graph/index.d.ts.map +1 -1
  84. package/dist/core/artifact-graph/index.js +2 -2
  85. package/dist/core/artifact-graph/index.js.map +1 -1
  86. package/dist/core/artifact-graph/instruction-loader.d.ts +8 -0
  87. package/dist/core/artifact-graph/instruction-loader.d.ts.map +1 -1
  88. package/dist/core/artifact-graph/instruction-loader.js +58 -36
  89. package/dist/core/artifact-graph/instruction-loader.js.map +1 -1
  90. package/dist/core/artifact-graph/resolver.d.ts +39 -12
  91. package/dist/core/artifact-graph/resolver.d.ts.map +1 -1
  92. package/dist/core/artifact-graph/resolver.js +82 -36
  93. package/dist/core/artifact-graph/resolver.js.map +1 -1
  94. package/dist/core/artifact-graph/schema.d.ts +24 -0
  95. package/dist/core/artifact-graph/schema.d.ts.map +1 -1
  96. package/dist/core/artifact-graph/schema.js +32 -1
  97. package/dist/core/artifact-graph/schema.js.map +1 -1
  98. package/dist/core/artifact-graph/stub-detection.d.ts +10 -2
  99. package/dist/core/artifact-graph/stub-detection.d.ts.map +1 -1
  100. package/dist/core/artifact-graph/stub-detection.js +18 -11
  101. package/dist/core/artifact-graph/stub-detection.js.map +1 -1
  102. package/dist/core/artifact-graph/types.d.ts +2 -2
  103. package/dist/core/artifact-graph/types.d.ts.map +1 -1
  104. package/dist/core/artifact-graph/types.js +10 -1
  105. package/dist/core/artifact-graph/types.js.map +1 -1
  106. package/dist/core/change-metadata/schema.d.ts.map +1 -1
  107. package/dist/core/change-metadata/schema.js +18 -4
  108. package/dist/core/change-metadata/schema.js.map +1 -1
  109. package/dist/core/change-presenter.d.ts.map +1 -1
  110. package/dist/core/change-presenter.js +13 -8
  111. package/dist/core/change-presenter.js.map +1 -1
  112. package/dist/core/change-status-policy.d.ts.map +1 -1
  113. package/dist/core/change-status-policy.js +3 -1
  114. package/dist/core/change-status-policy.js.map +1 -1
  115. package/dist/core/codex-metrics.d.ts +1 -1
  116. package/dist/core/codex-metrics.js +1 -1
  117. package/dist/core/config-schema.d.ts +8 -0
  118. package/dist/core/config-schema.d.ts.map +1 -1
  119. package/dist/core/config-schema.js +10 -0
  120. package/dist/core/config-schema.js.map +1 -1
  121. package/dist/core/dashboard-activity.d.ts +4 -4
  122. package/dist/core/dashboard-activity.js +4 -4
  123. package/dist/core/dashboard-data.d.ts +11 -5
  124. package/dist/core/dashboard-data.d.ts.map +1 -1
  125. package/dist/core/dashboard-data.js +54 -28
  126. package/dist/core/dashboard-data.js.map +1 -1
  127. package/dist/core/dashboard-hub.d.ts +258 -0
  128. package/dist/core/dashboard-hub.d.ts.map +1 -0
  129. package/dist/core/dashboard-hub.js +655 -0
  130. package/dist/core/dashboard-hub.js.map +1 -0
  131. package/dist/core/dashboard-routes.d.ts +57 -0
  132. package/dist/core/dashboard-routes.d.ts.map +1 -0
  133. package/dist/core/dashboard-routes.js +398 -0
  134. package/dist/core/dashboard-routes.js.map +1 -0
  135. package/dist/core/global-config.d.ts +71 -2
  136. package/dist/core/global-config.d.ts.map +1 -1
  137. package/dist/core/global-config.js +149 -4
  138. package/dist/core/global-config.js.map +1 -1
  139. package/dist/core/id.d.ts +9 -0
  140. package/dist/core/id.d.ts.map +1 -1
  141. package/dist/core/id.js +37 -0
  142. package/dist/core/id.js.map +1 -1
  143. package/dist/core/init.d.ts +9 -2
  144. package/dist/core/init.d.ts.map +1 -1
  145. package/dist/core/init.js +119 -33
  146. package/dist/core/init.js.map +1 -1
  147. package/dist/core/list.d.ts.map +1 -1
  148. package/dist/core/list.js +69 -45
  149. package/dist/core/list.js.map +1 -1
  150. package/dist/core/local-server.d.ts +10 -3
  151. package/dist/core/local-server.d.ts.map +1 -1
  152. package/dist/core/local-server.js +25 -5
  153. package/dist/core/local-server.js.map +1 -1
  154. package/dist/core/markdown-render.d.ts.map +1 -1
  155. package/dist/core/markdown-render.js +7 -2
  156. package/dist/core/markdown-render.js.map +1 -1
  157. package/dist/core/migrate.d.ts +24 -2
  158. package/dist/core/migrate.d.ts.map +1 -1
  159. package/dist/core/migrate.js +335 -59
  160. package/dist/core/migrate.js.map +1 -1
  161. package/dist/core/parsers/change-parser.d.ts +20 -1
  162. package/dist/core/parsers/change-parser.d.ts.map +1 -1
  163. package/dist/core/parsers/change-parser.js +76 -73
  164. package/dist/core/parsers/change-parser.js.map +1 -1
  165. package/dist/core/parsers/markdown-parser.d.ts +24 -0
  166. package/dist/core/parsers/markdown-parser.d.ts.map +1 -1
  167. package/dist/core/parsers/markdown-parser.js +65 -10
  168. package/dist/core/parsers/markdown-parser.js.map +1 -1
  169. package/dist/core/parsers/requirement-blocks.d.ts +127 -6
  170. package/dist/core/parsers/requirement-blocks.d.ts.map +1 -1
  171. package/dist/core/parsers/requirement-blocks.js +349 -56
  172. package/dist/core/parsers/requirement-blocks.js.map +1 -1
  173. package/dist/core/parsers/requirement-text.d.ts +25 -1
  174. package/dist/core/parsers/requirement-text.d.ts.map +1 -1
  175. package/dist/core/parsers/requirement-text.js +62 -6
  176. package/dist/core/parsers/requirement-text.js.map +1 -1
  177. package/dist/core/parsers/spec-structure.d.ts.map +1 -1
  178. package/dist/core/parsers/spec-structure.js +36 -24
  179. package/dist/core/parsers/spec-structure.js.map +1 -1
  180. package/dist/core/project-config.d.ts +9 -2
  181. package/dist/core/project-config.d.ts.map +1 -1
  182. package/dist/core/project-config.js +30 -7
  183. package/dist/core/project-config.js.map +1 -1
  184. package/dist/core/project-layout.d.ts +26 -0
  185. package/dist/core/project-layout.d.ts.map +1 -1
  186. package/dist/core/project-layout.js +51 -1
  187. package/dist/core/project-layout.js.map +1 -1
  188. package/dist/core/schema-names.d.ts +17 -0
  189. package/dist/core/schema-names.d.ts.map +1 -1
  190. package/dist/core/schema-names.js +24 -0
  191. package/dist/core/schema-names.js.map +1 -1
  192. package/dist/core/shared/dashboard-mod.d.ts +24 -0
  193. package/dist/core/shared/dashboard-mod.d.ts.map +1 -0
  194. package/dist/core/shared/dashboard-mod.js +44 -0
  195. package/dist/core/shared/dashboard-mod.js.map +1 -0
  196. package/dist/core/shared/index.d.ts +2 -1
  197. package/dist/core/shared/index.d.ts.map +1 -1
  198. package/dist/core/shared/index.js +2 -1
  199. package/dist/core/shared/index.js.map +1 -1
  200. package/dist/core/shared/rules-generation.d.ts +12 -0
  201. package/dist/core/shared/rules-generation.d.ts.map +1 -1
  202. package/dist/core/shared/rules-generation.js +26 -2
  203. package/dist/core/shared/rules-generation.js.map +1 -1
  204. package/dist/core/shared/skill-generation.d.ts +7 -0
  205. package/dist/core/shared/skill-generation.d.ts.map +1 -1
  206. package/dist/core/shared/skill-generation.js +19 -0
  207. package/dist/core/shared/skill-generation.js.map +1 -1
  208. package/dist/core/shared/tool-detection.d.ts +10 -1
  209. package/dist/core/shared/tool-detection.d.ts.map +1 -1
  210. package/dist/core/shared/tool-detection.js +37 -6
  211. package/dist/core/shared/tool-detection.js.map +1 -1
  212. package/dist/core/spec-presenter.d.ts.map +1 -1
  213. package/dist/core/spec-presenter.js +23 -1
  214. package/dist/core/spec-presenter.js.map +1 -1
  215. package/dist/core/specs-apply.d.ts +98 -1
  216. package/dist/core/specs-apply.d.ts.map +1 -1
  217. package/dist/core/specs-apply.js +426 -63
  218. package/dist/core/specs-apply.js.map +1 -1
  219. package/dist/core/templates/fragments/change-diff.d.ts +37 -0
  220. package/dist/core/templates/fragments/change-diff.d.ts.map +1 -0
  221. package/dist/core/templates/fragments/change-diff.js +37 -0
  222. package/dist/core/templates/fragments/change-diff.js.map +1 -0
  223. package/dist/core/templates/fragments/sync-fail.d.ts +8 -0
  224. package/dist/core/templates/fragments/sync-fail.d.ts.map +1 -0
  225. package/dist/core/templates/fragments/sync-fail.js +8 -0
  226. package/dist/core/templates/fragments/sync-fail.js.map +1 -0
  227. package/dist/core/templates/fragments/validate.d.ts +14 -1
  228. package/dist/core/templates/fragments/validate.d.ts.map +1 -1
  229. package/dist/core/templates/fragments/validate.js +14 -1
  230. package/dist/core/templates/fragments/validate.js.map +1 -1
  231. package/dist/core/templates/fragments/verify.d.ts +2 -2
  232. package/dist/core/templates/fragments/verify.d.ts.map +1 -1
  233. package/dist/core/templates/fragments/verify.js +8 -5
  234. package/dist/core/templates/fragments/verify.js.map +1 -1
  235. package/dist/core/templates/workflows/apply.d.ts +1 -1
  236. package/dist/core/templates/workflows/apply.d.ts.map +1 -1
  237. package/dist/core/templates/workflows/apply.js +29 -10
  238. package/dist/core/templates/workflows/apply.js.map +1 -1
  239. package/dist/core/templates/workflows/archive.d.ts.map +1 -1
  240. package/dist/core/templates/workflows/archive.js +27 -9
  241. package/dist/core/templates/workflows/archive.js.map +1 -1
  242. package/dist/core/templates/workflows/decision.d.ts.map +1 -1
  243. package/dist/core/templates/workflows/decision.js +10 -4
  244. package/dist/core/templates/workflows/decision.js.map +1 -1
  245. package/dist/core/templates/workflows/explore.d.ts +2 -2
  246. package/dist/core/templates/workflows/explore.d.ts.map +1 -1
  247. package/dist/core/templates/workflows/explore.js +7 -3
  248. package/dist/core/templates/workflows/explore.js.map +1 -1
  249. package/dist/core/templates/workflows/grill.d.ts +2 -2
  250. package/dist/core/templates/workflows/grill.d.ts.map +1 -1
  251. package/dist/core/templates/workflows/grill.js +5 -4
  252. package/dist/core/templates/workflows/grill.js.map +1 -1
  253. package/dist/core/templates/workflows/issue.d.ts.map +1 -1
  254. package/dist/core/templates/workflows/issue.js +9 -3
  255. package/dist/core/templates/workflows/issue.js.map +1 -1
  256. package/dist/core/templates/workflows/propose.d.ts.map +1 -1
  257. package/dist/core/templates/workflows/propose.js +4 -1
  258. package/dist/core/templates/workflows/propose.js.map +1 -1
  259. package/dist/core/templates/workflows/sync.d.ts.map +1 -1
  260. package/dist/core/templates/workflows/sync.js +25 -11
  261. package/dist/core/templates/workflows/sync.js.map +1 -1
  262. package/dist/core/templates/workflows/update.d.ts.map +1 -1
  263. package/dist/core/templates/workflows/update.js +17 -10
  264. package/dist/core/templates/workflows/update.js.map +1 -1
  265. package/dist/core/update.d.ts +5 -0
  266. package/dist/core/update.d.ts.map +1 -1
  267. package/dist/core/update.js +69 -29
  268. package/dist/core/update.js.map +1 -1
  269. package/dist/core/validation/constants.d.ts +7 -1
  270. package/dist/core/validation/constants.d.ts.map +1 -1
  271. package/dist/core/validation/constants.js +16 -2
  272. package/dist/core/validation/constants.js.map +1 -1
  273. package/dist/core/validation/purpose-placeholder.d.ts.map +1 -1
  274. package/dist/core/validation/purpose-placeholder.js +35 -11
  275. package/dist/core/validation/purpose-placeholder.js.map +1 -1
  276. package/dist/core/validation/section-validator.d.ts +7 -0
  277. package/dist/core/validation/section-validator.d.ts.map +1 -1
  278. package/dist/core/validation/section-validator.js +22 -2
  279. package/dist/core/validation/section-validator.js.map +1 -1
  280. package/dist/core/validation/sister-changes.d.ts +93 -0
  281. package/dist/core/validation/sister-changes.d.ts.map +1 -0
  282. package/dist/core/validation/sister-changes.js +180 -0
  283. package/dist/core/validation/sister-changes.js.map +1 -0
  284. package/dist/core/validation/task-markers.d.ts +9 -0
  285. package/dist/core/validation/task-markers.d.ts.map +1 -0
  286. package/dist/core/validation/task-markers.js +71 -0
  287. package/dist/core/validation/task-markers.js.map +1 -0
  288. package/dist/core/validation/types.d.ts +58 -0
  289. package/dist/core/validation/types.d.ts.map +1 -1
  290. package/dist/core/validation/types.js +22 -0
  291. package/dist/core/validation/types.js.map +1 -1
  292. package/dist/core/validation/validator.d.ts +79 -2
  293. package/dist/core/validation/validator.d.ts.map +1 -1
  294. package/dist/core/validation/validator.js +565 -84
  295. package/dist/core/validation/validator.js.map +1 -1
  296. package/dist/utils/change-metadata.d.ts +62 -5
  297. package/dist/utils/change-metadata.d.ts.map +1 -1
  298. package/dist/utils/change-metadata.js +193 -31
  299. package/dist/utils/change-metadata.js.map +1 -1
  300. package/dist/utils/change-utils.d.ts +21 -0
  301. package/dist/utils/change-utils.d.ts.map +1 -1
  302. package/dist/utils/change-utils.js +168 -53
  303. package/dist/utils/change-utils.js.map +1 -1
  304. package/dist/utils/command-line.d.ts +11 -0
  305. package/dist/utils/command-line.d.ts.map +1 -0
  306. package/dist/utils/command-line.js +43 -0
  307. package/dist/utils/command-line.js.map +1 -0
  308. package/dist/utils/display-width.d.ts +10 -0
  309. package/dist/utils/display-width.d.ts.map +1 -0
  310. package/dist/utils/display-width.js +15 -0
  311. package/dist/utils/display-width.js.map +1 -0
  312. package/dist/utils/file-system.d.ts +16 -0
  313. package/dist/utils/file-system.d.ts.map +1 -1
  314. package/dist/utils/file-system.js +49 -2
  315. package/dist/utils/file-system.js.map +1 -1
  316. package/dist/utils/frontmatter.d.ts.map +1 -1
  317. package/dist/utils/frontmatter.js +11 -5
  318. package/dist/utils/frontmatter.js.map +1 -1
  319. package/dist/utils/interactive.d.ts.map +1 -1
  320. package/dist/utils/interactive.js +3 -1
  321. package/dist/utils/interactive.js.map +1 -1
  322. package/dist/utils/item-discovery.d.ts +3 -2
  323. package/dist/utils/item-discovery.d.ts.map +1 -1
  324. package/dist/utils/item-discovery.js +34 -16
  325. package/dist/utils/item-discovery.js.map +1 -1
  326. package/dist/utils/link.d.ts +0 -1
  327. package/dist/utils/link.d.ts.map +1 -1
  328. package/dist/utils/link.js +0 -1
  329. package/dist/utils/link.js.map +1 -1
  330. package/dist/utils/match.d.ts.map +1 -1
  331. package/dist/utils/match.js +10 -1
  332. package/dist/utils/match.js.map +1 -1
  333. package/dist/utils/option-flags.d.ts +9 -0
  334. package/dist/utils/option-flags.d.ts.map +1 -0
  335. package/dist/utils/option-flags.js +12 -0
  336. package/dist/utils/option-flags.js.map +1 -0
  337. package/dist/utils/sentence.d.ts +9 -0
  338. package/dist/utils/sentence.d.ts.map +1 -0
  339. package/dist/utils/sentence.js +12 -0
  340. package/dist/utils/sentence.js.map +1 -0
  341. package/dist/utils/spec-files.d.ts +84 -1
  342. package/dist/utils/spec-files.d.ts.map +1 -1
  343. package/dist/utils/spec-files.js +192 -3
  344. package/dist/utils/spec-files.js.map +1 -1
  345. package/dist/utils/task-progress.d.ts.map +1 -1
  346. package/dist/utils/task-progress.js +32 -9
  347. package/dist/utils/task-progress.js.map +1 -1
  348. package/dist/utils/timestamp.d.ts +27 -0
  349. package/dist/utils/timestamp.d.ts.map +1 -1
  350. package/dist/utils/timestamp.js +37 -0
  351. package/dist/utils/timestamp.js.map +1 -1
  352. package/package.json +6 -2
  353. package/schemas/decision/templates/decision.md +2 -2
  354. package/schemas/issue/schema.yaml +12 -6
  355. package/schemas/issue/templates/task.md +1 -1
  356. package/schemas/sdd/schema.yaml +21 -10
  357. package/schemas/sdd/templates/proposal.md +1 -1
  358. package/assets/dashboard/chart.umd.js +0 -14
@@ -0,0 +1,270 @@
1
+ // The session panel: one session's live log, and the question it waits on.
2
+ // The page answers what a session asked and nothing more: there is no box to
3
+ // type a prompt into, so every instruction still comes from the terminal. It
4
+ // sits beside whatever page is open and survives navigation.
5
+ //
6
+ // Built once per opened session and updated in place on every push: a
7
+ // rebuilt panel would wipe a half-chosen answer and steal the keyboard focus.
8
+
9
+ import { el, icon, activityDot, lineBadge } from '../lib/dom.js';
10
+ import { sessionStatus, sessionDetail, ACTIVITY_LABEL } from '../lib/derive.js';
11
+ import { store, projectByKey, slotOf, logId, loadSessionLog, forgetSessionLog, answerAsk } from '../lib/store.js';
12
+
13
+ const KIND_LABEL = { prompt: '你', reply: 'Claude', ask: '問題', answer: '回答', permission: '權限' };
14
+ const LOG_LIMIT = 200;
15
+
16
+ const panel = {
17
+ key: null,
18
+ session: null,
19
+ opener: null,
20
+ /** The highest event number drawn, so a push appends only what is new. */
21
+ drawnSeq: 0,
22
+ /** The ask drawn in the ask area; undefined until the first draw. */
23
+ askId: undefined,
24
+ dom: null,
25
+ };
26
+
27
+ function host() {
28
+ return document.getElementById('session-panel');
29
+ }
30
+
31
+ export function isSessionPanelOpen(key, session) {
32
+ return panel.key === key && panel.session === session;
33
+ }
34
+
35
+ export function openSessionPanel(key, session) {
36
+ if (panel.key && !isSessionPanelOpen(key, session)) forgetSessionLog(panel.key, panel.session);
37
+ const opener = document.activeElement;
38
+ Object.assign(panel, { key, session, opener, drawnSeq: 0, askId: undefined });
39
+ build();
40
+ const node = host();
41
+ node.hidden = false;
42
+ document.body.classList.add('has-session-panel');
43
+ loadSessionLog(key, session);
44
+ updateSessionPanel();
45
+ panel.dom.title.focus({ preventScroll: true });
46
+ }
47
+
48
+ export function closeSessionPanel() {
49
+ if (!panel.key) return;
50
+ forgetSessionLog(panel.key, panel.session);
51
+ const opener = panel.opener;
52
+ Object.assign(panel, { key: null, session: null, opener: null, dom: null });
53
+ host().hidden = true;
54
+ host().replaceChildren();
55
+ document.body.classList.remove('has-session-panel');
56
+ if (opener && opener.isConnected) opener.focus({ preventScroll: true });
57
+ }
58
+
59
+ function build() {
60
+ const close = el('button', { type: 'button', class: 'icon-button', title: '關閉' }, [icon('close', 14), el('span', { class: 'sr-only', text: '關閉 session 面板' })]);
61
+ close.addEventListener('click', closeSessionPanel);
62
+ const title = el('h2', { id: 'session-panel-title', class: 'sp-title', tabindex: '-1' }, [
63
+ el('span', { text: 'Session ' }),
64
+ el('span', { class: 'mono', text: panel.session.slice(0, 8) }),
65
+ ]);
66
+ const dot = el('span', { class: 'sp-dot' });
67
+ const status = el('p', { class: 'sp-status' });
68
+ const log = el('ol', { class: 'sp-log', 'aria-label': 'session log', 'aria-live': 'polite' });
69
+ const logNote = el('p', { class: 'sp-log-note' });
70
+ const ask = el('section', { class: 'sp-ask', 'aria-label': '等待回答' });
71
+ ask.hidden = true;
72
+ const foot = el('p', { class: 'sp-foot' });
73
+ const node = host();
74
+ node.setAttribute('aria-labelledby', 'session-panel-title');
75
+ node.replaceChildren(
76
+ el('header', { class: 'sp-head' }, [el('div', { class: 'sp-head-row' }, [dot, title, close]), status]),
77
+ el('div', { class: 'sp-scroll' }, [logNote, log]),
78
+ ask,
79
+ foot
80
+ );
81
+ node.onkeydown = (event) => {
82
+ if (event.key === 'Escape') closeSessionPanel();
83
+ };
84
+ panel.dom = { title, dot, status, log, logNote, ask, foot };
85
+ }
86
+
87
+ /** Brings the open panel up to date with the store; cheap when nothing changed. */
88
+ export function updateSessionPanel() {
89
+ if (!panel.key || !panel.dom) return;
90
+ const project = projectByKey(panel.key);
91
+ const session = project && project.sessions.find((s) => s.id === panel.session);
92
+ const dom = panel.dom;
93
+ const now = Date.now();
94
+
95
+ const status = session ? sessionStatus(session) : 'offline';
96
+ dom.dot.replaceChildren(activityDot(status));
97
+ const detail = session ? sessionDetail(session, now) : '這個 session 已經離開 dashboard.';
98
+ // Filtered: the native replaceChildren writes a null out as the text "null".
99
+ dom.status.replaceChildren(...[
100
+ project ? lineBadge(project.name, slotOf(project.key)) : null,
101
+ el('strong', { text: ACTIVITY_LABEL[status] }),
102
+ detail ? el('span', { class: 'sp-detail', text: detail }) : null,
103
+ ].filter(Boolean));
104
+
105
+ drawLog();
106
+ drawAsk(session);
107
+ dom.foot.textContent = store.interactive
108
+ ? '這裡只能回答 session 提出的問題; 其他指示請在 terminal 輸入.'
109
+ : '這個 dashboard 對外開放 (--allow-remote), 只能檢視.';
110
+ }
111
+
112
+ function drawLog() {
113
+ const dom = panel.dom;
114
+ const log = store.logs.get(logId(panel.key, panel.session));
115
+ if (!log) {
116
+ dom.logNote.textContent = '正在讀取 log.';
117
+ return;
118
+ }
119
+ dom.logNote.textContent = log.error ? 'log 讀取失敗: ' + log.error : log.events.length ? '' : '這個 session 還沒有活動. 它下一次動作時會出現在這裡.';
120
+ const events = log.events;
121
+ const last = events.length ? events[events.length - 1].seq : 0;
122
+ // Numbers restart when a session expires and comes back: draw the log anew.
123
+ if (last < panel.drawnSeq) {
124
+ dom.log.replaceChildren();
125
+ panel.drawnSeq = 0;
126
+ }
127
+ const fresh = events.filter((e) => e.seq > panel.drawnSeq);
128
+ if (!fresh.length) return;
129
+ const scroller = dom.log.parentElement;
130
+ const atBottom = scroller.scrollHeight - scroller.scrollTop - scroller.clientHeight < 48;
131
+ for (const event of fresh) dom.log.append(logRow(event));
132
+ while (dom.log.children.length > LOG_LIMIT) dom.log.firstElementChild.remove();
133
+ panel.drawnSeq = last;
134
+ if (atBottom) scroller.scrollTop = scroller.scrollHeight;
135
+ }
136
+
137
+ function clock(at) {
138
+ const d = new Date(at);
139
+ const pad = (n) => String(n).padStart(2, '0');
140
+ return `${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
141
+ }
142
+
143
+ function logRow(event) {
144
+ const isTool = event.kind === 'tool';
145
+ const failed = event.ok === false;
146
+ const body = isTool
147
+ ? [el('span', { class: 'mono sp-target', text: event.target || '' }), el('span', { class: 'sp-mark', text: failed ? '失敗' : '完成' })]
148
+ : event.kind === 'permission'
149
+ ? [el('span', { class: 'mono sp-target', text: `${event.tool || ''} ${event.target || ''}`.trim() }), ' ', event.text || '']
150
+ : [event.text || ''];
151
+ return el('li', { class: 'sp-entry kind-' + event.kind + (failed ? ' is-fail' : '') }, [
152
+ el('time', { class: 'sp-time num', datetime: event.at, text: clock(event.at) }),
153
+ el('span', { class: 'sp-who' + (isTool ? ' mono' : ''), text: isTool ? event.tool : KIND_LABEL[event.kind] || event.kind }),
154
+ el('span', { class: 'sp-body' }, body),
155
+ ]);
156
+ }
157
+
158
+ // --- what the session waits on -------------------------------------------------------
159
+
160
+ function drawAsk(session) {
161
+ const dom = panel.dom;
162
+ const ask = session && session.ask;
163
+ const id = ask ? ask.id : null;
164
+ if (id === panel.askId) return;
165
+ panel.askId = id;
166
+ dom.ask.hidden = !ask;
167
+ if (!ask) {
168
+ dom.ask.replaceChildren();
169
+ return;
170
+ }
171
+ dom.ask.replaceChildren(...(ask.kind === 'permission' ? permissionForm(ask) : questionForm(ask)).filter(Boolean));
172
+ }
173
+
174
+ function readOnlyNote() {
175
+ return store.interactive ? null : el('p', { class: 'sp-hint', text: '這個 dashboard 對外開放 (--allow-remote), 只能檢視; 請在 terminal 回答.' });
176
+ }
177
+
178
+ async function submitAnswer(ask, answer, controls, status) {
179
+ const { key, session } = panel;
180
+ for (const control of controls) control.disabled = true;
181
+ status.textContent = '正在送出.';
182
+ try {
183
+ await answerAsk(key, session, ask.id, answer);
184
+ status.textContent = '已送出.';
185
+ } catch (e) {
186
+ // 404: the terminal answered first, and the question is being withdrawn.
187
+ status.textContent = e.status === 404 ? '這個問題已經在 terminal 回答, 或已撤回.' : '沒有送出: ' + e.message;
188
+ if (e.status !== 404) for (const control of controls) control.disabled = false;
189
+ }
190
+ }
191
+
192
+ function questionForm(ask) {
193
+ const status = el('p', { class: 'sp-hint', 'aria-live': 'polite' });
194
+ const controls = [];
195
+ const fields = ask.questions.map((q, qi) => {
196
+ const name = 'sp-q-' + qi;
197
+ const type = q.multiSelect ? 'checkbox' : 'radio';
198
+ const options = q.options.map((option) => {
199
+ const input = el('input', { type, name, value: option.label });
200
+ controls.push(input);
201
+ return el('label', { class: 'sp-option' }, [
202
+ input,
203
+ el('span', {}, [el('strong', { text: option.label }), option.description ? el('span', { class: 'sp-option-desc', text: option.description }) : null]),
204
+ ]);
205
+ });
206
+ const otherPick = el('input', { type, name, value: '' });
207
+ const other = el('input', { type: 'text', class: 'sp-other', placeholder: '其他 (自行輸入)', 'aria-label': q.question + ' 其他答案' });
208
+ other.addEventListener('input', () => {
209
+ otherPick.checked = other.value.trim() !== '';
210
+ });
211
+ controls.push(otherPick, other);
212
+ return {
213
+ q,
214
+ node: el('fieldset', { class: 'sp-question' }, [
215
+ el('legend', {}, [q.header ? el('span', { class: 'tag', text: q.header }) : null, ' ', q.question]),
216
+ ...options,
217
+ el('label', { class: 'sp-option sp-option-other' }, [otherPick, other]),
218
+ ]),
219
+ read: () => {
220
+ const picked = [...document.getElementsByName(name)].filter((i) => i.checked && i.value).map((i) => i.value);
221
+ const typed = otherPick.checked ? other.value.trim() : '';
222
+ if (typed) picked.push(typed);
223
+ // AskUserQuestion's own form joins a multi-select answer with commas.
224
+ return picked.join(', ');
225
+ },
226
+ };
227
+ });
228
+ const send = el('button', { type: 'submit', class: 'button button-primary', text: '送出回答' });
229
+ controls.push(send);
230
+ const form = el('form', { class: 'sp-ask-form' }, [
231
+ el('p', { class: 'sp-ask-title', text: 'Claude 在問你' }),
232
+ el('p', { class: 'sp-hint sp-ask-hint', text: 'Terminal 也在詢問; 先回答的一方有效, 另一方會自動撤回.' }),
233
+ ...fields.map((f) => f.node),
234
+ el('div', { class: 'sp-form-foot' }, [status, send]),
235
+ readOnlyNote(),
236
+ ]);
237
+ if (!store.interactive) for (const control of controls) control.disabled = true;
238
+ form.addEventListener('submit', (event) => {
239
+ event.preventDefault();
240
+ const answers = {};
241
+ for (const field of fields) {
242
+ const value = field.read();
243
+ if (!value) {
244
+ status.textContent = '每一題都要回答: ' + field.q.question;
245
+ return;
246
+ }
247
+ answers[field.q.question] = value;
248
+ }
249
+ submitAnswer(ask, { answers }, controls, status);
250
+ });
251
+ return [form];
252
+ }
253
+
254
+ function permissionForm(ask) {
255
+ const status = el('p', { class: 'sp-hint', 'aria-live': 'polite' });
256
+ const allow = el('button', { type: 'button', class: 'button button-primary', text: '允許' });
257
+ const deny = el('button', { type: 'button', class: 'button', text: '拒絕' });
258
+ const controls = [allow, deny];
259
+ if (!store.interactive) for (const control of controls) control.disabled = true;
260
+ allow.addEventListener('click', () => submitAnswer(ask, { allow: true }, controls, status));
261
+ deny.addEventListener('click', () => submitAnswer(ask, { allow: false }, controls, status));
262
+ return [
263
+ el('p', { class: 'sp-ask-title', text: 'Claude 要求權限' }),
264
+ el('p', { class: 'sp-permission' }, [el('strong', { class: 'mono', text: ask.tool }), ' ', el('code', { class: 'sp-target', text: ask.target })]),
265
+ el('p', { class: 'sp-hint', text: 'Terminal 也在詢問; 先回答的一方有效, 另一方會自動撤回.' }),
266
+ el('div', { class: 'sp-actions' }, [deny, allow]),
267
+ status,
268
+ readOnlyNote(),
269
+ ];
270
+ }
@@ -0,0 +1,131 @@
1
+ // The sidebar: every project with its live sessions, each expandable to its
2
+ // changes, so any page is one click from the overview, its project, or a
3
+ // sibling change. Below 960px it becomes a drawer the sign bar opens.
4
+
5
+ import { el, marker, paintLine, icon, activityDot } from '../lib/dom.js';
6
+ import { signalOf, lineSignal, staleDaysOf, projectActivity, sessionStatus, ACTIVITY_LABEL, SIGNAL_LABEL } from '../lib/derive.js';
7
+ import { store, dataOf, slotOf } from '../lib/store.js';
8
+ import { routes } from '../lib/routes.js';
9
+
10
+ /** key -> open, for projects the person toggled; the current project is open unless closed. */
11
+ const toggled = new Map();
12
+ let lastSignature = null;
13
+
14
+ function isOpen(key, route) {
15
+ return toggled.has(key) ? toggled.get(key) : key === route.key;
16
+ }
17
+
18
+ /**
19
+ * Everything the sidebar draws, so a push that changes none of it (most do:
20
+ * a snapshot of a project whose list is the same) leaves the DOM, and the
21
+ * keyboard focus inside it, alone.
22
+ */
23
+ function signature(route, now) {
24
+ return JSON.stringify([
25
+ route.view,
26
+ route.key,
27
+ route.name,
28
+ [...toggled],
29
+ store.projects.map((p) => {
30
+ const data = dataOf(p.key);
31
+ const staleDays = staleDaysOf(data);
32
+ const changes = data.overview ? data.overview.changes.map((c) => [c.name, lineSignal(signalOf(c, staleDays, now), p.online)]) : null;
33
+ return [p.key, p.name, slotOf(p.key), p.online, p.sessions.map(sessionStatus), changes];
34
+ }),
35
+ ]);
36
+ }
37
+
38
+ export function renderSidebar(host, route) {
39
+ const now = Date.now();
40
+ const sig = signature(route, now);
41
+ if (sig === lastSignature) return;
42
+ lastSignature = sig;
43
+
44
+ // A redraw replaces the links; put the focus back on the one that had it.
45
+ const focusedHref = host.contains(document.activeElement) ? document.activeElement.getAttribute('href') : null;
46
+
47
+ const sessionTotal = store.projects.reduce((n, p) => n + p.sessions.length, 0);
48
+ const working = store.projects.reduce((n, p) => n + p.sessions.filter((s) => sessionStatus(s) === 'working').length, 0);
49
+ const waiting = store.projects.reduce((n, p) => n + p.sessions.filter((s) => sessionStatus(s) === 'waiting').length, 0);
50
+
51
+ const list = el('ul', { class: 'side-projects' }, store.projects.map((project) => projectItem(project, route, now)));
52
+ const foot = [`${sessionTotal} 個 session`];
53
+ if (working) foot.push(`${working} 個工作中`);
54
+ if (waiting) foot.push(`${waiting} 個等你回覆`);
55
+
56
+ host.replaceChildren(
57
+ el('a', { class: 'side-home', href: routes.home(), 'aria-current': route.view === 'home' ? 'page' : null }, [icon('overview', 16), el('span', { text: '總覽' })]),
58
+ el('h2', { class: 'side-title' }, ['Projects', el('span', { class: 'count', text: String(store.projects.length) })]),
59
+ store.projects.length ? list : el('p', { class: 'side-empty', text: store.projectsLoaded ? '還沒有 project 註冊.' : '正在讀取.' }),
60
+ el('p', { class: 'side-foot num', text: foot.join(' · ') })
61
+ );
62
+
63
+ if (focusedHref) {
64
+ const again = [...host.querySelectorAll('a')].find((a) => a.getAttribute('href') === focusedHref);
65
+ if (again) again.focus({ preventScroll: true });
66
+ }
67
+ }
68
+
69
+ function projectItem(project, route, now) {
70
+ const data = dataOf(project.key);
71
+ const open = isOpen(project.key, route);
72
+ const activity = projectActivity(project);
73
+ const listId = 'side-changes-' + project.key;
74
+ const here = route.key === project.key;
75
+
76
+ const toggle = el('button', {
77
+ type: 'button',
78
+ class: 'side-toggle',
79
+ 'aria-expanded': open ? 'true' : 'false',
80
+ 'aria-controls': listId,
81
+ title: open ? '收合 change 清單' : '展開 change 清單',
82
+ }, [icon('chevron', 14), el('span', { class: 'sr-only', text: project.name + ' 的 change' })]);
83
+ toggle.addEventListener('click', () => {
84
+ toggled.set(project.key, !open);
85
+ renderSidebar(toggle.closest('.sidebar'), route);
86
+ // The redraw replaced this button; keep the keyboard on its successor.
87
+ const again = document.querySelector(`[aria-controls="${listId}"]`);
88
+ if (again) again.focus({ preventScroll: true });
89
+ });
90
+
91
+ const n = project.sessions.length;
92
+ const sessionsTitle = activity === 'offline' ? '沒有 session 連線' : `${n} 個 session · ${ACTIVITY_LABEL[activity]}`;
93
+ const item = paintLine(el('li', { class: 'side-project' + (here ? ' is-current' : '') + (project.online ? '' : ' is-offline') }), slotOf(project.key));
94
+ item.append(
95
+ el('div', { class: 'side-row' }, [
96
+ toggle,
97
+ el('a', { class: 'side-link', href: routes.project(project.key), 'aria-current': route.view === 'project' && here ? 'page' : null }, [
98
+ el('span', { class: 'line-swatch', 'aria-hidden': 'true' }),
99
+ el('span', { class: 'side-name', text: project.name }),
100
+ ]),
101
+ el('span', { class: 'side-sessions num', title: sessionsTitle }, [
102
+ activityDot(activity),
103
+ el('span', { text: String(n) }),
104
+ el('span', { class: 'sr-only', text: ' ' + sessionsTitle }),
105
+ ]),
106
+ ])
107
+ );
108
+
109
+ const changes = el('ul', { class: 'side-changes', id: listId });
110
+ changes.hidden = !open;
111
+ const overview = data.overview;
112
+ if (!overview) {
113
+ changes.append(el('li', { class: 'side-empty', text: data.error ? '快照讀取失敗' : '正在讀取.' }));
114
+ } else if (!overview.changes.length) {
115
+ changes.append(el('li', { class: 'side-empty', text: '沒有進行中的 change' }));
116
+ } else {
117
+ const staleDays = staleDaysOf(data);
118
+ for (const change of overview.changes) {
119
+ const signal = lineSignal(signalOf(change, staleDays, now), project.online);
120
+ const current = route.view === 'change' && here && route.name === change.name;
121
+ changes.append(el('li', {}, [
122
+ el('a', { class: 'side-change signal-' + signal, href: routes.change(project.key, change.name), 'aria-current': current ? 'page' : null, title: `${change.name} · ${SIGNAL_LABEL[signal]}` }, [
123
+ marker(signal, 10),
124
+ el('span', { class: 'side-change-name', text: change.name }),
125
+ ]),
126
+ ]));
127
+ }
128
+ }
129
+ item.append(changes);
130
+ return item;
131
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,OAAO,EAAgD,MAAM,WAAW,CAAC;AAmIlF,QAAA,MAAM,OAAO,SAAgB,CAAC;AAghB9B,OAAO,EAAE,OAAO,EAAE,CAAC;AAiBnB;;;;GAIG;AACH,wBAAsB,MAAM,CAAC,IAAI,WAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAqC/D"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,OAAO,EAAgD,MAAM,WAAW,CAAC;AAkIlF,QAAA,MAAM,OAAO,SAAgB,CAAC;AA0gB9B,OAAO,EAAE,OAAO,EAAE,CAAC;AAyCnB;;;;GAIG;AACH,wBAAsB,MAAM,CAAC,IAAI,WAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAsC/D"}