@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
@@ -10,15 +10,16 @@ export function cardContext(source: string) {
10
10
  return { links, recommendation };
11
11
  }
12
12
 
13
- export function optionLabel(id: string, source: string, reason: string) {
14
- const heading = /^#{1,6}\s+(?<title>.+)$/mu.exec(source)?.groups?.title;
15
- if (heading) {
16
- return heading;
13
+ export function optionText(id: string, source: string) {
14
+ const heading = promptHeading(source);
15
+ if (heading.title) {
16
+ return { description: heading.rest, label: heading.title };
17
17
  }
18
- if (id === "retry" && /reviewed pr is ready to merge/iu.test(reason)) {
19
- return "Request changes (retry)";
18
+ const lead = /^\*\*(?<label>[^*]+)\*\*\s*(?:[—–:-]\s*)?/u.exec(source.trim());
19
+ if (lead?.groups?.label) {
20
+ return { description: source.trim().slice(lead[0].length).trim(), label: lead.groups.label.trim() };
20
21
  }
21
- return id.charAt(0).toUpperCase() + id.slice(1).replaceAll("-", " ");
22
+ return { description: source.trim(), label: id.charAt(0).toUpperCase() + id.slice(1).replaceAll("-", " ") };
22
23
  }
23
24
 
24
25
  function section(source: string, title: string) {
@@ -48,3 +49,8 @@ function section(source: string, title: string) {
48
49
  function nextFence(current: string | undefined, marker: string) {
49
50
  return current?.startsWith(marker.charAt(0)) && marker.length >= current.length ? undefined : marker;
50
51
  }
52
+
53
+ export function promptHeading(source: string) {
54
+ const heading = /^#{1,6}\s+(?<title>.+)$/mu.exec(source);
55
+ return { rest: heading ? source.replace(heading[0], "").trim() : source.trim(), title: heading?.groups?.title?.trim() };
56
+ }
@@ -51,11 +51,51 @@ function authoredLane(status: string | undefined, approved: boolean): NowLane |
51
51
  return Object.entries(stages).find(([stage]) => stage === status)?.[1];
52
52
  }
53
53
 
54
+ const fleetHolders: Record<FleetWork["stage"], string> = {
55
+ adopting: "Fleet is working on this",
56
+ completed: "Fleet finished this",
57
+ delivering: "Fleet is working on this",
58
+ executing: "Fleet is working on this",
59
+ "needs-human": "Waiting on a person",
60
+ prepared: "Queued for Fleet",
61
+ released: "Fleet handed this back",
62
+ repairing: "Fleet is working on this",
63
+ reviewing: "Fleet is working on this",
64
+ validating: "Fleet is working on this",
65
+ };
66
+
67
+ export function fleetHolder(fleet: FleetWork): string {
68
+ if (fleet.attentionOwner === "maintainer") {
69
+ return "Needs you";
70
+ }
71
+ return fleet.attentionOwner === "orchestrator" ? "Waiting on the orchestrator" : fleetHolders[fleet.stage];
72
+ }
73
+
54
74
  export function holderOf(metadata: Metadata, fleet: FleetWork | undefined): string | undefined {
55
75
  if (fleet) {
56
- return fleet.attentionOwner && fleet.attentionOwner !== "automation" ? fleet.attentionOwner : "Fleet";
76
+ return fleetHolder(fleet);
57
77
  }
58
- return ["maintainer", "orchestrator"].includes(metadata.owner ?? "") ? metadata.owner : undefined;
78
+ if (metadata.owner === "maintainer") {
79
+ return "Assigned to you";
80
+ }
81
+ return metadata.owner === "orchestrator" ? "Assigned to the orchestrator" : undefined;
82
+ }
83
+
84
+ const stageNames: Record<FleetWork["stage"], string> = {
85
+ adopting: "Adopting",
86
+ completed: "Done",
87
+ delivering: "Delivering",
88
+ executing: "Executing",
89
+ "needs-human": "Waiting",
90
+ prepared: "Queued",
91
+ released: "Handed back",
92
+ repairing: "Repairing",
93
+ reviewing: "Reviewing",
94
+ validating: "Validating",
95
+ };
96
+
97
+ export function stageTime(stage: FleetWork["stage"]): { readonly ended: boolean; readonly label: string } {
98
+ return { ended: stage === "completed" || stage === "released", label: stageNames[stage] };
59
99
  }
60
100
 
61
101
  export function cardStatus(metadata: Metadata, fleet: FleetWork | undefined, waiting: string | undefined): string | undefined {
@@ -40,6 +40,7 @@ export const InboxProjection = Schema.Struct({
40
40
  complete: Schema.Boolean,
41
41
  issues: Schema.Array(Schema.Struct({ file: Schema.String, line: Schema.Number, message: Schema.String })),
42
42
  items: Schema.Array(InboxItem),
43
+ later: Schema.Array(InboxItem),
43
44
  parked: Schema.Array(InboxItem),
44
45
  resolved: Schema.Array(InboxItem),
45
46
  sourceIssues: Schema.Boolean,
@@ -3,7 +3,7 @@ import { type Components, MarkdownHooks } from "react-markdown";
3
3
  import rehypeRaw from "rehype-raw";
4
4
  import type { PluggableList } from "unified";
5
5
  import type { Document } from "#browser/document/schema.ts";
6
- import { Age } from "#client/react/Age";
6
+ import { Age } from "#client/react/Age.tsx";
7
7
  import { documentHeadings } from "#render/documentHeadings.ts";
8
8
  import { documentLinks } from "#render/documentLinks.ts";
9
9
  import { documentPlugins } from "#render/documentPlugins.ts";
@@ -1,5 +1,5 @@
1
1
  import { Option } from "effect";
2
- import type { SaveState } from "@shivaedev/effect-react/save-state.ts";
2
+ import type { SaveState } from "@shivaedev/effect-react/client/saveState.ts";
3
3
  import { type SaveFailure, saveFailure } from "./saveFailure.ts";
4
4
 
5
5
  export type DraftInteraction = "draft" | "cleared" | "submitted";
@@ -30,6 +30,6 @@ export function formStatus<A>(
30
30
  };
31
31
  }
32
32
 
33
- export function responseSaved(id: string, dirty: boolean) {
34
- return `Saved: responses/${id}.md. This records feedback, not acceptance.${dirty ? " Your current draft remains unsaved." : ""}`;
33
+ export function responseSaved(id: string, dirty: boolean, choice = "") {
34
+ return `Saved: your answer${choice ? ` “${choice}”` : ""} is recorded. The agent reads it from responses/${id}.md the next time it checks. This records your answer, not acceptance of the work.${dirty ? " Your current draft remains unsaved." : ""}`;
35
35
  }
@@ -1,25 +1,30 @@
1
+ import { ExternalLink } from "lucide-react";
1
2
  import { cardContext } from "#browser/responses/cardContext.ts";
2
3
  import type { QuestionPreview } from "#browser/responses/schema.ts";
3
- import { ResponseMarkdown } from "./ResponseMarkdown.tsx";
4
+ import { buttonVariants } from "#client/react/components/ui/buttonVariants.ts";
5
+ import { cn } from "#client/react/lib/cn.ts";
6
+ import { AnswerMarkdown } from "./AnswerMarkdown.tsx";
4
7
 
5
8
  export function AnswerContext({ preview }: { readonly preview: QuestionPreview }) {
6
9
  const context = cardContext(preview.context);
7
10
  return (
8
11
  <>
9
12
  {Boolean(context.recommendation) && (
10
- <div className="answer-recommendation">
11
- <ResponseMarkdown file={preview.source} source={context.recommendation} />
13
+ <div className="rounded-lg bg-muted/60 px-4 py-3 text-sm">
14
+ <p className="mb-1 font-medium text-muted-foreground text-xs uppercase tracking-wide">Recommendation</p>
15
+ <AnswerMarkdown className="answer-recommendation" file={preview.source} source={context.recommendation} />
12
16
  </div>
13
17
  )}
14
18
  {context.links.length > 0 && (
15
19
  <div className="answer-links flex flex-wrap gap-2">
16
20
  {context.links.map((link) => (
17
21
  <a
18
- className={`answer-link ${link.primary ? "bg-primary text-primary-foreground" : "border border-border"} inline-flex items-center rounded-md px-4 py-2 font-medium`}
22
+ className={cn(buttonVariants({ size: "sm", variant: link.primary ? "default" : "outline" }), "answer-link")}
19
23
  href={link.url}
20
24
  key={link.url}
21
25
  >
22
26
  {link.label}
27
+ <ExternalLink aria-hidden="true" />
23
28
  </a>
24
29
  ))}
25
30
  </div>
@@ -1,5 +1,8 @@
1
+ import { CheckCircle2 } from "lucide-react";
1
2
  import type { Document, DocumentRequest } from "#browser/document/schema.ts";
2
3
  import type { QuestionPreview } from "#browser/responses/schema.ts";
4
+ import { Button } from "#client/react/components/ui/Button.tsx";
5
+ import { cn } from "#client/react/lib/cn.ts";
3
6
  import { AnswerMore } from "./AnswerMore.tsx";
4
7
  import { AnswerPrompts } from "./AnswerPrompts.tsx";
5
8
  import { FormField } from "./FormField.tsx";
@@ -7,6 +10,8 @@ import { shellIds } from "./shellIds.ts";
7
10
  import { useFormFocus } from "./useFormFocus.ts";
8
11
  import { useResponseForm } from "./useResponseForm.ts";
9
12
 
13
+ const READ_ONLY = "This Board is read-only. Start it with --responses to record answers.";
14
+
10
15
  interface AnswerProps {
11
16
  readonly document: Document;
12
17
  readonly preview: QuestionPreview;
@@ -38,6 +43,7 @@ function AnswerSession({ document: data, request, preview }: AnswerProps) {
38
43
  return (
39
44
  <form
40
45
  {...focus}
46
+ className="space-y-4"
41
47
  data-item={preview.item}
42
48
  data-question={preview.id}
43
49
  data-request={preview.request}
@@ -76,13 +82,26 @@ function AnswerSession({ document: data, request, preview }: AnswerProps) {
76
82
  showErrors={state.attempted}
77
83
  />
78
84
  {prompts.length === 0 && body}
79
- <button disabled={!data.writable || state.created.saving} id={id(shellIds.responseSubmit)} type="submit">
80
- Record
81
- </button>
82
- <AnswerMore body={prompts.length > 0 ? body : undefined} id={id} request={request} state={state} />
83
- <p data-outcome={state.outcome} id={id(shellIds.responseStatus)} role="status">
84
- {data.writable ? writableStatus : `${state.status} Read-only. Restart with --responses to enable answers.`.trim()}
85
+ <div className="flex flex-wrap items-center gap-3">
86
+ <Button disabled={!data.writable || state.created.saving} id={id(shellIds.responseSubmit)} type="submit">
87
+ Record answer
88
+ </Button>
89
+ </div>
90
+ <p
91
+ className={cn(
92
+ "flex items-start gap-2 text-sm empty:hidden",
93
+ !data.writable && "text-muted-foreground",
94
+ state.outcome === "saved" && "rounded-md border border-done/40 bg-done/10 px-3 py-2",
95
+ (state.outcome === "rejected" || state.outcome === "uncertain") && "rounded-md border border-destructive/40 px-3 py-2 text-destructive",
96
+ )}
97
+ data-outcome={state.outcome}
98
+ id={id(shellIds.responseStatus)}
99
+ role="status"
100
+ >
101
+ {state.outcome === "saved" && <CheckCircle2 aria-hidden="true" className="mt-0.5 size-4 shrink-0 text-done" />}
102
+ {data.writable ? writableStatus : `${state.status} ${READ_ONLY}`.trim()}
85
103
  </p>
104
+ <AnswerMore body={prompts.length > 0 ? body : undefined} id={id} request={request} state={state} />
86
105
  </form>
87
106
  );
88
107
  }
@@ -0,0 +1,15 @@
1
+ import { cn } from "#client/react/lib/cn.ts";
2
+ import { ResponseMarkdown } from "./ResponseMarkdown.tsx";
3
+
4
+ export function AnswerMarkdown({ className, file, source }: { readonly className?: string; readonly file: string; readonly source: string }) {
5
+ return (
6
+ <div
7
+ className={cn(
8
+ "[&_a]:underline [&_a]:underline-offset-4 [&_code]:font-mono [&_code]:text-[0.9em] [&_li]:my-1 [&_ol]:list-decimal [&_ol]:pl-5 [&_p+p]:mt-2 [&_ul]:list-disc [&_ul]:pl-5",
9
+ className,
10
+ )}
11
+ >
12
+ <ResponseMarkdown file={file} source={source} />
13
+ </div>
14
+ );
15
+ }
@@ -1,5 +1,6 @@
1
1
  import type { ReactNode } from "react";
2
2
  import type { DocumentRequest } from "#browser/document/schema.ts";
3
+ import { Button } from "#client/react/components/ui/Button.tsx";
3
4
  import { FormField } from "./FormField.tsx";
4
5
  import { shellIds } from "./shellIds.ts";
5
6
  import type { useResponseForm } from "./useResponseForm.ts";
@@ -16,52 +17,63 @@ export function AnswerMore({
16
17
  readonly state: ReturnType<typeof useResponseForm>;
17
18
  }) {
18
19
  return (
19
- <details data-key="response-details">
20
- <summary>More</summary>
21
- <FormField
22
- autoComplete="nickname"
23
- form={state.form}
24
- label="Local author label"
25
- maxLength={200}
26
- name="author"
27
- required
28
- showErrors={state.attempted}
29
- />
30
- <FormField form={state.form} label="Response kind" name="type">
31
- <option value="answer">Answer (including no)</option>
32
- <option value="clarify">Ask back / clarify</option>
33
- <option value="not_now">Not now</option>
34
- </FormField>
35
- {body}
36
- <FormField form={state.form} label="Supersedes an earlier response (optional)" name="supersedes">
37
- <option value="">Adds feedback; does not supersede</option>
38
- {request.responses?.map((record) => (
39
- <option key={record.id} value={record.id}>
40
- {record.id} · {record.response.author} · {record.response.reviewedRevision.slice(0, 12)}
41
- </option>
42
- ))}
43
- </FormField>
44
- <pre hidden={!state.recovery} id={id(shellIds.responseDraftRecovery)}>
45
- {state.recovery}
46
- </pre>
47
- <p>
48
- Drafts stay in this browser for seven days after the last edit, with a 256 KiB limit per draft. Saved responses are Markdown files in
49
- responses/.
50
- </p>
51
- <button
52
- disabled={typeof window === "undefined" || state.created.saving}
53
- id={id(shellIds.responsePreview)}
54
- onClick={state.showPreview}
55
- type="button"
56
- >
57
- Preview Markdown
58
- </button>
59
- <pre hidden={!state.preview} id={id(shellIds.responsePreviewContent)}>
60
- {state.preview}
61
- </pre>
62
- <button disabled={typeof window === "undefined" || state.created.saving} id={id(shellIds.responseClear)} onClick={state.clear} type="button">
63
- Clear draft
64
- </button>
20
+ <details className="group rounded-lg border px-4 py-2 text-sm [&[open]]:pb-4" data-key="response-details">
21
+ <summary className="cursor-pointer select-none py-1 text-muted-foreground hover:text-foreground">More options</summary>
22
+ <div className="mt-3 grid gap-4">
23
+ <FormField
24
+ autoComplete="nickname"
25
+ form={state.form}
26
+ label="Local author label"
27
+ maxLength={200}
28
+ name="author"
29
+ required
30
+ showErrors={state.attempted}
31
+ />
32
+ <FormField form={state.form} label="Response kind" name="type">
33
+ <option value="answer">Answer (including no)</option>
34
+ <option value="clarify">Ask back / clarify</option>
35
+ <option value="not_now">Not now</option>
36
+ </FormField>
37
+ {body}
38
+ <FormField form={state.form} label="Supersedes an earlier response (optional)" name="supersedes">
39
+ <option value="">Adds feedback; does not supersede</option>
40
+ {request.responses?.map((record) => (
41
+ <option key={record.id} value={record.id}>
42
+ {record.id} · {record.response.author} · {record.response.reviewedRevision.slice(0, 12)}
43
+ </option>
44
+ ))}
45
+ </FormField>
46
+ <pre className="whitespace-pre-wrap rounded-md bg-muted p-3 text-xs" hidden={!state.recovery} id={id(shellIds.responseDraftRecovery)}>
47
+ {state.recovery}
48
+ </pre>
49
+ <p className="text-muted-foreground text-xs">
50
+ Drafts stay in this browser for seven days after the last edit, with a 256 KiB limit per draft. Saved responses are Markdown files in
51
+ responses/.
52
+ </p>
53
+ <div className="flex flex-wrap gap-2">
54
+ <Button
55
+ disabled={typeof window === "undefined" || state.created.saving}
56
+ id={id(shellIds.responsePreview)}
57
+ onClick={state.showPreview}
58
+ size="sm"
59
+ variant="outline"
60
+ >
61
+ Preview Markdown
62
+ </Button>
63
+ <Button
64
+ disabled={typeof window === "undefined" || state.created.saving}
65
+ id={id(shellIds.responseClear)}
66
+ onClick={state.clear}
67
+ size="sm"
68
+ variant="ghost"
69
+ >
70
+ Clear draft
71
+ </Button>
72
+ </div>
73
+ <pre className="whitespace-pre-wrap rounded-md bg-muted p-3 text-xs" hidden={!state.preview} id={id(shellIds.responsePreviewContent)}>
74
+ {state.preview}
75
+ </pre>
76
+ </div>
65
77
  </details>
66
78
  );
67
79
  }
@@ -1,9 +1,10 @@
1
+ import { MessageCircleQuestion } from "lucide-react";
1
2
  import type { Document, DocumentRequest } from "#browser/document/schema.ts";
2
3
  import { reasonMarkdown } from "#browser/responses/reasonMarkdown.ts";
3
4
  import { AnswerContext } from "./AnswerContext.tsx";
4
5
  import { AnswerForm } from "./AnswerForm.tsx";
6
+ import { AnswerMarkdown } from "./AnswerMarkdown.tsx";
5
7
  import { ResponseHistory } from "./ResponseHistory.tsx";
6
- import { ResponseMarkdown } from "./ResponseMarkdown.tsx";
7
8
 
8
9
  export function AnswerPanel({ document: data }: { readonly document: Document }) {
9
10
  const requests = data.requests.filter(
@@ -30,23 +31,28 @@ function AnswerCard({ document: data, request }: { readonly document: Document;
30
31
  return (
31
32
  <section
32
33
  aria-labelledby={`answer-title-${request.id}`}
33
- className="response-panel rounded-lg border border-border bg-card p-4"
34
+ className="response-panel max-w-[72ch] space-y-5 rounded-xl border bg-card p-5 text-card-foreground shadow-sm"
34
35
  id={`answer-card-${request.id}`}
35
36
  tabIndex={-1}
36
37
  >
37
- <h2 id={`answer-title-${request.id}`}>{cardTitle(request, closed)}</h2>
38
+ <h2 className="flex items-center gap-2 font-medium text-muted-foreground text-sm" id={`answer-title-${request.id}`}>
39
+ {request.open ? <MessageCircleQuestion aria-hidden="true" className="size-4 text-needs-you" /> : null}
40
+ {cardTitle(request, closed)}
41
+ </h2>
38
42
  {!(request.open || unavailable) && (
39
- <p role="status">
43
+ <p className="text-muted-foreground text-sm" role="status">
40
44
  {closed ? "This question is closed. Recorded answers remain below." : "Answer unavailable. Check the source diagnostics."}
41
45
  </p>
42
46
  )}
43
- {Boolean(unavailable) && <p role="status">{unavailable}</p>}
47
+ {Boolean(unavailable) && (
48
+ <p className="rounded-md border border-destructive/40 px-3 py-2 text-sm" role="status">
49
+ {unavailable}
50
+ </p>
51
+ )}
44
52
  {request.open && preview !== undefined && (
45
53
  <>
54
+ <AnswerMarkdown className="answer-reason font-medium text-base" file={source.file} source={reasonMarkdown(preview.reason)} />
46
55
  <AnswerContext preview={preview} />
47
- <div className="answer-reason">
48
- <ResponseMarkdown file={source.file} source={reasonMarkdown(preview.reason)} />
49
- </div>
50
56
  </>
51
57
  )}
52
58
  {request.open && preview && !unavailable && <AnswerForm document={source} preview={preview} request={request} />}
@@ -1,10 +1,11 @@
1
1
  import { useAtomValue } from "@effect/atom-react";
2
2
  import { useId, useLayoutEffect, useRef } from "react";
3
3
  import type { Document } from "#browser/document/schema.ts";
4
+ import { promptHeading } from "#browser/responses/cardContext.ts";
4
5
  import type { Prompt } from "#browser/responses/Prompt.ts";
5
6
  import type { PromptAnswer } from "#browser/responses/schema.ts";
7
+ import { AnswerMarkdown } from "./AnswerMarkdown.tsx";
6
8
  import { PromptOption } from "./PromptOption.tsx";
7
- import { ResponseMarkdown } from "./ResponseMarkdown.tsx";
8
9
  import type { useResponseForm } from "./useResponseForm.ts";
9
10
 
10
11
  type Model = ReturnType<typeof useResponseForm>["form"];
@@ -36,12 +37,9 @@ function PromptField({
36
37
  useLayoutEffect(() => ref.current?.setCustomValidity(error ?? ""), [error]);
37
38
  const choices = document.metadata.options;
38
39
 
39
- let guidance = "Answer in your own words.";
40
- if (choices) {
41
- guidance = "Choose an option or write another answer.";
42
- } else if (prompt.mode !== "text") {
43
- guidance = `${prompt.mode === "one" ? "Choose one option" : "Choose any combination"}, or answer in your own words.`;
44
- }
40
+ const guidance = promptGuidance(prompt.mode, choices !== undefined);
41
+ const heading = promptHeading(prompt.source);
42
+ const title = heading.title ?? `Question ${index + 1}`;
45
43
  function change(next: PromptAnswer) {
46
44
  onEdit(index, next);
47
45
  }
@@ -57,14 +55,14 @@ function PromptField({
57
55
  }
58
56
  let message = error;
59
57
  if (error && answer.selected.length === 0 && !answer.text.trim()) {
60
- message = `Unanswered: Question ${index + 1} · ${prompt.id}. Choose an option or answer in your own words.`;
58
+ message = `Unanswered: “${title}”. Choose an option or answer in your own words.`;
61
59
  }
62
60
  return (
63
- <fieldset data-mode={prompt.mode} data-prompt={prompt.id} data-source={prompt.source}>
64
- <legend>{choices ? "Your answer" : `Question ${index + 1} · ${prompt.id}`}</legend>
65
- {choices === undefined && <ResponseMarkdown file={document.file} source={prompt.source} />}
66
- <p>{guidance} Nothing is selected for you.</p>
67
- <div className={choices ? "question-choices" : undefined}>
61
+ <fieldset className="m-0 min-w-0 space-y-3 border-0 p-0" data-mode={prompt.mode} data-prompt={prompt.id} data-source={prompt.source}>
62
+ <legend className="mb-1 p-0 font-semibold text-base">{choices === undefined ? title : "Your answer"}</legend>
63
+ {choices === undefined && heading.rest ? <AnswerMarkdown className="text-sm" file={document.file} source={heading.rest} /> : null}
64
+ <p className="text-muted-foreground text-sm">{guidance}</p>
65
+ <div className={`grid gap-2 ${choices ? "question-choices" : ""}`}>
68
66
  {prompt.options.map((option, choice) => (
69
67
  <PromptOption
70
68
  checked={answer.selected.includes(option.id)}
@@ -78,26 +76,43 @@ function PromptField({
78
76
  ))}
79
77
  </div>
80
78
  {prompt.mode !== "text" && (
81
- <button data-clear-choices onClick={() => change({ ...answer, selected: [] })} type="button">
79
+ <button
80
+ className="text-muted-foreground text-xs underline-offset-4 hover:underline disabled:opacity-50"
81
+ data-clear-choices
82
+ disabled={answer.selected.length === 0}
83
+ onClick={() => change({ ...answer, selected: [] })}
84
+ type="button"
85
+ >
82
86
  Clear selections
83
87
  </button>
84
88
  )}
85
- <label>
86
- {choices ? "Another answer / additional text" : "Additional text / another answer"}
89
+ <label className="grid gap-2 text-sm">
90
+ <span>{prompt.mode === "text" ? "Your answer" : "Add a note or answer in your own words (optional)"}</span>
87
91
  <textarea
88
92
  aria-describedby={`${errorId}-error`}
89
93
  aria-invalid={Boolean(error)}
94
+ className="w-full rounded-md border bg-background px-3 py-2 text-sm aria-invalid:border-destructive"
90
95
  data-answer-text
91
96
  maxLength={8192}
92
97
  onInput={(event) => change({ ...answer, text: event.currentTarget.value })}
93
98
  ref={ref}
94
- rows={choices ? 4 : 3}
99
+ rows={choices || prompt.mode === "text" ? 4 : 2}
95
100
  value={answer.text}
96
101
  />
97
- <span className="response-field-error" hidden={!error} id={`${errorId}-error`}>
102
+ <span className="response-field-error text-destructive text-xs" hidden={!error} id={`${errorId}-error`}>
98
103
  {message}
99
104
  </span>
100
105
  </label>
101
106
  </fieldset>
102
107
  );
103
108
  }
109
+
110
+ function promptGuidance(mode: Prompt["mode"], authored: boolean) {
111
+ if (authored) {
112
+ return "Choose an option or write another answer. Nothing is selected for you.";
113
+ }
114
+ if (mode === "text") {
115
+ return "Answer in your own words.";
116
+ }
117
+ return `${mode === "one" ? "Choose one option" : "Choose any combination"}, or answer in your own words. Nothing is selected for you.`;
118
+ }
@@ -2,9 +2,8 @@ import { useRouterState } from "@tanstack/react-router";
2
2
  import { Option } from "effect";
3
3
  import type * as AtomRegistry from "effect/reactivity/AtomRegistry";
4
4
  import { useCallback, useContext, useEffect, useState } 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
- import { navigationQuery } from "#client/page/routeQuery.ts";
8
7
  import { AgeTicker } from "./Age.tsx";
9
8
  import { FleetMain } from "./FleetMain.tsx";
10
9
  import { FormContext } from "./FormContext.ts";
@@ -15,6 +14,7 @@ import { emptyMainLocation, type MainLocation } from "./mainLocation.ts";
15
14
  import { PhoneNavigation } from "./PhoneNavigation.tsx";
16
15
  import { ReadingLibrary } from "./ReadingLibrary.tsx";
17
16
  import { ReadingReadyContext, readingReady } from "./ReadingReadyContext.ts";
17
+ import { navigationQuery } from "./routeQuery.ts";
18
18
  import { SearchDialog } from "./SearchDialog.tsx";
19
19
  import { SessionContext } from "./SessionContext.ts";
20
20
  import type { ShellApi } from "./ShellApi.ts";
@@ -1,12 +1,12 @@
1
1
  import { Cause, Option } from "effect";
2
2
  import { useEffect, 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 type { Document } from "#browser/document/schema.ts";
5
- import { documentQuery } from "#client/page/routeQuery.ts";
6
5
  import { DocumentPage } from "./DocumentPage.tsx";
7
6
  import type { MainLocation } from "./mainLocation.ts";
8
7
  import { ProjectionMain } from "./ProjectionMain.tsx";
9
8
  import { RemainingMain } from "./RemainingMain.tsx";
9
+ import { documentQuery } from "./routeQuery.ts";
10
10
  import type { ShellApi } from "./ShellApi.ts";
11
11
  import { shellIds } from "./shellIds.ts";
12
12
  import { useDocumentPage } from "./useDocumentPage.ts";
@@ -1,6 +1,6 @@
1
1
  import { Option } from "effect";
2
2
  import type { ComponentProps } 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 { BoardMain } from "./BoardMain.tsx";
5
5
  import { FleetItemsContext } from "./FleetItemsContext.ts";
6
6
  export function FleetMain(props: ComponentProps<typeof BoardMain>) {
@@ -1,5 +1,5 @@
1
1
  import { type ReactNode, useId, useLayoutEffect, useRef } from "react";
2
- import { useField } from "@shivaedev/effect-form/react.ts";
2
+ import { useField } from "@shivaedev/effect-form/client/react.ts";
3
3
  import type { Fields, Form, Name } from "@shivaedev/effect-form/shape.ts";
4
4
 
5
5
  type Control = HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement;
@@ -1,10 +1,10 @@
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 { Document } from "#browser/document/schema.ts";
4
- import { routeQueryOptions } from "#client/page/routeQuery.ts";
5
4
  import { GoalCriteria } from "./GoalCriteria.tsx";
6
5
  import { GoalWorkTable } from "./GoalWorkTable.tsx";
7
6
  import { targetsGoal } from "./goalWork.ts";
7
+ import { routeQueryOptions } from "./routeQuery.ts";
8
8
  import type { ShellApi } from "./ShellApi.ts";
9
9
 
10
10
  export function GoalPage({ api, document }: { readonly api: ShellApi; readonly document: Document }) {
@@ -1,9 +1,9 @@
1
1
  import { Option } from "effect";
2
2
  import { useId } 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 type { Document } from "#browser/document/schema.ts";
5
- import { routeQueryOptions } from "#client/page/routeQuery.ts";
6
5
  import { GoalProgressSummary } from "./GoalProgressSummary.tsx";
6
+ import { routeQueryOptions } from "./routeQuery.ts";
7
7
  import type { ShellApi } from "./ShellApi.ts";
8
8
 
9
9
  export function GoalProgressHeader({ api, document }: { readonly api: ShellApi; readonly document: Document }) {
@@ -3,6 +3,7 @@ import type { ReactNode } from "react";
3
3
  import { Badge } from "#client/react/components/ui/Badge.tsx";
4
4
  import { Button } from "#client/react/components/ui/Button.tsx";
5
5
  import { buttonVariants } from "#client/react/components/ui/buttonVariants.ts";
6
+ import { destinationName } from "#client/react/destinationName.ts";
6
7
  import { cn } from "#client/react/lib/cn.ts";
7
8
  import { KeyboardHints } from "./KeyboardHints.tsx";
8
9
  import type { MainLocation } from "./mainLocation.ts";
@@ -12,7 +13,7 @@ import type { AppearanceControls } from "./useAppearance.ts";
12
13
  import type { SidebarControls } from "./useSidebar.ts";
13
14
  export function InboxCount({ count, id }: { readonly count: number | null; readonly id: string }) {
14
15
  return (
15
- <Badge aria-label={`${count ?? "?"} waiting`} id={id} role="status">
16
+ <Badge aria-label={count === null ? "Needs you unavailable" : `${count} need you`} id={id} role="status">
16
17
  {count ?? "?"}
17
18
  </Badge>
18
19
  );
@@ -57,7 +58,7 @@ export function Header({
57
58
  <Breadcrumbs file={main.title ?? main.file} />
58
59
  <a
59
60
  aria-current={main.view === "today" ? "page" : undefined}
60
- aria-label="Today"
61
+ aria-label={destinationName("Today", count, "need you")}
61
62
  className={cn(buttonVariants({ size: "sm", variant: "ghost" }), "hidden md:inline-flex")}
62
63
  href="/"
63
64
  id={shellIds.inboxTopbar}
@@ -1,6 +1,6 @@
1
1
  import { Option } from "effect";
2
2
  import { 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 type { Library, LibraryFile } from "#browser/navigation/library.ts";
5
5
  import { Age } from "./Age.tsx";
6
6
  import type { ShellApi } from "./ShellApi.ts";
@@ -1,7 +1,7 @@
1
- import { routeQueryOptions } from "#client/page/routeQuery.ts";
2
1
  import { LibraryPage } from "./LibraryPage.tsx";
3
2
  import type { MainLocation } from "./mainLocation.ts";
4
3
  import { ProjectionFrame } from "./ProjectionFrame.tsx";
4
+ import { routeQueryOptions } from "./routeQuery.ts";
5
5
  import type { ShellApi } from "./ShellApi.ts";
6
6
  export function LibraryMain({
7
7
  api,