@shivaedev/work-board 0.17.0 → 0.18.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 (304) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/README.md +27 -25
  3. package/assets/react.css +1 -1
  4. package/assets/react.js +37 -36
  5. package/dist/browser/responses/cardContext.d.ts +8 -1
  6. package/dist/browser/responses/cardContext.d.ts.map +1 -1
  7. package/dist/browser/responses/cardContext.js +12 -7
  8. package/dist/browser/responses/cardContext.js.map +1 -1
  9. package/dist/browser/views/now.d.ts +5 -0
  10. package/dist/browser/views/now.d.ts.map +1 -1
  11. package/dist/browser/views/now.js +38 -2
  12. package/dist/browser/views/now.js.map +1 -1
  13. package/dist/browser/views/schema.d.ts +11 -0
  14. package/dist/browser/views/schema.d.ts.map +1 -1
  15. package/dist/browser/views/schema.js +1 -0
  16. package/dist/browser/views/schema.js.map +1 -1
  17. package/dist/client/document/View.js +1 -1
  18. package/dist/client/document/View.js.map +1 -1
  19. package/dist/client/formStatus.d.ts +2 -2
  20. package/dist/client/formStatus.d.ts.map +1 -1
  21. package/dist/client/formStatus.js +2 -2
  22. package/dist/client/formStatus.js.map +1 -1
  23. package/dist/client/react/AnswerContext.d.ts.map +1 -1
  24. package/dist/client/react/AnswerContext.js +5 -2
  25. package/dist/client/react/AnswerContext.js.map +1 -1
  26. package/dist/client/react/AnswerForm.d.ts.map +1 -1
  27. package/dist/client/react/AnswerForm.js +6 -2
  28. package/dist/client/react/AnswerForm.js.map +1 -1
  29. package/dist/client/react/AnswerMarkdown.d.ts +6 -0
  30. package/dist/client/react/AnswerMarkdown.d.ts.map +1 -0
  31. package/dist/client/react/AnswerMarkdown.js +7 -0
  32. package/dist/client/react/AnswerMarkdown.js.map +1 -0
  33. package/dist/client/react/AnswerMore.d.ts.map +1 -1
  34. package/dist/client/react/AnswerMore.js +2 -1
  35. package/dist/client/react/AnswerMore.js.map +1 -1
  36. package/dist/client/react/AnswerPanel.d.ts.map +1 -1
  37. package/dist/client/react/AnswerPanel.js +3 -2
  38. package/dist/client/react/AnswerPanel.js.map +1 -1
  39. package/dist/client/react/AnswerPrompts.d.ts.map +1 -1
  40. package/dist/client/react/AnswerPrompts.js +16 -10
  41. package/dist/client/react/AnswerPrompts.js.map +1 -1
  42. package/dist/client/react/BoardShell.js +2 -2
  43. package/dist/client/react/BoardShell.js.map +1 -1
  44. package/dist/client/react/DocumentMain.d.ts.map +1 -1
  45. package/dist/client/react/DocumentMain.js +2 -2
  46. package/dist/client/react/DocumentMain.js.map +1 -1
  47. package/dist/client/react/FleetMain.js +1 -1
  48. package/dist/client/react/FleetMain.js.map +1 -1
  49. package/dist/client/react/FormField.js +1 -1
  50. package/dist/client/react/FormField.js.map +1 -1
  51. package/dist/client/react/GoalPage.js +2 -2
  52. package/dist/client/react/GoalPage.js.map +1 -1
  53. package/dist/client/react/GoalProgressHeader.js +2 -2
  54. package/dist/client/react/GoalProgressHeader.js.map +1 -1
  55. package/dist/client/react/Header.d.ts.map +1 -1
  56. package/dist/client/react/Header.js +3 -2
  57. package/dist/client/react/Header.js.map +1 -1
  58. package/dist/client/react/LibraryContents.js +1 -1
  59. package/dist/client/react/LibraryContents.js.map +1 -1
  60. package/dist/client/react/LibraryMain.d.ts.map +1 -1
  61. package/dist/client/react/LibraryMain.js +1 -1
  62. package/dist/client/react/LibraryMain.js.map +1 -1
  63. package/dist/client/react/LibraryPage.js +2 -2
  64. package/dist/client/react/LibraryPage.js.map +1 -1
  65. package/dist/client/react/LiveControls.js +1 -1
  66. package/dist/client/react/LiveControls.js.map +1 -1
  67. package/dist/client/react/ProjectionMain.d.ts.map +1 -1
  68. package/dist/client/react/ProjectionMain.js +1 -1
  69. package/dist/client/react/ProjectionMain.js.map +1 -1
  70. package/dist/client/react/PromptOption.d.ts.map +1 -1
  71. package/dist/client/react/PromptOption.js +7 -5
  72. package/dist/client/react/PromptOption.js.map +1 -1
  73. package/dist/client/react/ReadingHistory.js +1 -1
  74. package/dist/client/react/ReadingHistory.js.map +1 -1
  75. package/dist/client/react/RemainingMain.d.ts.map +1 -1
  76. package/dist/client/react/RemainingMain.js +1 -1
  77. package/dist/client/react/RemainingMain.js.map +1 -1
  78. package/dist/client/react/ResponseHistory.d.ts.map +1 -1
  79. package/dist/client/react/ResponseHistory.js +11 -5
  80. package/dist/client/react/ResponseHistory.js.map +1 -1
  81. package/dist/client/react/SearchPalette.js +1 -1
  82. package/dist/client/react/SearchPalette.js.map +1 -1
  83. package/dist/client/react/Sidebar.d.ts.map +1 -1
  84. package/dist/client/react/Sidebar.js +9 -2
  85. package/dist/client/react/Sidebar.js.map +1 -1
  86. package/dist/client/react/SystemFleetLane.d.ts.map +1 -1
  87. package/dist/client/react/SystemFleetLane.js +2 -1
  88. package/dist/client/react/SystemFleetLane.js.map +1 -1
  89. package/dist/client/react/SystemHealth.d.ts +2 -1
  90. package/dist/client/react/SystemHealth.d.ts.map +1 -1
  91. package/dist/client/react/SystemHealth.js +3 -2
  92. package/dist/client/react/SystemHealth.js.map +1 -1
  93. package/dist/client/react/SystemMain.d.ts.map +1 -1
  94. package/dist/client/react/SystemMain.js +6 -2
  95. package/dist/client/react/SystemMain.js.map +1 -1
  96. package/dist/client/react/SystemPage.d.ts +2 -1
  97. package/dist/client/react/SystemPage.d.ts.map +1 -1
  98. package/dist/client/react/SystemPage.js +2 -2
  99. package/dist/client/react/SystemPage.js.map +1 -1
  100. package/dist/client/react/TodayNeeds.d.ts +5 -0
  101. package/dist/client/react/TodayNeeds.d.ts.map +1 -1
  102. package/dist/client/react/TodayNeeds.js +6 -3
  103. package/dist/client/react/TodayNeeds.js.map +1 -1
  104. package/dist/client/react/TodayOptionButton.d.ts +14 -0
  105. package/dist/client/react/TodayOptionButton.d.ts.map +1 -0
  106. package/dist/client/react/TodayOptionButton.js +10 -0
  107. package/dist/client/react/TodayOptionButton.js.map +1 -0
  108. package/dist/client/react/TodayOptions.d.ts.map +1 -1
  109. package/dist/client/react/TodayOptions.js +18 -14
  110. package/dist/client/react/TodayOptions.js.map +1 -1
  111. package/dist/client/react/TodayPage.d.ts.map +1 -1
  112. package/dist/client/react/TodayPage.js +20 -5
  113. package/dist/client/react/TodayPage.js.map +1 -1
  114. package/dist/client/react/WorkCard.d.ts.map +1 -1
  115. package/dist/client/react/WorkCard.js +7 -2
  116. package/dist/client/react/WorkCard.js.map +1 -1
  117. package/dist/client/react/WorkLane.d.ts.map +1 -1
  118. package/dist/client/react/WorkLane.js +1 -1
  119. package/dist/client/react/WorkLane.js.map +1 -1
  120. package/dist/client/react/WorkNow.js +1 -1
  121. package/dist/client/react/WorkNow.js.map +1 -1
  122. package/dist/client/react/activeRead.js +1 -1
  123. package/dist/client/react/activeRead.js.map +1 -1
  124. package/dist/client/react/destinationName.d.ts +2 -0
  125. package/dist/client/react/destinationName.d.ts.map +1 -0
  126. package/dist/client/react/destinationName.js +4 -0
  127. package/dist/client/react/destinationName.js.map +1 -0
  128. package/dist/client/react/loadBoardPage.d.ts.map +1 -1
  129. package/dist/client/react/loadBoardPage.js +2 -2
  130. package/dist/client/react/loadBoardPage.js.map +1 -1
  131. package/dist/client/react/loadProjection.d.ts +135 -135
  132. package/dist/client/react/loadProjection.d.ts.map +1 -1
  133. package/dist/client/react/loadProjection.js +2 -2
  134. package/dist/client/react/loadProjection.js.map +1 -1
  135. package/dist/client/react/loadRemaining.d.ts +184 -184
  136. package/dist/client/react/loadRemaining.d.ts.map +1 -1
  137. package/dist/client/react/loadRemaining.js +2 -2
  138. package/dist/client/react/loadRemaining.js.map +1 -1
  139. package/dist/client/react/pageQueries.js +1 -1
  140. package/dist/client/react/pageQueries.js.map +1 -1
  141. package/dist/client/react/resolvedDocumentFailure.d.ts.map +1 -0
  142. package/dist/client/react/resolvedDocumentFailure.js.map +1 -0
  143. package/dist/client/react/routeQuery.d.ts.map +1 -0
  144. package/dist/client/react/routeQuery.js.map +1 -0
  145. package/dist/client/react/useHandoffForm.js +1 -1
  146. package/dist/client/react/useHandoffForm.js.map +1 -1
  147. package/dist/client/react/useProjectionData.d.ts +1 -1
  148. package/dist/client/react/useProjectionData.js +1 -1
  149. package/dist/client/react/useProjectionData.js.map +1 -1
  150. package/dist/client/react/useResponseForm.d.ts.map +1 -1
  151. package/dist/client/react/useResponseForm.js +3 -3
  152. package/dist/client/react/useResponseForm.js.map +1 -1
  153. package/dist/client/responses/summary.d.ts +1 -0
  154. package/dist/client/responses/summary.d.ts.map +1 -1
  155. package/dist/client/responses/summary.js +12 -1
  156. package/dist/client/responses/summary.js.map +1 -1
  157. package/dist/diagnostics/source.js +1 -1
  158. package/dist/diagnostics/source.js.map +1 -1
  159. package/dist/rpc/contract.d.ts +1951 -1918
  160. package/dist/rpc/contract.d.ts.map +1 -1
  161. package/dist/server/Responses.js +2 -2
  162. package/dist/server/Responses.js.map +1 -1
  163. package/dist/server/attention/inbox.d.ts +13 -2
  164. package/dist/server/attention/inbox.d.ts.map +1 -1
  165. package/dist/server/attention/inbox.js +44 -27
  166. package/dist/server/attention/inbox.js.map +1 -1
  167. package/dist/server/attention/managedRequestReceipt.d.ts +10 -0
  168. package/dist/server/attention/managedRequestReceipt.d.ts.map +1 -1
  169. package/dist/server/attention/managedRequestReceipt.js +29 -10
  170. package/dist/server/attention/managedRequestReceipt.js.map +1 -1
  171. package/dist/server/attention/projection.d.ts.map +1 -1
  172. package/dist/server/attention/projection.js +2 -1
  173. package/dist/server/attention/projection.js.map +1 -1
  174. package/dist/server/attention/requestInbox.d.ts +3 -3
  175. package/dist/server/attention/requestInbox.js +2 -2
  176. package/dist/server/attention/requestInbox.js.map +1 -1
  177. package/dist/server/attention/requests.d.ts +1 -0
  178. package/dist/server/attention/requests.d.ts.map +1 -1
  179. package/dist/server/attention/requests.js +7 -3
  180. package/dist/server/attention/requests.js.map +1 -1
  181. package/dist/server/board.js +1 -1
  182. package/dist/server/board.js.map +1 -1
  183. package/dist/server/document/answerBody.d.ts.map +1 -1
  184. package/dist/server/document/answerBody.js +9 -1
  185. package/dist/server/document/answerBody.js.map +1 -1
  186. package/dist/server/document/reviewedWork.d.ts +3 -0
  187. package/dist/server/document/reviewedWork.d.ts.map +1 -0
  188. package/dist/server/document/reviewedWork.js +23 -0
  189. package/dist/server/document/reviewedWork.js.map +1 -0
  190. package/dist/server/files/changes.d.ts.map +1 -1
  191. package/dist/server/files/changes.js +1 -1
  192. package/dist/server/files/changes.js.map +1 -1
  193. package/dist/server/files/watchDirectories.d.ts +1 -1
  194. package/dist/server/files/watchDirectories.d.ts.map +1 -1
  195. package/dist/server/files/watchDirectories.js +71 -12
  196. package/dist/server/files/watchDirectories.js.map +1 -1
  197. package/dist/server/handoffs/Service.js +2 -2
  198. package/dist/server/handoffs/Service.js.map +1 -1
  199. package/dist/server/handoffs/source.js +1 -1
  200. package/dist/server/handoffs/source.js.map +1 -1
  201. package/dist/server/http/boardRoutes.js +1 -1
  202. package/dist/server/http/boardRoutes.js.map +1 -1
  203. package/dist/server/mcp/client.d.ts +11 -0
  204. package/dist/server/mcp/client.d.ts.map +1 -1
  205. package/dist/server/mcp/queries.d.ts +2851 -2818
  206. package/dist/server/mcp/queries.d.ts.map +1 -1
  207. package/dist/server/packageInfo.d.ts +1 -1
  208. package/dist/server/packageInfo.js +1 -1
  209. package/dist/server/render/Highlighter.js +1 -1
  210. package/dist/server/render/Highlighter.js.map +1 -1
  211. package/dist/server/responses/locate.js +1 -1
  212. package/dist/server/responses/locate.js.map +1 -1
  213. package/dist/server/rpc/handlers.js +2 -2
  214. package/dist/server/rpc/handlers.js.map +1 -1
  215. package/dist/server/serve.js +1 -1
  216. package/dist/server/serve.js.map +1 -1
  217. package/dist/server/views/projectionQueries.d.ts +11 -0
  218. package/dist/server/views/projectionQueries.d.ts.map +1 -1
  219. package/dist/server/workspace.js +1 -1
  220. package/dist/server/workspace.js.map +1 -1
  221. package/docs/delivery/README.md +2 -2
  222. package/package.json +18 -362
  223. package/src/browser/responses/cardContext.ts +13 -7
  224. package/src/browser/views/now.ts +42 -2
  225. package/src/browser/views/schema.ts +1 -0
  226. package/src/client/document/View.ts +1 -1
  227. package/src/client/formStatus.ts +3 -3
  228. package/src/client/react/AnswerContext.tsx +9 -4
  229. package/src/client/react/AnswerForm.tsx +25 -6
  230. package/src/client/react/AnswerMarkdown.tsx +15 -0
  231. package/src/client/react/AnswerMore.tsx +58 -46
  232. package/src/client/react/AnswerPanel.tsx +14 -8
  233. package/src/client/react/AnswerPrompts.tsx +33 -18
  234. package/src/client/react/BoardShell.tsx +2 -2
  235. package/src/client/react/DocumentMain.tsx +2 -2
  236. package/src/client/react/FleetMain.tsx +1 -1
  237. package/src/client/react/FormField.tsx +1 -1
  238. package/src/client/react/GoalPage.tsx +2 -2
  239. package/src/client/react/GoalProgressHeader.tsx +2 -2
  240. package/src/client/react/Header.tsx +3 -2
  241. package/src/client/react/LibraryContents.tsx +1 -1
  242. package/src/client/react/LibraryMain.tsx +1 -1
  243. package/src/client/react/LibraryPage.tsx +2 -2
  244. package/src/client/react/LiveControls.tsx +1 -1
  245. package/src/client/react/ProjectionMain.tsx +1 -1
  246. package/src/client/react/PromptOption.tsx +39 -24
  247. package/src/client/react/ReadingHistory.tsx +1 -1
  248. package/src/client/react/RemainingMain.tsx +1 -1
  249. package/src/client/react/ResponseHistory.tsx +53 -14
  250. package/src/client/react/SearchPalette.tsx +1 -1
  251. package/src/client/react/Sidebar.tsx +10 -2
  252. package/src/client/react/SystemFleetLane.tsx +2 -1
  253. package/src/client/react/SystemHealth.tsx +24 -4
  254. package/src/client/react/SystemMain.tsx +6 -2
  255. package/src/client/react/SystemPage.tsx +8 -2
  256. package/src/client/react/TodayNeeds.tsx +21 -6
  257. package/src/client/react/TodayOptionButton.tsx +35 -0
  258. package/src/client/react/TodayOptions.tsx +42 -33
  259. package/src/client/react/TodayPage.tsx +36 -7
  260. package/src/client/react/WorkCard.tsx +16 -8
  261. package/src/client/react/WorkLane.tsx +8 -2
  262. package/src/client/react/WorkNow.tsx +1 -1
  263. package/src/client/react/activeRead.ts +1 -1
  264. package/src/client/react/destinationName.ts +3 -0
  265. package/src/client/react/entry.css +0 -6
  266. package/src/client/react/loadBoardPage.ts +2 -2
  267. package/src/client/react/loadProjection.ts +2 -2
  268. package/src/client/react/loadRemaining.ts +2 -2
  269. package/src/client/react/pageQueries.ts +1 -1
  270. package/src/client/react/useHandoffForm.ts +1 -1
  271. package/src/client/react/useProjectionData.ts +1 -1
  272. package/src/client/react/useResponseForm.ts +5 -3
  273. package/src/client/responses/summary.ts +13 -1
  274. package/src/diagnostics/source.ts +1 -1
  275. package/src/server/Responses.ts +2 -2
  276. package/src/server/attention/inbox.ts +51 -27
  277. package/src/server/attention/managedRequestReceipt.ts +41 -10
  278. package/src/server/attention/projection.ts +5 -2
  279. package/src/server/attention/requestInbox.ts +2 -2
  280. package/src/server/attention/requests.ts +7 -2
  281. package/src/server/board.ts +1 -1
  282. package/src/server/document/answerBody.ts +10 -1
  283. package/src/server/document/reviewedWork.ts +24 -0
  284. package/src/server/files/changes.ts +1 -0
  285. package/src/server/files/watchDirectories.ts +90 -27
  286. package/src/server/handoffs/Service.ts +2 -2
  287. package/src/server/handoffs/source.ts +1 -1
  288. package/src/server/http/boardRoutes.ts +1 -1
  289. package/src/server/packageInfo.ts +1 -1
  290. package/src/server/render/Highlighter.ts +1 -1
  291. package/src/server/responses/locate.ts +1 -1
  292. package/src/server/rpc/handlers.ts +2 -2
  293. package/src/server/serve.ts +1 -1
  294. package/src/server/workspace.ts +1 -1
  295. package/dist/client/page/resolvedDocumentFailure.d.ts.map +0 -1
  296. package/dist/client/page/resolvedDocumentFailure.js.map +0 -1
  297. package/dist/client/page/routeQuery.d.ts.map +0 -1
  298. package/dist/client/page/routeQuery.js.map +0 -1
  299. /package/dist/client/{page → react}/resolvedDocumentFailure.d.ts +0 -0
  300. /package/dist/client/{page → react}/resolvedDocumentFailure.js +0 -0
  301. /package/dist/client/{page → react}/routeQuery.d.ts +0 -0
  302. /package/dist/client/{page → react}/routeQuery.js +0 -0
  303. /package/src/client/{page → react}/resolvedDocumentFailure.ts +0 -0
  304. /package/src/client/{page → react}/routeQuery.ts +0 -0
@@ -1,8 +1,8 @@
1
1
  import { Option } from "effect";
2
2
  import { useState } from "react";
3
- import { useQuery } from "@shivaedev/effect-react/result-state.ts";
4
- import { routeQueryOptions } from "#client/page/routeQuery.ts";
3
+ import { useQuery } from "@shivaedev/effect-react/client/resultState.ts";
5
4
  import { LibraryContents } from "./LibraryContents.tsx";
5
+ import { routeQueryOptions } from "./routeQuery.ts";
6
6
  import type { ShellApi } from "./ShellApi.ts";
7
7
 
8
8
  export function LibraryPage({ api, url = "/_board/library" }: { readonly api: ShellApi; readonly url?: string }) {
@@ -2,7 +2,7 @@ import { useAtomValue } from "@effect/atom-react";
2
2
  import { Option } from "effect";
3
3
  import * as Atom from "effect/reactivity/Atom";
4
4
  import { useMemo } from "react";
5
- import { useQuery } from "@shivaedev/effect-react/result-state.ts";
5
+ import { useQuery } from "@shivaedev/effect-react/client/resultState.ts";
6
6
  import type { BrowserClient } from "#client/client.ts";
7
7
  import { activeRead } from "./activeRead.ts";
8
8
  import { liveStatus } from "./liveStatus.ts";
@@ -1,9 +1,9 @@
1
1
  import { documentSourceId } from "#client/document/layout.ts";
2
- import { routeQueryOptions } from "#client/page/routeQuery.ts";
3
2
  import { HomeBoard } from "./HomeBoard.tsx";
4
3
  import type { MainLocation } from "./mainLocation.ts";
5
4
  import { ProjectionFrame } from "./ProjectionFrame.tsx";
6
5
  import type { ProjectionRoute } from "./projectionRoute.ts";
6
+ import { routeQueryOptions } from "./routeQuery.ts";
7
7
  import type { ShellApi } from "./ShellApi.ts";
8
8
  import { TodayPage } from "./TodayPage.tsx";
9
9
  import { WorkPage } from "./WorkPage.tsx";
@@ -1,8 +1,10 @@
1
1
  import { useId } from "react";
2
2
  import type { Document } from "#browser/document/schema.ts";
3
- import { optionLabel } from "#browser/responses/cardContext.ts";
3
+ import { optionText } from "#browser/responses/cardContext.ts";
4
4
  import type { Prompt } from "#browser/responses/Prompt.ts";
5
- import { ResponseMarkdown } from "./ResponseMarkdown.tsx";
5
+ import { Badge } from "#client/react/components/ui/Badge.tsx";
6
+ import { cn } from "#client/react/lib/cn.ts";
7
+
6
8
  export function PromptOption({
7
9
  document,
8
10
  prompt,
@@ -20,37 +22,50 @@ export function PromptOption({
20
22
  }) {
21
23
  const source = document.metadata.options?.[choice];
22
24
  const id = useId();
23
- const control = (
24
- <input
25
- checked={checked}
26
- data-option-source={option.source}
27
- hidden
28
- id={id}
29
- name={`choice-${prompt.id}`}
30
- onChange={(event) => choose(option.id, event.currentTarget.checked)}
31
- onInput={(event) => choose(option.id, event.currentTarget.checked)}
32
- type={prompt.mode === "one" ? "radio" : "checkbox"}
33
- value={option.id}
34
- />
35
- );
36
- const label = source?.label ?? optionLabel(option.id, option.source, document.requests.find((request) => request.open)?.preview?.reason ?? "");
25
+ const authored = optionText(option.id, option.source);
26
+ const label = source?.label ?? authored.label;
27
+ const description = source?.description ?? authored.description;
37
28
  return (
38
29
  <div>
39
- {control}
30
+ <input
31
+ checked={checked}
32
+ data-option-source={option.source}
33
+ hidden
34
+ id={id}
35
+ name={`choice-${prompt.id}`}
36
+ onChange={(event) => choose(option.id, event.currentTarget.checked)}
37
+ onInput={(event) => choose(option.id, event.currentTarget.checked)}
38
+ type={prompt.mode === "one" ? "radio" : "checkbox"}
39
+ value={option.id}
40
+ />
40
41
  <button
41
42
  aria-pressed={checked}
42
- className="question-choice"
43
+ className={cn(
44
+ "question-choice flex w-full items-start gap-3 rounded-lg border bg-background px-4 py-3 text-left transition-colors hover:bg-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
45
+ checked && "border-primary bg-accent",
46
+ )}
43
47
  data-choice={option.id}
44
48
  onClick={() => choose(option.id, prompt.mode !== "many" || !checked)}
45
49
  type="button"
46
50
  >
47
- <strong>
48
- {label}
49
- {label === document.metadata.recommended && " · Recommended"}
50
- </strong>
51
- {source !== undefined && <small>{source.description}</small>}
51
+ <span
52
+ aria-hidden="true"
53
+ className={cn(
54
+ "mt-0.5 grid size-4 shrink-0 place-items-center border border-muted-foreground",
55
+ prompt.mode === "many" ? "rounded-sm" : "rounded-full",
56
+ checked && "border-primary",
57
+ )}
58
+ >
59
+ {checked ? <span className={cn("size-2 bg-primary", prompt.mode === "many" ? "rounded-[1px]" : "rounded-full")} /> : null}
60
+ </span>
61
+ <span className="grid gap-1">
62
+ <strong className="flex flex-wrap items-center gap-2 font-medium text-sm">
63
+ {label}
64
+ {label === document.metadata.recommended && <Badge variant="outline">Recommended</Badge>}
65
+ </strong>
66
+ {Boolean(description) && <small className="text-muted-foreground text-sm">{description}</small>}
67
+ </span>
52
68
  </button>
53
- {source === undefined && <ResponseMarkdown file={document.file} source={option.source.replace(/^#{1,6}\s+.+$/mu, "")} />}
54
69
  </div>
55
70
  );
56
71
  }
@@ -1,5 +1,5 @@
1
1
  import { Option } from "effect";
2
- import { useQuery } from "@shivaedev/effect-react/result-state.ts";
2
+ import { useQuery } from "@shivaedev/effect-react/client/resultState.ts";
3
3
  import type { Item } from "#client/react/LibraryContext.ts";
4
4
  import { fileUrl } from "#files/url.ts";
5
5
  import type { ShellApi } from "./ShellApi.ts";
@@ -1,4 +1,3 @@
1
- import { routeQueryOptions } from "#client/page/routeQuery.ts";
2
1
  import { GoalsPage } from "./GoalsPage.tsx";
3
2
  import { HandoffPage } from "./HandoffPage.tsx";
4
3
  import { LibraryMain } from "./LibraryMain.tsx";
@@ -6,6 +5,7 @@ import type { MainLocation } from "./mainLocation.ts";
6
5
  import { ProjectionFrame } from "./ProjectionFrame.tsx";
7
6
  import { ResultPage } from "./ResultPage.tsx";
8
7
  import type { RemainingRoute } from "./remainingRoute.ts";
8
+ import { routeQueryOptions } from "./routeQuery.ts";
9
9
  import type { ShellApi } from "./ShellApi.ts";
10
10
  import { StartPage } from "./StartPage.tsx";
11
11
  import { SystemMain } from "./SystemMain.tsx";
@@ -1,6 +1,12 @@
1
1
  import type { DocumentRequest } from "#browser/document/schema.ts";
2
- import { ResponseMarkdown } from "./ResponseMarkdown.tsx";
2
+ import type { RecordedResponse } from "#browser/responses/schema.ts";
3
+ import { chosenLabels } from "#client/responses/summary.ts";
4
+ import { Age } from "./Age.tsx";
5
+ import { AnswerMarkdown } from "./AnswerMarkdown.tsx";
3
6
  import { shellIds } from "./shellIds.ts";
7
+
8
+ const KINDS = { "answer": "answered", "clarify": "asked back", "not_now": "said not now" } as const;
9
+
4
10
  export function ResponseHistory({
5
11
  request,
6
12
  id = shellIds.responseHistory,
@@ -11,34 +17,67 @@ export function ResponseHistory({
11
17
  const records = request?.responses ?? [];
12
18
  const preview = request?.preview;
13
19
  return (
14
- <section id={id}>
15
- <h2>Recorded responses</h2>
20
+ <section className="space-y-3 border-t pt-4" id={id}>
21
+ <h2 className="font-medium text-sm">Recorded responses</h2>
16
22
  {records.length === 0 ? (
17
- <p>No response is recorded for this source revision.</p>
23
+ <p className="text-muted-foreground text-sm">No response is recorded yet.</p>
18
24
  ) : (
19
- records.map((record) => <ResponseEntry current={record.response.question === preview?.id} key={record.id} record={record} />)
25
+ records.map((record) => (
26
+ <ResponseEntry current={record.response.question === preview?.id} key={record.id} record={record} request={request} />
27
+ ))
20
28
  )}
21
29
  </section>
22
30
  );
23
31
  }
24
- function ResponseEntry({ record, current }: { readonly record: import("#browser/responses/schema.ts").RecordedResponse; readonly current: boolean }) {
32
+ function ResponseEntry({
33
+ record,
34
+ current,
35
+ request,
36
+ }: {
37
+ readonly record: RecordedResponse;
38
+ readonly current: boolean;
39
+ readonly request: DocumentRequest | undefined;
40
+ }) {
41
+ const answers = record.response.answers;
42
+ const chosen = chosenLabels(request?.prompts ?? [], answers);
43
+ const notes = (answers ?? []).map((answer) => answer.text.trim()).filter(Boolean);
25
44
  return (
26
- <article id={record.id}>
27
- <h3>
28
- <a href={`/responses/${record.id}.md`}>
29
- {record.response.author} · {record.response.type}
45
+ <article className="space-y-2 rounded-lg bg-muted/50 px-4 py-3 text-sm" id={record.id}>
46
+ <h3 className="flex flex-wrap items-baseline gap-x-1.5 font-normal">
47
+ <a className="font-medium underline-offset-4 hover:underline" href={`/responses/${record.id}.md`}>
48
+ {record.response.author}
30
49
  </a>
50
+ <span className="text-muted-foreground">{KINDS[record.response.type]}</span>
51
+ <Age modified={record.response.recordedAt} short />
31
52
  </h3>
32
- <p>
53
+ {answers === undefined ? (
54
+ <AnswerMarkdown file={`responses/${record.id}.md`} source={record.body} />
55
+ ) : (
56
+ <>
57
+ <p>
58
+ <span className="text-muted-foreground">Chose </span>
59
+ <strong className="font-medium">{chosen || "no listed option"}</strong>
60
+ </p>
61
+ {notes.map((note) => (
62
+ <p className="whitespace-pre-wrap" key={note}>
63
+ {note}
64
+ </p>
65
+ ))}
66
+ <details className="text-muted-foreground text-xs">
67
+ <summary className="cursor-pointer select-none">Full response</summary>
68
+ <AnswerMarkdown className="mt-2 text-foreground text-sm" file={`responses/${record.id}.md`} source={record.body} />
69
+ </details>
70
+ </>
71
+ )}
72
+ <p className="text-muted-foreground text-xs" title={record.response.reviewedRevision}>
33
73
  {current ? "This reviewed revision" : "Earlier reviewed context — does not approve the current source"} ·{" "}
34
- <code>{record.response.reviewedRevision}</code>
74
+ <code>{record.response.reviewedRevision.slice(0, 12)}</code>
35
75
  </p>
36
76
  {Boolean(record.response.supersedes) && (
37
- <p>
77
+ <p className="text-muted-foreground text-xs">
38
78
  Explicitly supersedes <a href={`/responses/${record.response.supersedes}.md`}>{record.response.supersedes}</a>.
39
79
  </p>
40
80
  )}
41
- <ResponseMarkdown file={`responses/${record.id}.md`} source={record.body} />
42
81
  </article>
43
82
  );
44
83
  }
@@ -1,6 +1,6 @@
1
1
  import { Option } from "effect";
2
2
  import { useEffect, useRef, useState } from "react";
3
- import { useQuery } from "@shivaedev/effect-react/result-state.ts";
3
+ import { useQuery } from "@shivaedev/effect-react/client/resultState.ts";
4
4
  import { SearchResults } from "#client/react/SearchResults.tsx";
5
5
  import type { ShellApi } from "#client/react/ShellApi.ts";
6
6
  import { matchingCommands, type SearchItem } from "#client/react/searchCommands.ts";
@@ -1,10 +1,11 @@
1
1
  import { Option } from "effect";
2
2
  import { PanelsTopLeft } from "lucide-react";
3
3
  import { useContext, useEffect } from "react";
4
- import { useQuery } from "@shivaedev/effect-react/result-state.ts";
4
+ import { useQuery } from "@shivaedev/effect-react/client/resultState.ts";
5
5
  import type { Navigation } from "#browser/Navigation.ts";
6
6
  import { Badge } from "#client/react/components/ui/Badge.tsx";
7
7
  import { buttonVariants } from "#client/react/components/ui/buttonVariants.ts";
8
+ import { destinationName } from "#client/react/destinationName.ts";
8
9
  import { GoalProgress } from "#client/react/GoalProgress.tsx";
9
10
  import { InboxCount } from "#client/react/Header.tsx";
10
11
  import { type Item, LibraryContext } from "#client/react/LibraryContext.ts";
@@ -38,6 +39,12 @@ export function Sidebar({
38
39
  today: data?.inboxCount,
39
40
  work: data?.counts.now,
40
41
  };
42
+ function name(view: (typeof workspaceDestinations)[number]["view"], label: string) {
43
+ if (view === "today") {
44
+ return destinationName(label, counts.today, "need you");
45
+ }
46
+ return view === "work" ? destinationName(label, counts.work, "open tasks") : label;
47
+ }
41
48
  useEffect(() => {
42
49
  const sidebar = document.getElementById(shellIds.sidebar);
43
50
  if (controls.narrow && controls.open) {
@@ -66,6 +73,7 @@ export function Sidebar({
66
73
  {workspaceDestinations.map(({ label, href, view, id }) => (
67
74
  <a
68
75
  aria-current={(view === "today" ? home : main.view === view) ? "page" : undefined}
76
+ aria-label={name(view, label)}
69
77
  className={cn(
70
78
  buttonVariants({ variant: "ghost" }),
71
79
  "justify-between no-underline",
@@ -77,7 +85,7 @@ export function Sidebar({
77
85
  >
78
86
  <span>{label}</span>
79
87
  {view === "today" && <InboxCount count={counts.today ?? null} id={shellIds.inboxCount} />}
80
- {view === "work" && <Badge>{counts.work ?? "?"}</Badge>}
88
+ {view === "work" && counts.work !== undefined && <Badge aria-hidden="true">{counts.work}</Badge>}
81
89
  </a>
82
90
  ))}
83
91
  </nav>
@@ -1,4 +1,5 @@
1
1
  import type { FleetItemsProjection } from "#browser/views/FleetItemsProjection.ts";
2
+ import { fleetHolder } from "#browser/views/now.ts";
2
3
  import { Badge } from "#client/react/components/ui/Badge.tsx";
3
4
  import { Card } from "#client/react/components/ui/Card.tsx";
4
5
 
@@ -29,7 +30,7 @@ function FleetRow({ item }: { readonly item: Item }) {
29
30
  {title}
30
31
  </a>
31
32
  <Badge variant="outline">{item.stage}</Badge>
32
- {Boolean(item.attentionOwner) && <span className="text-muted-foreground text-xs">Waiting on {item.attentionOwner}</span>}
33
+ <span className="text-muted-foreground text-xs">{fleetHolder(item)}</span>
33
34
  {Boolean(item.prUrl) && (
34
35
  <a aria-label={`Pull request for ${title}`} className="text-sm underline" href={item.prUrl}>
35
36
  PR ↗
@@ -1,6 +1,17 @@
1
1
  import type { FleetHealth } from "#diagnostics/health.ts";
2
2
  import { ObservedTime } from "./diagnosticsValues.tsx";
3
- export function SystemHealth({ value, now }: { readonly value: FleetHealth; readonly now: number }) {
3
+
4
+ const waitingOn = { automation: "Waiting on Fleet agents", orchestrator: "Waiting on the orchestrator" } as const;
5
+
6
+ export function SystemHealth({
7
+ value,
8
+ now,
9
+ needsYou,
10
+ }: {
11
+ readonly value: FleetHealth;
12
+ readonly now: number;
13
+ readonly needsYou?: number | null | undefined;
14
+ }) {
4
15
  let freshness = "Current snapshot";
5
16
  if (now < value.observedAt) {
6
17
  freshness = "Publisher clock is ahead";
@@ -26,11 +37,20 @@ export function SystemHealth({ value, now }: { readonly value: FleetHealth; read
26
37
  <strong>{value.queued}</strong>
27
38
  </dd>
28
39
  </div>
29
- {Object.entries(value.waiting).map(([owner, count]) => (
40
+ <div data-needs-you>
41
+ <dt>Needs you</dt>
42
+ <dd className="flex flex-wrap items-baseline gap-2">
43
+ <strong>{needsYou ?? "Unavailable"}</strong>
44
+ <a className="text-xs" href="/#needs-you">
45
+ Open Today
46
+ </a>
47
+ </dd>
48
+ </div>
49
+ {(["orchestrator", "automation"] as const).map((owner) => (
30
50
  <div key={owner}>
31
- <dt>Waiting on {owner}</dt>
51
+ <dt>{waitingOn[owner]}</dt>
32
52
  <dd>
33
- <strong>{count}</strong>
53
+ <strong>{value.waiting[owner]}</strong>
34
54
  </dd>
35
55
  </div>
36
56
  ))}
@@ -1,7 +1,9 @@
1
+ import { Option } from "effect";
1
2
  import { useContext } from "react";
2
- import { routeQueryOptions } from "#client/page/routeQuery.ts";
3
+ import { useQuery } from "@shivaedev/effect-react/client/resultState.ts";
3
4
  import type { MainLocation } from "./mainLocation.ts";
4
5
  import { ProjectionFrame } from "./ProjectionFrame.tsx";
6
+ import { navigationQuery, routeQueryOptions } from "./routeQuery.ts";
5
7
  import { SessionContext } from "./SessionContext.ts";
6
8
  import type { ShellApi } from "./ShellApi.ts";
7
9
  import { SystemPage } from "./SystemPage.tsx";
@@ -18,12 +20,14 @@ export function SystemMain({
18
20
  }) {
19
21
  const atom = api.diagnostics.query(undefined, routeQueryOptions);
20
22
  useDiagnosticsPolling(useContext(SessionContext), atom, api.fleetItems.query(undefined, routeQueryOptions));
23
+ const navigation = useQuery(navigationQuery(api), { keepPreviousData: true });
24
+ const needsYou = Option.getOrUndefined(navigation.data)?.inboxCount;
21
25
  return (
22
26
  <ProjectionFrame
23
27
  atom={atom}
24
28
  location={() => ({ file: "System", status: 200, view: "system" })}
25
29
  onPage={onPage}
26
- render={(data) => <SystemPage data={data} />}
30
+ render={(data) => <SystemPage data={data} needsYou={needsYou} />}
27
31
  route="system"
28
32
  url={url}
29
33
  />
@@ -7,7 +7,13 @@ import { FleetItemsContext } from "./FleetItemsContext.ts";
7
7
  import { githubLimited } from "./githubLimited.ts";
8
8
  import { SystemFleetLane } from "./SystemFleetLane.tsx";
9
9
  import { SystemHealth } from "./SystemHealth.tsx";
10
- export function SystemPage({ data: { sources: data, now } }: { readonly data: DiagnosticsProjection }) {
10
+ export function SystemPage({
11
+ data: { sources: data, now },
12
+ needsYou,
13
+ }: {
14
+ readonly data: DiagnosticsProjection;
15
+ readonly needsYou?: number | null | undefined;
16
+ }) {
11
17
  const items = useContext(FleetItemsContext);
12
18
  const fleet = data.fleet.state === "ready" ? { ...data.fleet, observedAt: data.fleet.value.observedAt } : data.fleet;
13
19
  return (
@@ -38,7 +44,7 @@ export function SystemPage({ data: { sources: data, now } }: { readonly data: Di
38
44
  || now < fleet.value.observedAt)
39
45
  }
40
46
  name="Fleet"
41
- render={(value) => <SystemHealth now={now} value={value} />}
47
+ render={(value) => <SystemHealth needsYou={needsYou} now={now} value={value} />}
42
48
  source={fleet}
43
49
  stale={fleet.state === "ready" && now - fleet.value.observedAt > fleet.value.staleAfterMs}
44
50
  />
@@ -1,3 +1,4 @@
1
+ import type { ReactNode } from "react";
1
2
  import type { InboxProjection } from "#browser/views/schema.ts";
2
3
  import { StatusChip } from "#client/react/components/ui/StatusChip.tsx";
3
4
  import { Age } from "./Age.tsx";
@@ -13,7 +14,9 @@ export function TodayNeeds({ api, data }: { readonly api: ShellApi; readonly dat
13
14
  <ul aria-label="Needs you" className="m-0 list-none divide-y rounded-lg border border-border p-0">
14
15
  {data.you.map((item) => (
15
16
  <li className="h-12 max-h-12" data-attention-id={item.id} key={`${item.file}:${item.href}`}>
16
- <TodayRow api={api} item={item} />
17
+ <TodayRow item={item} status="needs-you">
18
+ <TodayOptions api={api} item={item} />
19
+ </TodayRow>
17
20
  </li>
18
21
  ))}
19
22
  </ul>
@@ -22,13 +25,25 @@ export function TodayNeeds({ api, data }: { readonly api: ShellApi; readonly dat
22
25
  );
23
26
  }
24
27
 
25
- function TodayRow({ api, item }: { readonly api: ShellApi; readonly item: Item }) {
28
+ export function TodayLater({ items }: { readonly items: readonly Item[] }) {
26
29
  return (
27
- <div className="today-row flex h-full min-w-0 items-center gap-2 px-2" data-attention-row>
30
+ <ul aria-label="Later" className="m-0 list-none divide-y rounded-lg border border-border p-0">
31
+ {items.map((item) => (
32
+ <li className="h-12 max-h-12" key={item.file}>
33
+ <TodayRow item={item} status="deferred" />
34
+ </li>
35
+ ))}
36
+ </ul>
37
+ );
38
+ }
39
+
40
+ function TodayRow({ children, item, status }: { readonly children?: ReactNode; readonly item: Item; readonly status: "deferred" | "needs-you" }) {
41
+ return (
42
+ <div className="today-row flex h-full min-w-0 items-center gap-2 px-2" data-attention-row={status === "needs-you" ? "" : undefined}>
28
43
  <StatusChip
29
- aria-label="Needs you"
44
+ aria-label={status === "needs-you" ? "Needs you" : "Deferred"}
30
45
  className="shrink-0 max-sm:size-6 max-sm:justify-center max-sm:gap-0 max-sm:px-1 max-sm:text-[0px] max-sm:[&_svg]:shrink-0"
31
- status="needs-you"
46
+ status={status}
32
47
  />
33
48
  <div className="min-w-0 flex-1">
34
49
  <div className="flex min-w-0 items-baseline gap-2">
@@ -48,7 +63,7 @@ function TodayRow({ api, item }: { readonly api: ShellApi; readonly item: Item }
48
63
  <span className="shrink-0 text-[10px] text-muted-foreground tabular-nums">
49
64
  {item.modified === undefined ? "Age ?" : <Age modified={item.modified} short />}
50
65
  </span>
51
- <TodayOptions api={api} item={item} />
66
+ {children}
52
67
  </div>
53
68
  );
54
69
  }
@@ -0,0 +1,35 @@
1
+ import { optionText } from "#browser/responses/cardContext.ts";
2
+ import { Button } from "#client/react/components/ui/Button.tsx";
3
+
4
+ export function TodayOptionButton({
5
+ disabled,
6
+ onChoose,
7
+ option,
8
+ recommended,
9
+ source,
10
+ }: {
11
+ readonly disabled: boolean;
12
+ readonly onChoose: () => void;
13
+ readonly option: { readonly id: string; readonly source: string };
14
+ readonly recommended: string | undefined;
15
+ readonly source: { readonly label: string; readonly description: string } | undefined;
16
+ }) {
17
+ const authored = optionText(option.id, option.source);
18
+ const text = source?.label ?? authored.label;
19
+ const description = source?.description ?? authored.description;
20
+ return (
21
+ <Button
22
+ className="h-auto w-full flex-col items-start gap-0.5 whitespace-normal px-3 py-2 text-left"
23
+ disabled={disabled}
24
+ onClick={onChoose}
25
+ size="sm"
26
+ variant="outline"
27
+ >
28
+ <span>
29
+ {text}
30
+ {recommended === text ? " · Recommended" : null}
31
+ </span>
32
+ {description ? <span className="font-normal text-muted-foreground text-xs">{description}</span> : null}
33
+ </Button>
34
+ );
35
+ }
@@ -1,21 +1,23 @@
1
1
  import { RegistryContext } from "@effect/atom-react";
2
2
  import { Cause, Effect, Option } from "effect";
3
- import { useContext, useMemo, useState } from "react";
4
- import { useAction, useQuery } from "@shivaedev/effect-react/result-state.ts";
3
+ import { useContext, useMemo, useRef, useState } from "react";
4
+ import { useAction, useQuery } from "@shivaedev/effect-react/client/resultState.ts";
5
5
  import type { Document, DocumentRequest } from "#browser/document/schema.ts";
6
6
  import { answerBody } from "#browser/responses/answerBody.ts";
7
- import { optionLabel } from "#browser/responses/cardContext.ts";
7
+ import { promptHeading } from "#browser/responses/cardContext.ts";
8
+ import { reasonMarkdown } from "#browser/responses/reasonMarkdown.ts";
8
9
  import type { InboxProjection } from "#browser/views/schema.ts";
9
- import { documentQuery, navigationQuery, routeQueryOptions } from "#client/page/routeQuery.ts";
10
- import { Button } from "#client/react/components/ui/Button.tsx";
11
10
  import { Popover } from "#client/react/components/ui/Popover.tsx";
12
11
  import { authorLabel } from "#client/responses/authorLabel.ts";
12
+ import { chosenLabels } from "#client/responses/summary.ts";
13
13
  import { saveFailure } from "#client/saveFailure.ts";
14
14
  import { fileUrl } from "#files/url.ts";
15
15
  import { responseUrl } from "#responses/url.ts";
16
16
  import { createResponse } from "./createResponse.ts";
17
17
  import { useFormClient } from "./FormContext.ts";
18
+ import { documentQuery, navigationQuery, routeQueryOptions } from "./routeQuery.ts";
18
19
  import type { ShellApi } from "./ShellApi.ts";
20
+ import { TodayOptionButton } from "./TodayOptionButton.tsx";
19
21
 
20
22
  export function TodayOptions({ api, item }: { readonly api: ShellApi; readonly item: InboxProjection["you"][number] }) {
21
23
  const query = useQuery(documentQuery(api, fileUrl(item.file)), { keepPreviousData: true });
@@ -28,11 +30,11 @@ export function TodayOptions({ api, item }: { readonly api: ShellApi; readonly i
28
30
  && !request.promptUnavailable
29
31
  && request.prompts?.length === 1
30
32
  && request.prompts[0]?.mode === "one" ? (
31
- <Popover label={`Answer ${item.title}`} trigger="Answer" triggerClassName="h-8 w-auto px-1 text-xs">
32
- <a className="mb-3 block text-xs" href={item.href}>
33
- Open full question
34
- </a>
33
+ <Popover className="w-80" label={`Answer ${item.title}`} trigger="Answer" triggerClassName="h-8 w-auto px-1 text-xs">
35
34
  <InlineOptions api={api} document={data} key={request.preview.reviewedRevision} request={request} />
35
+ <a className="mt-3 block border-t px-2 pt-2 text-muted-foreground text-xs hover:text-foreground" href={item.href}>
36
+ Open the full question with its context →
37
+ </a>
36
38
  </Popover>
37
39
  ) : (
38
40
  <a aria-label={`Answer ${item.title}`} className="shrink-0 text-xs" href={item.href}>
@@ -43,6 +45,8 @@ export function TodayOptions({ api, item }: { readonly api: ShellApi; readonly i
43
45
 
44
46
  function InlineOptions({ api, document: data, request }: { readonly api: ShellApi; readonly document: Document; readonly request: DocumentRequest }) {
45
47
  const registry = useContext(RegistryContext);
48
+ const requestRef = useRef(request);
49
+ requestRef.current = request;
46
50
  const client = useFormClient();
47
51
  const [label] = useState(() => authorLabel(() => localStorage, document.documentElement.dataset.workspace));
48
52
  const [author, setAuthor] = useState(() => label.read("") || "maintainer");
@@ -50,8 +54,8 @@ function InlineOptions({ api, document: data, request }: { readonly api: ShellAp
50
54
  () =>
51
55
  client.runtime.fn(
52
56
  Effect.fn("Board.answerOption")(function* (choice: string) {
53
- const preview = request.preview;
54
- const prompt = request.prompts?.[0];
57
+ const preview = requestRef.current.preview;
58
+ const prompt = requestRef.current.prompts?.[0];
55
59
  if (!(preview && prompt && author.trim())) {
56
60
  return yield* Effect.fail("Add your local author label.");
57
61
  }
@@ -73,12 +77,14 @@ function InlineOptions({ api, document: data, request }: { readonly api: ShellAp
73
77
  return saved;
74
78
  }),
75
79
  ),
76
- [client, api, registry, request, author, data.file, label.save],
80
+ [client, api, registry, author, data.file, label.save],
77
81
  );
78
82
  const action = useAction(actionAtom);
79
83
  const failure = Option.getOrUndefined(Option.flatMap(action.cause, Cause.findErrorOption));
80
84
  const saved = Option.getOrUndefined(action.data);
81
- let status = saved ? `Saved: responses/${saved.id}.md. This records feedback, not acceptance.` : "";
85
+ let status = saved
86
+ ? `Saved: your answer “${chosenLabels(request.prompts ?? [], saved.answers)}” is recorded. The agent reads it the next time it checks.`
87
+ : "";
82
88
  if (failure) {
83
89
  status = typeof failure === "string" ? failure : saveFailure(failure, "response").text;
84
90
  }
@@ -86,33 +92,36 @@ function InlineOptions({ api, document: data, request }: { readonly api: ShellAp
86
92
  status = "Pending: registering reviewed context, then recording response…";
87
93
  }
88
94
  const prompt = request.prompts?.[0];
95
+ const reason = reasonMarkdown(request.preview?.reason ?? "").split("\n")[0];
89
96
  return (
90
- <fieldset aria-label={`Options for ${data.identity}`} className="m-0 min-w-0 space-y-2 border-0 px-2 pb-2">
91
- <div className="flex flex-wrap gap-2">
92
- {prompt?.options.map((option, index) => {
93
- const text = data.metadata.options?.[index]?.label ?? optionLabel(option.id, option.source, request.preview?.reason ?? "");
94
- return (
95
- <Button
96
- disabled={!(data.writable && author.trim()) || action.pending || Boolean(saved)}
97
- key={option.id}
98
- onClick={() => action.dispatch(option.id)}
99
- size="sm"
100
- variant="outline"
101
- >
102
- {text}
103
- {data.metadata.recommended === text && " · Recommended"}
104
- </Button>
105
- );
106
- })}
97
+ <fieldset aria-label={`Options for ${data.identity}`} className="m-0 min-w-0 space-y-3 border-0 px-2 pb-1">
98
+ {Boolean(reason) && <p className="m-0 font-medium text-sm">{reason}</p>}
99
+ {prompt ? <p className="m-0 text-muted-foreground text-xs">{promptHeading(prompt.source).title ?? "Choose one option."}</p> : null}
100
+ <div className="grid gap-2">
101
+ {prompt?.options.map((option, index) => (
102
+ <TodayOptionButton
103
+ disabled={!(data.writable && author.trim()) || action.pending || Boolean(saved)}
104
+ key={option.id}
105
+ onChoose={() => action.dispatch(option.id)}
106
+ option={option}
107
+ recommended={data.metadata.recommended}
108
+ source={data.metadata.options?.[index]}
109
+ />
110
+ ))}
107
111
  </div>
108
112
  <details className="text-muted-foreground text-xs">
109
113
  <summary className="cursor-pointer">Answer as {author || "…"}</summary>
110
- <label>
114
+ <label className="mt-2 flex items-center gap-2">
111
115
  Local author label{" "}
112
- <input className="rounded border px-2 py-1" maxLength={200} onChange={(event) => setAuthor(event.currentTarget.value)} value={author} />
116
+ <input
117
+ className="rounded border bg-background px-2 py-1"
118
+ maxLength={200}
119
+ onChange={(event) => setAuthor(event.currentTarget.value)}
120
+ value={author}
121
+ />
113
122
  </label>
114
123
  </details>
115
- {!data.writable && <p className="m-0 text-muted-foreground text-xs">Read-only. Enable responses to record an answer.</p>}
124
+ {!data.writable && <p className="m-0 text-muted-foreground text-xs">This Board is read-only. Start it with --responses to record answers.</p>}
116
125
  {Boolean(status) && (
117
126
  <p className="m-0 text-xs" role="status">
118
127
  {status}