@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,15 +1,15 @@
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 { InboxProjection } from "#browser/views/schema.ts";
4
- import { navigationQuery, routeQueryOptions } from "#client/page/routeQuery.ts";
5
4
  import { fileUrl } from "#files/url.ts";
6
5
  import { inProgressItems } from "./inProgressItems.ts";
6
+ import { navigationQuery, routeQueryOptions } from "./routeQuery.ts";
7
7
  import type { ShellApi } from "./ShellApi.ts";
8
8
  import { TodayGoals } from "./TodayGoals.tsx";
9
- import { TodayNeeds } from "./TodayNeeds.tsx";
9
+ import { TodayLater, TodayNeeds } from "./TodayNeeds.tsx";
10
10
  import { TodayProgress } from "./TodayProgress.tsx";
11
11
 
12
- const todayIds = { goals: "today-goals", needs: "needs-you", progress: "in-progress" };
12
+ const todayIds = { goals: "today-goals", later: "later", needs: "needs-you", progress: "in-progress" };
13
13
 
14
14
  export function TodayPage({ api, data }: { readonly api: ShellApi; readonly data: InboxProjection }) {
15
15
  const query = useQuery(api.goals.query(undefined, routeQueryOptions), { keepPreviousData: true });
@@ -30,11 +30,11 @@ export function TodayPage({ api, data }: { readonly api: ShellApi; readonly data
30
30
  )}
31
31
  </header>
32
32
  <nav aria-label="Today summary" className="today-strip flex items-center gap-1 whitespace-nowrap text-xs tabular-nums sm:gap-2 sm:text-sm">
33
- <a href={`#${todayIds.needs}`}>{data.complete ? data.you.length : "?"} need you</a>
33
+ <a href={`#${todayIds.needs}`}>{data.complete ? `${data.you.length} need you` : "Needs you unavailable"}</a>
34
34
  <span aria-hidden="true">·</span>
35
- <a href={`#${todayIds.progress}`}>{progress?.length ?? "?"} in progress</a>
35
+ <a href={`#${todayIds.progress}`}>{progressSummary(progress?.length, fleet?.state === "unavailable" || Option.isSome(fleetQuery.cause))}</a>
36
36
  <span aria-hidden="true">·</span>
37
- <a href={`#${todayIds.goals}`}>{goals?.goals.length ?? "?"} active goals</a>
37
+ <a href={`#${todayIds.goals}`}>{goalsSummary(goals?.goals.length, Option.isSome(query.cause))}</a>
38
38
  </nav>
39
39
  <div className="today-grid grid min-w-0 gap-6 xl:grid-cols-[minmax(0,2fr)_minmax(0,1fr)]">
40
40
  <div className="min-w-0 space-y-6">
@@ -43,6 +43,7 @@ export function TodayPage({ api, data }: { readonly api: ShellApi; readonly data
43
43
  <h2 className="mt-0 mb-3 font-semibold text-lg">In progress</h2>
44
44
  <TodayProgress data={fleet} failed={Option.isSome(fleetQuery.cause)} />
45
45
  </section>
46
+ {data.complete && data.later.length > 0 && <Later items={data.later} />}
46
47
  </div>
47
48
  <section className="min-w-0" id={todayIds.goals}>
48
49
  <h2 className="mt-0 mb-3 font-semibold text-lg">Goals</h2>
@@ -67,3 +68,31 @@ function NeedsYou({ api, data }: { readonly api: ShellApi; readonly data: InboxP
67
68
  </section>
68
69
  );
69
70
  }
71
+
72
+ function Later({ items }: { readonly items: InboxProjection["later"] }) {
73
+ return (
74
+ <section className="min-w-0" id={todayIds.later}>
75
+ <details>
76
+ <summary className="mb-3 cursor-pointer">
77
+ <h2 className="m-0 inline font-semibold text-lg">Later</h2>
78
+ <span className="ml-2 text-muted-foreground text-sm tabular-nums">{items.length}</span>
79
+ </summary>
80
+ <TodayLater items={items} />
81
+ </details>
82
+ </section>
83
+ );
84
+ }
85
+
86
+ function progressSummary(count: number | undefined, unavailable: boolean) {
87
+ if (count !== undefined) {
88
+ return `${count} in progress`;
89
+ }
90
+ return unavailable ? "Fleet unavailable" : "Loading Fleet…";
91
+ }
92
+
93
+ function goalsSummary(count: number | undefined, failed: boolean) {
94
+ if (count !== undefined) {
95
+ return `${count} active goals`;
96
+ }
97
+ return failed ? "Goals unavailable" : "Loading goals…";
98
+ }
@@ -1,4 +1,4 @@
1
- import { cardStatus, type FleetWork, holderOf } from "#browser/views/now.ts";
1
+ import { cardStatus, type FleetWork, holderOf, stageTime } from "#browser/views/now.ts";
2
2
  import type { WorkProjection } from "#browser/views/schema.ts";
3
3
  import type { State } from "#browser/views/state.ts";
4
4
  import { Card } from "#client/react/components/ui/Card.tsx";
@@ -17,9 +17,10 @@ export function WorkCard({
17
17
  readonly fleet: FleetWork | undefined;
18
18
  }) {
19
19
  const holder = holderOf(item.metadata, fleet);
20
+ const kind = item.metadata.kind === "task" ? undefined : item.metadata.kind;
20
21
  return (
21
22
  <Card className="work-card" data-item={item.id} data-selected={item.id === state.item ? "true" : undefined}>
22
- <p className="work-kind">{item.metadata.kind ?? ""}</p>
23
+ {kind !== undefined && <p className="work-kind">{kind}</p>}
23
24
  <h3>
24
25
  <WorkItemTitle item={item} state={state} />
25
26
  </h3>
@@ -27,12 +28,19 @@ export function WorkCard({
27
28
  <StatusChip status={cardStatus(item.metadata, fleet, item.waiting)} title={item.waiting} />
28
29
  <PrChip url={fleet?.prUrl} />
29
30
  </div>
30
- {holder && <p className="work-owner">Holder: {holder}</p>}
31
- {fleet?.stageEnteredAt !== undefined && fleet.stageEnteredAt !== null && (
32
- <p className="work-stage">
33
- In {fleet.stage} for <Age modified={fleet.stageEnteredAt} short />
34
- </p>
35
- )}
31
+ {holder && <p className="work-owner">{holder}</p>}
32
+ {fleet?.stageEnteredAt !== undefined && fleet.stageEnteredAt !== null && <StageTime entered={fleet.stageEnteredAt} stage={fleet.stage} />}
36
33
  </Card>
37
34
  );
38
35
  }
36
+
37
+ function StageTime({ stage, entered }: { readonly stage: FleetWork["stage"]; readonly entered: number }) {
38
+ const { ended, label } = stageTime(stage);
39
+ return (
40
+ <p className="work-stage">
41
+ {label} {ended ? "" : "for "}
42
+ <Age modified={entered} short />
43
+ {ended ? " ago" : ""}
44
+ </p>
45
+ );
46
+ }
@@ -23,9 +23,15 @@ export function WorkLane({
23
23
  const [open, setOpen] = useState(!collapsed);
24
24
  return (
25
25
  <section aria-label={lane} className="work-column min-w-0">
26
- <h2 className="m-0 mb-3 flex min-h-8 items-center justify-between gap-3 whitespace-nowrap font-semibold text-sm">
26
+ <h2 className="m-0 mb-3 flex min-h-8 items-center justify-between gap-2 font-semibold text-sm">
27
27
  {collapsed ? (
28
- <Button aria-expanded={open} onClick={() => setOpen(!open)} size="sm" variant="outline">
28
+ <Button
29
+ aria-expanded={open}
30
+ className="h-auto min-h-8 shrink whitespace-normal text-left"
31
+ onClick={() => setOpen(!open)}
32
+ size="sm"
33
+ variant="outline"
34
+ >
29
35
  {lane}
30
36
  </Button>
31
37
  ) : (
@@ -22,7 +22,7 @@ export function WorkNow({ data }: { readonly data: WorkProjection }) {
22
22
  Fleet is unavailable. Lanes use authored status. {fleet?.state === "unavailable" ? fleet.reason : "Fleet data has not loaded."}
23
23
  </p>
24
24
  )}
25
- <div className="work-columns work-now grid auto-cols-[minmax(260px,1fr)] grid-flow-col gap-4 overflow-x-auto pb-2 max-md:auto-cols-auto max-md:grid-flow-row">
25
+ <div className="work-columns work-now grid grid-cols-[repeat(6,minmax(8.5rem,1fr))] gap-3 overflow-x-auto pb-2">
26
26
  {nowLanes.map((lane, index) => {
27
27
  const rows = (groups.get(lane) ?? []).filter((item) => index !== 5 || recent.has(item.id));
28
28
  return (
@@ -1,7 +1,7 @@
1
1
  import type { Cause } from "effect";
2
2
  import * as Atom from "effect/reactivity/Atom";
3
- import { resolvedDocumentFailure } from "#client/page/resolvedDocumentFailure.ts";
4
3
  import { pageQueries } from "./pageQueries.ts";
4
+ import { resolvedDocumentFailure } from "./resolvedDocumentFailure.ts";
5
5
  import type { ShellApi } from "./ShellApi.ts";
6
6
 
7
7
  function health(value: { readonly _tag: string; readonly waiting: boolean; readonly cause?: Cause.Cause<unknown> }) {
@@ -0,0 +1,3 @@
1
+ export function destinationName(label: string, count: number | null | undefined, unit: string) {
2
+ return count === null || count === undefined ? label : `${label}, ${count} ${unit}`;
3
+ }
@@ -219,12 +219,6 @@
219
219
  }
220
220
  }
221
221
 
222
- .answer-cards > .response-panel {
223
- max-width: 72ch;
224
- padding: 1rem;
225
- margin: 0;
226
- }
227
-
228
222
  @layer components {
229
223
  figure.diagram {
230
224
  @apply mx-0;
@@ -1,14 +1,14 @@
1
1
  import { isRedirect, type LoaderFnContext, redirect } from "@tanstack/react-router";
2
2
  import { Predicate } from "effect";
3
3
  import type * as AtomRegistry from "effect/reactivity/AtomRegistry";
4
- import { loadQuery } from "@shivaedev/effect-react/router.ts";
5
- import { documentQuery, navigationQuery } from "#client/page/routeQuery.ts";
4
+ import { loadQuery } from "@shivaedev/effect-react/loadQuery.ts";
6
5
  import type { ShellOptions } from "./BoardShell.tsx";
7
6
  import { documentRoute } from "./documentRoute.ts";
8
7
  import { loadProjection } from "./loadProjection.ts";
9
8
  import { loadRemaining } from "./loadRemaining.ts";
10
9
  import { projectionRoute } from "./projectionRoute.ts";
11
10
  import { remainingRoute } from "./remainingRoute.ts";
11
+ import { documentQuery, navigationQuery } from "./routeQuery.ts";
12
12
 
13
13
  export type RouterShell = ShellOptions & { readonly registry: AtomRegistry.AtomRegistry };
14
14
 
@@ -1,8 +1,8 @@
1
1
  import type * as AtomRegistry from "effect/reactivity/AtomRegistry";
2
- import { loadQuery } from "@shivaedev/effect-react/router.ts";
3
- import { routeQueryOptions } from "#client/page/routeQuery.ts";
2
+ import { loadQuery } from "@shivaedev/effect-react/loadQuery.ts";
4
3
  import { pageQueries } from "./pageQueries.ts";
5
4
  import type { ProjectionRoute } from "./projectionRoute.ts";
5
+ import { routeQueryOptions } from "./routeQuery.ts";
6
6
  import type { ShellApi } from "./ShellApi.ts";
7
7
  export function loadProjection(
8
8
  registry: AtomRegistry.AtomRegistry,
@@ -1,7 +1,7 @@
1
1
  import type * as AtomRegistry from "effect/reactivity/AtomRegistry";
2
- import { loadQuery } from "@shivaedev/effect-react/router.ts";
3
- import { routeQueryOptions } from "#client/page/routeQuery.ts";
2
+ import { loadQuery } from "@shivaedev/effect-react/loadQuery.ts";
4
3
  import type { RemainingRoute } from "./remainingRoute.ts";
4
+ import { routeQueryOptions } from "./routeQuery.ts";
5
5
  import type { ShellApi } from "./ShellApi.ts";
6
6
  export function loadRemaining(
7
7
  registry: AtomRegistry.AtomRegistry,
@@ -1,6 +1,6 @@
1
1
  import type * as AsyncResult from "effect/reactivity/AsyncResult";
2
2
  import type * as Atom from "effect/reactivity/Atom";
3
- import { routeQueryOptions } from "#client/page/routeQuery.ts";
3
+ import { routeQueryOptions } from "./routeQuery.ts";
4
4
  import type { ShellApi } from "./ShellApi.ts";
5
5
 
6
6
  export function pageQueries(api: ShellApi, kind: string, url: string): readonly Atom.Atom<AsyncResult.AsyncResult<unknown, unknown>>[] {
@@ -1,7 +1,7 @@
1
1
  import { useAtomRef } from "@effect/atom-react";
2
2
  import { Equal, Option, Schema } from "effect";
3
3
  import { useEffect, useState } from "react";
4
- import { useCreate } from "@shivaedev/effect-react/create.ts";
4
+ import { useCreate } from "@shivaedev/effect-react/client/create.ts";
5
5
  import { HandoffInput, type HandoffPreview } from "#browser/handoffs/schema.ts";
6
6
  import type { HandoffProjection } from "#browser/views/remaining.ts";
7
7
  import { formDraft } from "#client/formDraft.ts";
@@ -2,7 +2,7 @@ import { Cause, Option, Predicate } from "effect";
2
2
  import type * as AsyncResult from "effect/reactivity/AsyncResult";
3
3
  import type * as Atom from "effect/reactivity/Atom";
4
4
  import { 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 { ProjectionRoute } from "./projectionRoute.ts";
7
7
  import type { RemainingRoute } from "./remainingRoute.ts";
8
8
 
@@ -1,7 +1,7 @@
1
1
  import { useAtomRef } from "@effect/atom-react";
2
2
  import { Equal, Option, Schema } from "effect";
3
3
  import { useEffect, useState } from "react";
4
- import { useCreate } from "@shivaedev/effect-react/create.ts";
4
+ import { useCreate } from "@shivaedev/effect-react/client/create.ts";
5
5
  import type { Prompt } from "#browser/responses/Prompt.ts";
6
6
  import type { PromptAnswer, QuestionPreview } from "#browser/responses/schema.ts";
7
7
  import { formDraft } from "#client/formDraft.ts";
@@ -11,7 +11,7 @@ import { authorLabel } from "#client/responses/authorLabel.ts";
11
11
  import { type ResponseInput, responseFormSchema } from "#client/responses/formSchema.ts";
12
12
  import { responsePreview } from "#client/responses/preview.ts";
13
13
  import { retainContext } from "#client/responses/retainContext.ts";
14
- import { responseFeedback, responseSummary } from "#client/responses/summary.ts";
14
+ import { chosenLabels, responseFeedback, responseSummary } from "#client/responses/summary.ts";
15
15
  import { sourceChanged } from "#client/sourceChanged.ts";
16
16
  import { createResponse } from "./createResponse.ts";
17
17
  import { useFormClient } from "./FormContext.ts";
@@ -116,7 +116,9 @@ export function useResponseForm(source: QuestionPreview, prompts: readonly Promp
116
116
  setInteraction("cleared");
117
117
  }
118
118
  const previewed = preview !== undefined && same && Equal.equals(preview, values);
119
- const { outcome, text: status } = formStatus(created, interaction, "response", (saved) => responseSaved(saved.id, created.dirty));
119
+ const { outcome, text: status } = formStatus(created, interaction, "response", (saved) =>
120
+ responseSaved(saved.id, created.dirty, chosenLabels(prompts, saved.answers)),
121
+ );
120
122
  const summary = responseSummary(values, prompts, answers(values), attempted);
121
123
  return {
122
124
  answers: answers(values),
@@ -1,3 +1,4 @@
1
+ import { optionText, promptHeading } from "#browser/responses/cardContext.ts";
1
2
  import type { Prompt } from "#browser/responses/Prompt.ts";
2
3
  import type { PromptAnswer } from "#browser/responses/schema.ts";
3
4
  import type { ResponseInput } from "./formSchema.ts";
@@ -14,7 +15,9 @@ export function responseSummary(values: ResponseInput, prompts: readonly Prompt[
14
15
  : [];
15
16
  return [
16
17
  values.author.trim() ? "" : "Add your local author label.",
17
- missing.length > 0 ? `Unanswered prompts: ${missing.map((prompt) => `Question ${prompts.indexOf(prompt) + 1} · ${prompt.id}`).join("; ")}.` : "",
18
+ missing.length > 0
19
+ ? `Unanswered: ${missing.map((prompt) => `“${promptHeading(prompt.source).title ?? `Question ${prompts.indexOf(prompt) + 1}`}”`).join(", ")}.`
20
+ : "",
18
21
  ]
19
22
  .filter(Boolean)
20
23
  .join(" ");
@@ -23,3 +26,12 @@ export function responseSummary(values: ResponseInput, prompts: readonly Prompt[
23
26
  export function responseFeedback(status: string, outcome: string | undefined, summary: string) {
24
27
  return outcome || status.startsWith("Draft cleared") ? status : summary || status;
25
28
  }
29
+
30
+ export function chosenLabels(prompts: readonly Prompt[], answers: readonly PromptAnswer[] | undefined) {
31
+ return prompts
32
+ .flatMap((prompt) => {
33
+ const selected = answers?.find((answer) => answer.prompt === prompt.id)?.selected ?? [];
34
+ return prompt.options.filter((option) => selected.includes(option.id)).map((option) => optionText(option.id, option.source).label);
35
+ })
36
+ .join(", ");
37
+ }
@@ -1,5 +1,5 @@
1
1
  import { Clock, Context, Effect, Schema } from "effect";
2
- import { defineService } from "@shivaedev/effect-service/define-service.ts";
2
+ import { defineService } from "@shivaedev/effect-service/defineService.ts";
3
3
  import * as limits from "#browser/diagnostics/schema.ts";
4
4
  import type { FleetHealth } from "./health.ts";
5
5
 
@@ -1,7 +1,7 @@
1
1
  import { Clock, Effect, FileSystem, Path, Schema, Semaphore } from "effect";
2
- import { defineService } from "@shivaedev/effect-service/define-service.ts";
2
+ import { defineService } from "@shivaedev/effect-service/defineService.ts";
3
3
  import { Conflict, Forbidden, PreconditionFailed, ServiceUnavailable } from "@shivaedev/platform/errors/taxonomy.ts";
4
- import { rpcBoundary } from "@shivaedev/platform/rpc-server/boundary.ts";
4
+ import { rpcBoundary } from "@shivaedev/platform/server/rpc-server/boundary.ts";
5
5
  import type { DraftInput } from "#browser/responses/schema.ts";
6
6
  import { awaitResponse } from "#server/responses/await.ts";
7
7
  import { responseHistory } from "#server/responses/history.ts";
@@ -6,7 +6,7 @@ import type { Snapshot } from "#server/search/snapshot.ts";
6
6
  import { answeredQuestion } from "./questionState.ts";
7
7
  import { requestInbox } from "./requestInbox.ts";
8
8
  import { isMaintainerRequest } from "./requests.ts";
9
- import { closedItem } from "./status.ts";
9
+ import { closedItem, deferredItem } from "./status.ts";
10
10
 
11
11
  const automation = /^(?:codex|claude|fleet|orchestrator|agent|automation|bot|copilot)(?:$|[-_. /])/iu;
12
12
 
@@ -24,6 +24,10 @@ function addressedElsewhere(document: MetadataDocument, maintainer: string): boo
24
24
  }
25
25
 
26
26
  export function waitingReason(document: MetadataDocument, model: MetadataModel, maintainer = "maintainer"): string | undefined {
27
+ return deferredItem(document) ? undefined : attentionReason(document, model, maintainer);
28
+ }
29
+
30
+ function attentionReason(document: MetadataDocument, model: MetadataModel, maintainer: string): string | undefined {
27
31
  const fields = document.parsed.fields;
28
32
  if (!openItem(document) || fields.kind === "response" || fields.kind === "handoff" || fields.question) {
29
33
  return undefined;
@@ -64,39 +68,59 @@ function inboxTarget(document: MetadataDocument, problem: string | undefined): s
64
68
  return answer ? responseUrl(item, answer) : identityUrl(item);
65
69
  }
66
70
 
67
- export function inboxItems(snapshot: Snapshot) {
68
- if (snapshot.unavailable.length > 0) {
69
- return [];
70
- }
71
+ function directItems(snapshot: Snapshot, skipped: ReadonlySet<string>, reasonOf: (document: MetadataDocument) => string | undefined) {
71
72
  const titles = new Map(snapshot.entries.filter((entry) => entry.kind === "document").map((entry) => [entry.file, entry.title]));
72
- const requests = requestInbox(snapshot);
73
- const you = requests.items.filter((item) => item.responseFrom.includes(snapshot.maintainer ?? "maintainer"));
74
- const direct = snapshot.documents.flatMap((document) => {
75
- if (requests.files.has(document.file)) {
73
+ return snapshot.documents.flatMap((document) => {
74
+ const reason = skipped.has(document.file) ? undefined : reasonOf(document);
75
+ if (!reason) {
76
76
  return [];
77
77
  }
78
78
  const id = document.parsed.fields.id;
79
79
  const problem = identityProblem(document, snapshot.model);
80
- const reason = waitingReason(document, snapshot.model, snapshot.maintainer);
81
- return reason
82
- ? [
83
- {
84
- document,
85
- href: inboxTarget(document, problem),
86
- id: id ?? document.file,
87
- problem,
88
- reason,
89
- request: undefined,
90
- title: titles.get(document.file) ?? document.file,
91
- unblocks: [id ?? ""],
92
- },
93
- ]
94
- : [];
80
+ return [
81
+ {
82
+ document,
83
+ href: inboxTarget(document, problem),
84
+ id: id ?? document.file,
85
+ modified: snapshot.modified.get(document.file),
86
+ problem,
87
+ reason,
88
+ request: undefined,
89
+ title: titles.get(document.file) ?? document.file,
90
+ unblocks: [id ?? ""],
91
+ },
92
+ ];
95
93
  });
96
- const ordinary = direct
94
+ }
95
+
96
+ function byAge(
97
+ left: { readonly id: string; readonly modified: number | undefined },
98
+ right: { readonly id: string; readonly modified: number | undefined },
99
+ ) {
100
+ return (left.modified ?? 0) - (right.modified ?? 0) || left.id.localeCompare(right.id);
101
+ }
102
+
103
+ export function inboxItems(snapshot: Snapshot) {
104
+ if (snapshot.unavailable.length > 0) {
105
+ return [];
106
+ }
107
+ const requests = requestInbox(snapshot);
108
+ const you = requests.items.filter((item) => item.responseFrom.includes(snapshot.maintainer ?? "maintainer"));
109
+ const ordinary = directItems(snapshot, requests.files, (document) => waitingReason(document, snapshot.model, snapshot.maintainer))
97
110
  .filter((item) => item.problem || !you.some((ask) => ask.id === item.id))
98
- .map((item) => ({ ...item, earlierAsks: 0, modified: snapshot.modified.get(item.document.file) }));
99
- return [...ordinary, ...you].sort((left, right) => (left.modified ?? 0) - (right.modified ?? 0) || left.id.localeCompare(right.id));
111
+ .map((item) => ({ ...item, earlierAsks: 0 }));
112
+ return [...ordinary, ...you].sort(byAge);
113
+ }
114
+
115
+ export function laterItems(snapshot: Snapshot) {
116
+ if (snapshot.unavailable.length > 0) {
117
+ return [];
118
+ }
119
+ function parked(document: MetadataDocument) {
120
+ const reason = deferredItem(document) ? attentionReason(document, snapshot.model, snapshot.maintainer ?? "maintainer") : undefined;
121
+ return reason && (document.parsed.fields.nextAction ?? reason);
122
+ }
123
+ return directItems(snapshot, requestInbox(snapshot).files, parked).sort(byAge);
100
124
  }
101
125
 
102
126
  export function fleetRecords(model: MetadataModel): ReadonlySet<string> {
@@ -6,6 +6,13 @@ import { questionId, revisionOf } from "#server/responses/records.ts";
6
6
 
7
7
  type Request = NonNullable<Metadata["attention"]>[number];
8
8
 
9
+ export type ManagedRequestState =
10
+ | { readonly _tag: "Open" }
11
+ | { readonly _tag: "Settled"; readonly receipt: RequestReceipt }
12
+ | { readonly _tag: "Conflicting"; readonly receipts: readonly string[] };
13
+
14
+ const open: ManagedRequestState = { _tag: "Open" };
15
+
9
16
  function uniqueDocument(id: string, model: MetadataModel): MetadataDocument | undefined {
10
17
  const matches = model.ids.get(id);
11
18
  return matches?.length === 1 ? matches[0] : undefined;
@@ -38,7 +45,7 @@ function appliedAnswer(question: string, revision: string, responseId: string |
38
45
  return current.length === 1 && current[0]?.id === responseId;
39
46
  }
40
47
 
41
- export function managedRequestReceipt(document: MetadataDocument, request: Request, model: MetadataModel): RequestReceipt | undefined {
48
+ function registeredQuestion(document: MetadataDocument, request: Request, model: MetadataModel) {
42
49
  const item = document.parsed.fields.id;
43
50
  if (
44
51
  request.managed !== true
@@ -57,7 +64,6 @@ export function managedRequestReceipt(document: MetadataDocument, request: Reque
57
64
  const revision = revisionOf(source);
58
65
  const id = questionId(item, request.id, revision, document.file);
59
66
  const archived = uniqueDocument(id, model);
60
- const index = responseIndex(model);
61
67
  const question = registeredContext(archived, model);
62
68
  if (
63
69
  archived?.parsed.diagnostics.length !== 0
@@ -70,14 +76,13 @@ export function managedRequestReceipt(document: MetadataDocument, request: Reque
70
76
  ) {
71
77
  return undefined;
72
78
  }
73
- if (index.receiptProblems) {
74
- return undefined;
75
- }
76
- const receipts = index.receipts.get(id) ?? [];
77
- const saved = receipts.length === 1 ? receipts[0] : undefined;
78
- const receipt = saved?.parsed.fields.requestReceipt;
79
+ return { id, revision };
80
+ }
81
+
82
+ function qualified(saved: MetadataDocument, question: string, revision: string, model: MetadataModel): RequestReceipt | undefined {
83
+ const receipt = saved.parsed.fields.requestReceipt;
79
84
  if (
80
- !(saved && receipt)
85
+ !receipt
81
86
  || saved.parsed.fields.kind !== "result"
82
87
  || saved.parsed.diagnostics.length > 0
83
88
  || !saved.parsed.fields.id
@@ -89,5 +94,31 @@ export function managedRequestReceipt(document: MetadataDocument, request: Reque
89
94
  if (receipt.disposition === "superseded") {
90
95
  return receipt.response === undefined ? receipt : undefined;
91
96
  }
92
- return appliedAnswer(id, revision, receipt.response, model) ? receipt : undefined;
97
+ return appliedAnswer(question, revision, receipt.response, model) ? receipt : undefined;
98
+ }
99
+
100
+ function settling(receipts: readonly RequestReceipt[]): RequestReceipt | undefined {
101
+ return receipts.toSorted(
102
+ (left, right) => Number(right.disposition === "applied") - Number(left.disposition === "applied") || right.recordedAt - left.recordedAt,
103
+ )[0];
104
+ }
105
+
106
+ export function managedRequestState(document: MetadataDocument, request: Request, model: MetadataModel): ManagedRequestState {
107
+ const question = registeredQuestion(document, request, model);
108
+ const index = responseIndex(model);
109
+ if (!question || index.receiptProblems) {
110
+ return open;
111
+ }
112
+ const saved = index.receipts.get(question.id) ?? [];
113
+ const receipts = saved.flatMap((candidate) => qualified(candidate, question.id, question.revision, model) ?? []);
114
+ const receipt = receipts.length === saved.length ? settling(receipts) : undefined;
115
+ if (receipt) {
116
+ return { _tag: "Settled", receipt };
117
+ }
118
+ return receipts.length > 0 ? { _tag: "Conflicting", receipts: saved.map((candidate) => candidate.file) } : open;
119
+ }
120
+
121
+ export function managedRequestReceipt(document: MetadataDocument, request: Request, model: MetadataModel): RequestReceipt | undefined {
122
+ const state = managedRequestState(document, request, model);
123
+ return state._tag === "Settled" ? state.receipt : undefined;
93
124
  }
@@ -1,6 +1,6 @@
1
1
  import type { InboxProjection } from "#browser/views/schema.ts";
2
2
  import type { Snapshot } from "#server/search/snapshot.ts";
3
- import { inboxItems } from "./inbox.ts";
3
+ import { inboxItems, laterItems } from "./inbox.ts";
4
4
  import { requestInbox } from "./requestInbox.ts";
5
5
  import { attentionRequests } from "./requests.ts";
6
6
 
@@ -12,7 +12,9 @@ export function attentionProjection(snapshot: Snapshot): InboxProjection {
12
12
  return cached;
13
13
  }
14
14
  const requests = requestInbox(snapshot);
15
- function project({ document, request: _request, unblocks: _unblocks, ...item }: ReturnType<typeof inboxItems>[number]) {
15
+ function project<
16
+ TRow extends { readonly document: { readonly file: string }; readonly request: string | undefined; readonly unblocks: readonly string[] },
17
+ >({ document, request: _request, unblocks: _unblocks, ...item }: TRow) {
16
18
  return { ...item, file: document.file };
17
19
  }
18
20
  const you = inboxItems(snapshot).map(project);
@@ -22,6 +24,7 @@ export function attentionProjection(snapshot: Snapshot): InboxProjection {
22
24
  complete: snapshot.unavailable.length === 0,
23
25
  issues,
24
26
  items: you,
27
+ later: laterItems(snapshot).map(project),
25
28
  parked: requests.parked.map(project),
26
29
  resolved: requests.resolved.map(project),
27
30
  sourceIssues: issues.length > 0,
@@ -23,10 +23,10 @@ export function requestInbox(snapshot: Snapshot) {
23
23
  return {
24
24
  document: entry.document,
25
25
  earlierAsks: 0,
26
- href: responseUrl(entry.itemId, entry.request.id),
26
+ href: entry.problem === undefined ? responseUrl(entry.itemId, entry.request.id) : entry.href,
27
27
  id: target?.parsed.fields.id ?? entry.itemId,
28
28
  modified: snapshot.modified.get(entry.document.file),
29
- problem: undefined,
29
+ problem: entry.problem,
30
30
  reason: entry.request.managed ? `Managed decision: ${entry.request.reason}` : entry.request.reason,
31
31
  request: entry.request.id,
32
32
  responseFrom: entry.request.responseFrom,
@@ -1,5 +1,5 @@
1
1
  import type { Metadata } from "#metadata/schema.ts";
2
- import { managedRequestReceipt } from "#server/attention/managedRequestReceipt.ts";
2
+ import { managedRequestState } from "#server/attention/managedRequestReceipt.ts";
3
3
  import { identityUrl, referenceTarget } from "#server/metadata/links.ts";
4
4
  import type { MetadataDocument } from "#server/metadata/model.ts";
5
5
  import type { Snapshot } from "#server/search/snapshot.ts";
@@ -10,6 +10,7 @@ export interface AttentionEntry {
10
10
  readonly href: string;
11
11
  readonly itemId: string;
12
12
  readonly line: number;
13
+ readonly problem: string | undefined;
13
14
  readonly request: Request;
14
15
  readonly title: string;
15
16
  }
@@ -35,16 +36,20 @@ function requestsFor(document: MetadataDocument, snapshot: Snapshot, title: stri
35
36
  request.state !== "open"
36
37
  || requests.filter((other) => other.id === request.id).length !== 1
37
38
  || request.unblocks.some((target) => !referenceTarget(target, snapshot.model))
38
- || managedRequestReceipt(document, request, snapshot.model) !== undefined
39
39
  ) {
40
40
  return [];
41
41
  }
42
+ const receipts = managedRequestState(document, request, snapshot.model);
43
+ if (receipts._tag === "Settled") {
44
+ return [];
45
+ }
42
46
  return [
43
47
  {
44
48
  document,
45
49
  href: `${identityUrl(itemId)}#attention-request-${encodeURIComponent(request.id)}`,
46
50
  itemId,
47
51
  line: lines[`attention.${index}`] ?? lines.attention ?? 2,
52
+ problem: receipts._tag === "Conflicting" ? `Request receipts disagree: ${receipts.receipts.join(", ")}` : undefined,
48
53
  request,
49
54
  title,
50
55
  },
@@ -2,7 +2,7 @@ import { Effect, type FileSystem, Layer, type Path, type PlatformError } from "e
2
2
  import { HttpRouter } from "effect/http";
3
3
  import { RpcSerialization, RpcServer } from "effect/rpc";
4
4
  import type { InternalServerError } from "@shivaedev/platform/errors/taxonomy.ts";
5
- import { localRpcGuard } from "@shivaedev/platform/rpc-server/local.ts";
5
+ import { localRpcGuard } from "@shivaedev/platform/server/rpc-server/local.ts";
6
6
  import type { RenderFailed } from "#render/Failed.ts";
7
7
  import { workRpcs } from "#rpc/contract.ts";
8
8
  import { boardRuntime } from "#server/boardRuntime.ts";
@@ -5,13 +5,14 @@ import type { DocumentRequest } from "#browser/document/schema.ts";
5
5
  import { reasonMarkdown } from "#browser/responses/reasonMarkdown.ts";
6
6
  import type { MetadataDocument } from "#server/metadata/model.ts";
7
7
  import { documentBody } from "./body.ts";
8
+ import { REVIEWED_WORK_HEADING, reviewedWork } from "./reviewedWork.ts";
8
9
 
9
10
  export function answerBody(document: MetadataDocument, requests: readonly DocumentRequest[]): string {
10
11
  const source = documentBody(document);
11
12
  const cards = requests.filter((request) => request.open && request.preview?.source === document.file);
12
13
  const managed = document.parsed.fields.attention?.some((request) => request.managed);
13
14
  const title = managed ? requests[0]?.title : undefined;
14
- if (cards.length === 0 && !title) {
15
+ if (cards.length === 0 && !title && !managed) {
15
16
  return source;
16
17
  }
17
18
  const tree = unified().use(remarkParse).use(remarkDirective).parse(source);
@@ -28,6 +29,10 @@ export function answerBody(document: MetadataDocument, requests: readonly Docume
28
29
  if (index === 0 && node.type === "heading" && node.depth === 1 && title) {
29
30
  return [`# ${title}`];
30
31
  }
32
+ const reviewed = managed && node.type === "code" && underReviewedWork(tree.children[index - 1], source) ? reviewedWork(node.value) : undefined;
33
+ if (reviewed !== undefined) {
34
+ return [reviewed];
35
+ }
31
36
  return [source.slice(node.position?.start.offset, node.position?.end.offset)];
32
37
  })
33
38
  .join("\n\n");
@@ -94,3 +99,7 @@ function inCardSection(nodes: import("mdast").RootContent[], index: number, sour
94
99
  }
95
100
  return false;
96
101
  }
102
+
103
+ function underReviewedWork(node: import("mdast").RootContent | undefined, source: string) {
104
+ return node?.type === "heading" && source.slice(node.position?.start.offset, node.position?.end.offset).trim() === REVIEWED_WORK_HEADING;
105
+ }