@shivaedev/work-board 0.16.0 → 0.17.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 (197) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +4 -4
  3. package/assets/react.js +52 -39
  4. package/dist/browser/views/remaining.d.ts +0 -240
  5. package/dist/browser/views/remaining.d.ts.map +1 -1
  6. package/dist/browser/views/remaining.js +0 -2
  7. package/dist/browser/views/remaining.js.map +1 -1
  8. package/dist/client/react/DocumentAttention.d.ts.map +1 -1
  9. package/dist/client/react/DocumentAttention.js +2 -3
  10. package/dist/client/react/DocumentAttention.js.map +1 -1
  11. package/dist/client/react/ProjectionFrame.js +1 -1
  12. package/dist/client/react/ProjectionFrame.js.map +1 -1
  13. package/dist/client/react/RemainingMain.d.ts.map +1 -1
  14. package/dist/client/react/RemainingMain.js +0 -3
  15. package/dist/client/react/RemainingMain.js.map +1 -1
  16. package/dist/client/react/ResultPage.d.ts.map +1 -1
  17. package/dist/client/react/ResultPage.js +2 -1
  18. package/dist/client/react/ResultPage.js.map +1 -1
  19. package/dist/client/react/ShellApi.d.ts +0 -1
  20. package/dist/client/react/ShellApi.d.ts.map +1 -1
  21. package/dist/client/react/TodayOptions.d.ts.map +1 -1
  22. package/dist/client/react/TodayOptions.js +2 -2
  23. package/dist/client/react/TodayOptions.js.map +1 -1
  24. package/dist/client/react/entry.js +1 -1
  25. package/dist/client/react/entry.js.map +1 -1
  26. package/dist/client/react/loadBoardPage.d.ts.map +1 -1
  27. package/dist/client/react/loadBoardPage.js +5 -13
  28. package/dist/client/react/loadBoardPage.js.map +1 -1
  29. package/dist/client/react/loadRemaining.d.ts +109 -349
  30. package/dist/client/react/loadRemaining.d.ts.map +1 -1
  31. package/dist/client/react/loadRemaining.js +0 -2
  32. package/dist/client/react/loadRemaining.js.map +1 -1
  33. package/dist/client/react/pageQueries.d.ts.map +1 -1
  34. package/dist/client/react/pageQueries.js +0 -3
  35. package/dist/client/react/pageQueries.js.map +1 -1
  36. package/dist/client/react/readerLink.js +1 -1
  37. package/dist/client/react/readerLink.js.map +1 -1
  38. package/dist/client/react/remainingRoute.d.ts +1 -1
  39. package/dist/client/react/remainingRoute.d.ts.map +1 -1
  40. package/dist/client/react/remainingRoute.js +0 -2
  41. package/dist/client/react/remainingRoute.js.map +1 -1
  42. package/dist/responses/url.js +1 -1
  43. package/dist/responses/url.js.map +1 -1
  44. package/dist/rpc/PageUrl.d.ts.map +1 -1
  45. package/dist/rpc/PageUrl.js +0 -1
  46. package/dist/rpc/PageUrl.js.map +1 -1
  47. package/dist/rpc/contract.d.ts +2203 -2926
  48. package/dist/rpc/contract.d.ts.map +1 -1
  49. package/dist/rpc/contract.js +6 -9
  50. package/dist/rpc/contract.js.map +1 -1
  51. package/dist/rpc/responseContract.d.ts +56 -56
  52. package/dist/server/{responses/service.d.ts → Responses.d.ts} +9 -21
  53. package/dist/server/Responses.d.ts.map +1 -0
  54. package/dist/server/{responses/service.js → Responses.js} +22 -22
  55. package/dist/server/Responses.js.map +1 -0
  56. package/dist/server/attention/inbox.d.ts +2 -0
  57. package/dist/server/attention/inbox.d.ts.map +1 -1
  58. package/dist/server/attention/inbox.js +1 -0
  59. package/dist/server/attention/inbox.js.map +1 -1
  60. package/dist/server/attention/projection.js +1 -1
  61. package/dist/server/attention/projection.js.map +1 -1
  62. package/dist/server/attention/requestInbox.d.ts +3 -0
  63. package/dist/server/attention/requestInbox.d.ts.map +1 -1
  64. package/dist/server/attention/requestInbox.js +1 -0
  65. package/dist/server/attention/requestInbox.js.map +1 -1
  66. package/dist/server/boardRuntime.d.ts +2 -2
  67. package/dist/server/boardRuntime.d.ts.map +1 -1
  68. package/dist/server/boardRuntime.js +12 -16
  69. package/dist/server/boardRuntime.js.map +1 -1
  70. package/dist/server/boardServices.d.ts +3 -4
  71. package/dist/server/boardServices.d.ts.map +1 -1
  72. package/dist/server/boardServices.js +5 -4
  73. package/dist/server/boardServices.js.map +1 -1
  74. package/dist/server/boardSocket.d.ts +1 -1
  75. package/dist/server/boardSocket.d.ts.map +1 -1
  76. package/dist/server/document/read.d.ts +4 -10
  77. package/dist/server/document/read.d.ts.map +1 -1
  78. package/dist/server/document/read.js +5 -5
  79. package/dist/server/document/read.js.map +1 -1
  80. package/dist/server/document/requests.d.ts.map +1 -1
  81. package/dist/server/document/requests.js +1 -2
  82. package/dist/server/document/requests.js.map +1 -1
  83. package/dist/server/handoffs/Service.d.ts +1 -2
  84. package/dist/server/handoffs/Service.d.ts.map +1 -1
  85. package/dist/server/handoffs/Service.js +14 -13
  86. package/dist/server/handoffs/Service.js.map +1 -1
  87. package/dist/server/http/assets.d.ts +0 -3
  88. package/dist/server/http/assets.d.ts.map +1 -1
  89. package/dist/server/http/assets.js +1 -7
  90. package/dist/server/http/assets.js.map +1 -1
  91. package/dist/server/http/boardRoutes.d.ts.map +1 -1
  92. package/dist/server/http/boardRoutes.js +3 -3
  93. package/dist/server/http/boardRoutes.js.map +1 -1
  94. package/dist/server/integration/responses.d.ts +207 -4
  95. package/dist/server/integration/responses.d.ts.map +1 -1
  96. package/dist/server/integration/responses.js +4 -2
  97. package/dist/server/integration/responses.js.map +1 -1
  98. package/dist/server/mcp/client.d.ts +2 -242
  99. package/dist/server/mcp/client.d.ts.map +1 -1
  100. package/dist/server/mcp/queries.d.ts +24 -744
  101. package/dist/server/mcp/queries.d.ts.map +1 -1
  102. package/dist/server/mcp/queries.js +3 -3
  103. package/dist/server/mcp/queries.js.map +1 -1
  104. package/dist/server/packageInfo.d.ts +1 -1
  105. package/dist/server/packageInfo.js +1 -1
  106. package/dist/server/responses/readingActions.d.ts +10 -1
  107. package/dist/server/responses/readingActions.d.ts.map +1 -1
  108. package/dist/server/responses/readingActions.js +2 -4
  109. package/dist/server/responses/readingActions.js.map +1 -1
  110. package/dist/server/responses/registerQuestion.d.ts +2 -2
  111. package/dist/server/responses/registerQuestion.d.ts.map +1 -1
  112. package/dist/server/responses/registerQuestion.js +3 -3
  113. package/dist/server/responses/registerQuestion.js.map +1 -1
  114. package/dist/server/responses/retry.d.ts +2 -2
  115. package/dist/server/responses/retry.d.ts.map +1 -1
  116. package/dist/server/responses/retry.js +1 -1
  117. package/dist/server/responses/retry.js.map +1 -1
  118. package/dist/server/rpc/HandlerOptions.d.ts +0 -8
  119. package/dist/server/rpc/HandlerOptions.d.ts.map +1 -1
  120. package/dist/server/rpc/handlers.d.ts +1 -1
  121. package/dist/server/rpc/handlers.d.ts.map +1 -1
  122. package/dist/server/rpc/handlers.js +14 -13
  123. package/dist/server/rpc/handlers.js.map +1 -1
  124. package/dist/server/rpc/hints.d.ts +3 -5
  125. package/dist/server/rpc/hints.d.ts.map +1 -1
  126. package/dist/server/rpc/hints.js +5 -1
  127. package/dist/server/rpc/hints.js.map +1 -1
  128. package/dist/server/views/fleetItems.d.ts +2 -2
  129. package/dist/server/views/fleetItems.d.ts.map +1 -1
  130. package/dist/server/views/fleetItems.js +8 -3
  131. package/dist/server/views/fleetItems.js.map +1 -1
  132. package/dist/server/views/projectionQueries.d.ts +5 -9
  133. package/dist/server/views/projectionQueries.d.ts.map +1 -1
  134. package/dist/server/views/projectionQueries.js +6 -6
  135. package/dist/server/views/projectionQueries.js.map +1 -1
  136. package/dist/server/views/remainingQueries.d.ts +4 -252
  137. package/dist/server/views/remainingQueries.d.ts.map +1 -1
  138. package/dist/server/views/remainingQueries.js +11 -19
  139. package/dist/server/views/remainingQueries.js.map +1 -1
  140. package/dist/server/workspace.d.ts +222 -0
  141. package/dist/server/workspace.d.ts.map +1 -0
  142. package/dist/server/workspace.js +25 -0
  143. package/dist/server/workspace.js.map +1 -0
  144. package/package.json +3 -3
  145. package/src/browser/views/remaining.ts +0 -3
  146. package/src/client/react/DocumentAttention.tsx +2 -3
  147. package/src/client/react/ProjectionFrame.tsx +1 -1
  148. package/src/client/react/RemainingMain.tsx +0 -10
  149. package/src/client/react/ResultPage.tsx +2 -2
  150. package/src/client/react/ShellApi.ts +0 -1
  151. package/src/client/react/TodayOptions.tsx +2 -2
  152. package/src/client/react/entry.tsx +1 -1
  153. package/src/client/react/loadBoardPage.ts +5 -14
  154. package/src/client/react/loadRemaining.ts +0 -2
  155. package/src/client/react/pageQueries.ts +0 -3
  156. package/src/client/react/readerLink.ts +1 -1
  157. package/src/client/react/remainingRoute.ts +0 -2
  158. package/src/responses/url.ts +1 -1
  159. package/src/rpc/PageUrl.ts +0 -1
  160. package/src/rpc/contract.ts +6 -9
  161. package/src/server/{responses/service.ts → Responses.ts} +21 -29
  162. package/src/server/attention/inbox.ts +1 -0
  163. package/src/server/attention/projection.ts +1 -1
  164. package/src/server/attention/requestInbox.ts +1 -0
  165. package/src/server/boardRuntime.ts +20 -25
  166. package/src/server/boardServices.ts +7 -7
  167. package/src/server/document/read.ts +6 -14
  168. package/src/server/document/requests.ts +1 -2
  169. package/src/server/handoffs/Service.ts +16 -19
  170. package/src/server/http/assets.ts +1 -7
  171. package/src/server/http/boardRoutes.ts +3 -3
  172. package/src/server/integration/responses.ts +5 -3
  173. package/src/server/mcp/queries.ts +3 -3
  174. package/src/server/packageInfo.ts +1 -1
  175. package/src/server/responses/readingActions.ts +3 -3
  176. package/src/server/responses/registerQuestion.ts +5 -5
  177. package/src/server/responses/retry.ts +3 -3
  178. package/src/server/rpc/HandlerOptions.ts +0 -8
  179. package/src/server/rpc/handlers.ts +17 -15
  180. package/src/server/rpc/hints.ts +6 -3
  181. package/src/server/views/fleetItems.ts +8 -4
  182. package/src/server/views/projectionQueries.ts +6 -7
  183. package/src/server/views/remainingQueries.ts +11 -31
  184. package/src/server/workspace.ts +41 -0
  185. package/assets/native.js +0 -37
  186. package/dist/client/entry.d.ts +0 -3
  187. package/dist/client/entry.d.ts.map +0 -1
  188. package/dist/client/entry.js +0 -3
  189. package/dist/client/entry.js.map +0 -1
  190. package/dist/client/react/RespondPage.d.ts +0 -5
  191. package/dist/client/react/RespondPage.d.ts.map +0 -1
  192. package/dist/client/react/RespondPage.js +0 -5
  193. package/dist/client/react/RespondPage.js.map +0 -1
  194. package/dist/server/responses/service.d.ts.map +0 -1
  195. package/dist/server/responses/service.js.map +0 -1
  196. package/src/client/entry.ts +0 -3
  197. package/src/client/react/RespondPage.tsx +0 -8
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workspace.js","sourceRoot":"","sources":["../../src/server/workspace.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,IAAI,EAAsB,MAAM,QAAQ,CAAC;AAC/E,OAAO,EAAE,aAAa,EAAE,MAAM,6CAA6C,CAAC;AAE5E,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAgBxD,MAAM,OAAO,eAAgB,SAAQ,OAAO,CAAC,OAAO,EAAqC,CAAC,2BAA2B,CAAC;CAAG;AAEzH,MAAM,CAAC,MAAM,SAAS,GAAG,aAAa,CAAC;IACtC,EAAE,EAAE,qBAAqB;IACzB,UAAU,EAAE,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;QAC/B,OAAO,KAAK,CAAC,CAAC,eAAe,CAAC;IAC/B,CAAC,CAAC;IACF,OAAO,EAAE,CAAC,EAAE,KAAK,EAAE,GAAG,OAAO,EAAE,EAAE,EAAE;QAClC,MAAM,QAAQ,GAAG,EAAE,GAAG,OAAO,EAAE,QAAQ,EAAE,OAAO,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC;QACpE,MAAM,IAAI,GAAG,YAAY,CAAC,QAAQ,CAAC,CAAC;QACpC,SAAS,QAAQ,CAAC,GAAW;YAC5B,OAAO,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC,CAAC;QACjE,CAAC;QACD,OAAO;YACN,QAAQ;YACR,KAAK,EAAE,GAAG,EAAE,CAAC,KAAK;YAClB,QAAQ,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC;SACxC,CAAC;IACH,CAAC;IACD,QAAQ,EAAE,CAAC,eAAe,EAAE,UAAU,CAAC,UAAU,EAAE,IAAI,CAAC,IAAI,CAAC;CAC7D,CAAC,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shivaedev/work-board",
3
- "version": "0.16.0",
3
+ "version": "0.17.0",
4
4
  "description": "Serve a folder of markdown files as a live local page that updates in place when a file changes",
5
5
  "homepage": "https://github.com/ShivaeDev/platform/tree/main/packages/work-board#readme",
6
6
  "bugs": {
@@ -411,7 +411,7 @@
411
411
  "dependencies": {
412
412
  "@shivaedev/effect-contract": "0.5.1",
413
413
  "@shivaedev/effect-service": "0.3.0",
414
- "@shivaedev/platform": "0.14.3",
414
+ "@shivaedev/platform": "0.14.4",
415
415
  "mdast-util-directive": "3.1.1",
416
416
  "mermaid": "12.1.0",
417
417
  "react": "19.3.0",
@@ -470,7 +470,7 @@
470
470
  },
471
471
  "scripts": {
472
472
  "build": "node --eval \"import('node:fs').then(({ rmSync }) => rmSync('dist', { force: true, recursive: true }))\" && tsc --project tsconfig.build.json && pnpm build:browser",
473
- "build:browser": "esbuild src/client/entry.ts --bundle --platform=browser --format=esm --conditions=source --minify --banner:js='/* @shivaedev/work-board native browser bundle */' --outfile=assets/native.js && esbuild src/server/responses/agent-cli.ts --bundle --platform=node --format=esm --conditions=source --minify --outfile=assets/agent.js && vite build",
473
+ "build:browser": "esbuild src/server/responses/agent-cli.ts --bundle --platform=node --format=esm --conditions=source --minify --outfile=assets/agent.js && vite build",
474
474
  "ready": "pnpm typecheck && pnpm test && pnpm build",
475
475
  "test": "pnpm test:prepare && vitest run",
476
476
  "test:prepare": "pnpm build:browser",
@@ -1,5 +1,4 @@
1
1
  import { Schema } from "effect";
2
- import { Document, DocumentRequest } from "#browser/document/schema.ts";
3
2
  import { HandoffPreview, HandoffReading } from "#browser/handoffs/schema.ts";
4
3
  import { DiagnosticsSnapshot } from "#diagnostics/daemonContract.ts";
5
4
 
@@ -11,8 +10,6 @@ export const StartProjection = Schema.Struct({
11
10
  templates: Schema.Array(Schema.Struct({ file: Schema.String, label: Schema.String, source: Schema.String })),
12
11
  });
13
12
  export type StartProjection = typeof StartProjection.Type;
14
- export const RespondProjection = Schema.Struct({ document: Document, request: DocumentRequest });
15
- export type RespondProjection = typeof RespondProjection.Type;
16
13
  export const HandoffProjection = Schema.Struct({
17
14
  background: Schema.String,
18
15
  goal: Schema.String,
@@ -1,5 +1,6 @@
1
1
  import type { Document } from "#browser/document/schema.ts";
2
2
  import { fileUrl } from "#files/url.ts";
3
+ import { responseUrl } from "#responses/url.ts";
3
4
  import { keyed } from "./keyed.ts";
4
5
  import { Reference } from "./Reference.tsx";
5
6
  export function DocumentAttention({ document }: { readonly document: Document }) {
@@ -28,9 +29,7 @@ function Request({ document, index }: { readonly document: Document; readonly in
28
29
  document.metadata.attention?.filter((entry) => entry.id === request.id).length === 1
29
30
  ? `attention-request-${request.id}`
30
31
  : `attention-record-${index}`;
31
- const respond = document.requests.some((entry) => entry.id === request.id && entry.open)
32
- ? `#answer-card-${request.id}`
33
- : `/_board/respond?item=${encodeURIComponent(document.metadata.id ?? "")}&request=${encodeURIComponent(request.id)}`;
32
+ const respond = responseUrl(document.metadata.id ?? "", request.id);
34
33
  const targets = request.unblocks.map((target, targetIndex) => (
35
34
  <span key={target}>
36
35
  {targetIndex > 0 && ", "}
@@ -130,7 +130,7 @@ function projectionView(route: ProjectionRoute | RemainingRoute) {
130
130
  if (route === "board") {
131
131
  return "";
132
132
  }
133
- return route === "respond" ? "response" : route;
133
+ return route;
134
134
  }
135
135
 
136
136
  function failureStatus(missing: boolean, conflict: boolean) {
@@ -4,7 +4,6 @@ import { HandoffPage } from "./HandoffPage.tsx";
4
4
  import { LibraryMain } from "./LibraryMain.tsx";
5
5
  import type { MainLocation } from "./mainLocation.ts";
6
6
  import { ProjectionFrame } from "./ProjectionFrame.tsx";
7
- import { RespondPage } from "./RespondPage.tsx";
8
7
  import { ResultPage } from "./ResultPage.tsx";
9
8
  import type { RemainingRoute } from "./remainingRoute.ts";
10
9
  import type { ShellApi } from "./ShellApi.ts";
@@ -45,15 +44,6 @@ export function RemainingMain({
45
44
  );
46
45
  case "system":
47
46
  return <SystemMain api={api} onPage={onPage} url={url} />;
48
- case "respond":
49
- return (
50
- <ProjectionFrame
51
- {...options}
52
- atom={api.respond.query({ url }, routeQueryOptions)}
53
- location={() => ({ file: "Respond", status: 200, view: "response" })}
54
- render={(data) => <RespondPage data={data} />}
55
- />
56
- );
57
47
  case "handoff":
58
48
  return (
59
49
  <ProjectionFrame
@@ -1,5 +1,6 @@
1
1
  import type { ResultProjection } from "#browser/views/ResultProjection.ts";
2
2
  import { fileUrl } from "#files/url.ts";
3
+ import { responseUrl } from "#responses/url.ts";
3
4
  import { DocumentMetadata } from "./DocumentMetadata.tsx";
4
5
  import { ReadingPanel } from "./ReadingPanel.tsx";
5
6
  import { ResponseMarkdown } from "./ResponseMarkdown.tsx";
@@ -14,8 +15,7 @@ export function ResultPage({ data }: { readonly data: ResultProjection }) {
14
15
  );
15
16
  const links = actions.map((entry) => (
16
17
  <li key={entry.id}>
17
- <a href={`/_board/respond?item=${encodeURIComponent(document.identity ?? "")}&request=${encodeURIComponent(entry.id)}`}>Review {entry.id}</a> —{" "}
18
- {entry.reason}
18
+ <a href={responseUrl(document.identity ?? "", entry.id)}>Review {entry.id}</a> — {entry.reason}
19
19
  </li>
20
20
  ));
21
21
  return (
@@ -9,7 +9,6 @@ export interface ShellApi {
9
9
  readonly inbox: Pick<BrowserClient["api"]["inbox"], "query">;
10
10
  readonly library: Pick<BrowserClient["api"]["library"], "query">;
11
11
  readonly navigation: Pick<BrowserClient["api"]["navigation"], "query">;
12
- readonly respond: Pick<BrowserClient["api"]["respond"], "query">;
13
12
  readonly result: Pick<BrowserClient["api"]["result"], "query">;
14
13
  readonly search: Pick<BrowserClient["api"]["search"], "query">;
15
14
  readonly start: Pick<BrowserClient["api"]["start"], "query">;
@@ -12,6 +12,7 @@ import { Popover } from "#client/react/components/ui/Popover.tsx";
12
12
  import { authorLabel } from "#client/responses/authorLabel.ts";
13
13
  import { saveFailure } from "#client/saveFailure.ts";
14
14
  import { fileUrl } from "#files/url.ts";
15
+ import { responseUrl } from "#responses/url.ts";
15
16
  import { createResponse } from "./createResponse.ts";
16
17
  import { useFormClient } from "./FormContext.ts";
17
18
  import type { ShellApi } from "./ShellApi.ts";
@@ -19,8 +20,7 @@ import type { ShellApi } from "./ShellApi.ts";
19
20
  export function TodayOptions({ api, item }: { readonly api: ShellApi; readonly item: InboxProjection["you"][number] }) {
20
21
  const query = useQuery(documentQuery(api, fileUrl(item.file)), { keepPreviousData: true });
21
22
  const data = Option.getOrUndefined(query.data);
22
- const requestId = new URL(item.href, "http://127.0.0.1").searchParams.get("request");
23
- const request = data?.requests.find((entry) => entry.id === requestId && entry.open);
23
+ const request = data?.requests.find((entry) => entry.open && responseUrl(data.metadata.id ?? "", entry.id) === item.href);
24
24
  return data
25
25
  && !item.problem
26
26
  && request?.preview
@@ -1,4 +1,4 @@
1
- import { session } from "#client/entry.ts";
1
+ import { session } from "#client/session.ts";
2
2
  import { mountBoard } from "./mountBoard.tsx";
3
3
  import "#client/react/entry.css";
4
4
 
@@ -17,7 +17,7 @@ export async function loadBoardPage({
17
17
  location,
18
18
  abortController,
19
19
  }: Pick<LoaderFnContext, "location" | "abortController"> & { readonly context: { readonly shell?: RouterShell } }) {
20
- redirectRetired(location.pathname);
20
+ redirectLegacy(location.pathname, location.searchStr);
21
21
  if (!shell) {
22
22
  return undefined;
23
23
  }
@@ -25,7 +25,6 @@ export async function loadBoardPage({
25
25
  const url = location.pathname + location.searchStr;
26
26
  try {
27
27
  const navigation = await loadQuery(registry, navigationQuery(api), { abortController });
28
- await redirectResponse(shell, url, location.pathname, abortController);
29
28
  const projection = projectionRoute(location.pathname, navigation.home);
30
29
  if (projection) {
31
30
  await loadProjection(registry, api, projection, url, abortController);
@@ -55,22 +54,14 @@ export async function loadBoardPage({
55
54
  }
56
55
  }
57
56
 
58
- async function redirectResponse({ api, registry }: RouterShell, url: string, pathname: string, abortController: AbortController) {
59
- if (pathname !== "/_board/respond") {
60
- return;
61
- }
62
- const response = await loadRemaining(registry, api, "respond", url, abortController);
63
- if (response && "request" in response) {
64
- const item = response.document.identity ?? response.request.preview?.item ?? "";
65
- throw redirect({ href: `/_board/item/${encodeURIComponent(item)}/#answer-card-${encodeURIComponent(response.request.id)}`, replace: true });
66
- }
67
- }
68
-
69
57
  function mustRethrow(controller: AbortController, error: unknown) {
70
58
  return controller.signal.aborted || isRedirect(error);
71
59
  }
72
60
 
73
- function redirectRetired(pathname: string) {
61
+ function redirectLegacy(pathname: string, search: string) {
62
+ if (pathname === "/_board/respond") {
63
+ throw redirect({ href: pathname + search, reloadDocument: true });
64
+ }
74
65
  const retired: Readonly<Record<string, string>> = {
75
66
  "/_board/changes": "/_board/library?view=recently-changed",
76
67
  "/_board/diagnostics": "/_board/system",
@@ -22,8 +22,6 @@ export function loadRemaining(
22
22
  loadQuery(registry, api.diagnostics.query(undefined, routeQueryOptions), { abortController }),
23
23
  loadQuery(registry, api.fleetItems.query(undefined, routeQueryOptions), { abortController }),
24
24
  ]);
25
- case "respond":
26
- return loadQuery(registry, api.respond.query({ url }, routeQueryOptions), { abortController });
27
25
  case "handoff":
28
26
  return loadQuery(registry, api.handoff.query({ url }, routeQueryOptions), { abortController });
29
27
  case "result":
@@ -29,9 +29,6 @@ export function pageQueries(api: ShellApi, kind: string, url: string): readonly
29
29
  if (kind === "board") {
30
30
  return [api.board.query({ url }, routeQueryOptions)];
31
31
  }
32
- if (kind === "respond") {
33
- return [api.respond.query({ url }, routeQueryOptions)];
34
- }
35
32
  if (kind === "handoff") {
36
33
  return [api.handoff.query({ url }, routeQueryOptions)];
37
34
  }
@@ -2,7 +2,7 @@ export function readerLink(target: URL) {
2
2
  return (
3
3
  target.origin === location.origin
4
4
  && (target.pathname === "/"
5
- || /^\/_board\/(?:library|goals|system|diagnostics|handoff|result|respond|start|changes|inbox|overview|work)$/u.test(target.pathname)
5
+ || /^\/_board\/(?:library|goals|system|diagnostics|handoff|result|start|changes|inbox|overview|work)$/u.test(target.pathname)
6
6
  || target.pathname.startsWith("/_board/item/")
7
7
  || /\.md$/iu.test(target.pathname))
8
8
  );
@@ -8,8 +8,6 @@ export function remainingRoute(pathname: string) {
8
8
  return "system";
9
9
  case "/_board/start":
10
10
  return "start";
11
- case "/_board/respond":
12
- return "respond";
13
11
  case "/_board/handoff":
14
12
  return "handoff";
15
13
  case "/_board/result":
@@ -1,3 +1,3 @@
1
1
  export function responseUrl(item: string, request: string): string {
2
- return `/_board/respond?item=${encodeURIComponent(item)}&request=${encodeURIComponent(request)}`;
2
+ return `/_board/item/${encodeURIComponent(item)}/#${encodeURIComponent(`answer-card-${request}`)}`;
3
3
  }
@@ -17,7 +17,6 @@ export const PageUrl = Schema.String.check(
17
17
  "/_board/overview",
18
18
  "/_board/changes",
19
19
  "/_board/start",
20
- "/_board/respond",
21
20
  "/_board/handoff",
22
21
  "/_board/result",
23
22
  ].includes(path)
@@ -10,7 +10,7 @@ import { Library, LibraryInput } from "#browser/navigation/library.ts";
10
10
  import { FleetItemsProjection } from "#browser/views/FleetItemsProjection.ts";
11
11
  import { GoalsProjection } from "#browser/views/GoalsProjection.ts";
12
12
  import { ResultProjection } from "#browser/views/ResultProjection.ts";
13
- import { DiagnosticsProjection, HandoffProjection, RespondProjection, StartProjection } from "#browser/views/remaining.ts";
13
+ import { DiagnosticsProjection, HandoffProjection, StartProjection } from "#browser/views/remaining.ts";
14
14
  import { BoardProjection, InboxProjection, WorkProjection } from "#browser/views/schema.ts";
15
15
  import { handoffContract } from "#rpc/handoffContract.ts";
16
16
  import { responseContract } from "#rpc/responseContract.ts";
@@ -53,15 +53,13 @@ const ReadWork = query("work", {
53
53
  const ReadInbox = query("inbox", { reads: () => [workspace.list, index.list], rejections: { InternalServerError }, success: InboxProjection });
54
54
  const ReadServer = query("server", { reads: () => [], rejections: { ServiceUnavailable }, success: Server });
55
55
  const ReadDiagnostics = query("diagnostics", { reads: () => [workspace.list], rejections: { InternalServerError }, success: DiagnosticsProjection });
56
- const ReadFleetItems = query("fleetItems", { reads: () => [workspace.list, index.list], success: FleetItemsProjection });
56
+ const ReadFleetItems = query("fleetItems", {
57
+ reads: () => [workspace.list, index.list],
58
+ rejections: { InternalServerError },
59
+ success: FleetItemsProjection,
60
+ });
57
61
  const ReadGoals = query("goals", { reads: () => [workspace.list, index.list], rejections: { InternalServerError }, success: GoalsProjection });
58
62
  const ReadStart = query("start", { reads: () => [workspace.list, index.list], rejections: { InternalServerError }, success: StartProjection });
59
- const ReadRespond = query("respond", {
60
- payload: { url: PageUrl },
61
- reads: ({ url }) => [...pageKeys(url), index.list],
62
- rejections: documentRejections,
63
- success: RespondProjection,
64
- });
65
63
  const ReadHandoff = query("handoff", {
66
64
  payload: { url: PageUrl },
67
65
  reads: () => [workspace.list, index.list],
@@ -109,7 +107,6 @@ export const workContract = contract("work-board", {
109
107
  ReadFleetItems,
110
108
  ReadGoals,
111
109
  ReadStart,
112
- ReadRespond,
113
110
  ReadHandoff,
114
111
  ReadResult,
115
112
  ReadNavigation,
@@ -1,29 +1,20 @@
1
- import { Clock, Context, Effect, FileSystem, Path, Schema, Semaphore } from "effect";
1
+ import { Clock, Effect, FileSystem, Path, Schema, Semaphore } from "effect";
2
2
  import { defineService } from "@shivaedev/effect-service/define-service.ts";
3
3
  import { Conflict, Forbidden, PreconditionFailed, ServiceUnavailable } from "@shivaedev/platform/errors/taxonomy.ts";
4
4
  import { rpcBoundary } from "@shivaedev/platform/rpc-server/boundary.ts";
5
5
  import type { DraftInput } from "#browser/responses/schema.ts";
6
- import type { Changes } from "#server/files/changes.ts";
7
- import type { Snapshot } from "#server/search/snapshot.ts";
8
- import { awaitResponse } from "./await.ts";
9
- import { responseHistory } from "./history.ts";
10
- import { locate as locateQuestion } from "./locate.ts";
11
- import { makeResponse } from "./make.ts";
12
- import { publish } from "./publish.ts";
13
- import { readResponses } from "./read.ts";
14
- import { responseMarkdown } from "./records.ts";
15
- import { registerQuestion } from "./registerQuestion.ts";
16
- import { retryResponse } from "./retry.ts";
17
- import { validatedInput } from "./validatedInput.ts";
18
- import { validateSupersedes } from "./validateSupersedes.ts";
19
-
20
- export interface ResponseOptions {
21
- readonly changes: Changes;
22
- readonly enabled: boolean;
23
- readonly index: Effect.Effect<Snapshot, unknown>;
24
- readonly root: string;
25
- }
26
- export class ResponseConfig extends Context.Service<ResponseConfig, ResponseOptions>()("WorkBoard/ResponseConfig") {}
6
+ import { awaitResponse } from "#server/responses/await.ts";
7
+ import { responseHistory } from "#server/responses/history.ts";
8
+ import { locate as locateQuestion } from "#server/responses/locate.ts";
9
+ import { makeResponse } from "#server/responses/make.ts";
10
+ import { publish } from "#server/responses/publish.ts";
11
+ import { readResponses } from "#server/responses/read.ts";
12
+ import { responseMarkdown } from "#server/responses/records.ts";
13
+ import { registerQuestion } from "#server/responses/registerQuestion.ts";
14
+ import { retryResponse } from "#server/responses/retry.ts";
15
+ import { validatedInput } from "#server/responses/validatedInput.ts";
16
+ import { validateSupersedes } from "#server/responses/validateSupersedes.ts";
17
+ import { Workspace } from "#server/workspace.ts";
27
18
 
28
19
  function invalidResponse(cause: unknown): Conflict {
29
20
  return Schema.is(Conflict)(cause)
@@ -34,19 +25,20 @@ function invalidResponse(cause: unknown): Conflict {
34
25
  export const Responses = defineService({
35
26
  id: "WorkBoard/Responses",
36
27
  initialize: Effect.gen(function* () {
37
- return { fs: yield* FileSystem.FileSystem, lock: yield* Semaphore.make(1), options: yield* ResponseConfig };
28
+ const workspace = yield* Workspace;
29
+ return { fs: yield* FileSystem.FileSystem, lock: yield* Semaphore.make(1), options: yield* workspace.settings(), workspace };
38
30
  }),
39
- methods: ({ options, fs, lock }) => {
40
- const snapshot = options.index.pipe(
31
+ methods: ({ options, fs, lock, workspace }) => {
32
+ const snapshot = workspace.index().pipe(
41
33
  rpcBoundary(ServiceUnavailable, {
42
34
  message: "The workspace could not be indexed.",
43
35
  unexpected: (message) => new ServiceUnavailable({ message }),
44
36
  }),
45
37
  );
46
- const enabled = options.enabled
38
+ const enabled = options.writable
47
39
  ? Effect.void
48
40
  : Effect.fail(new Forbidden({ message: "Start with --responses to explicitly enable local question/response writes." }));
49
- const locate = locateQuestion(options.root, options.changes.realRoot, snapshot, fs);
41
+ const locate = locateQuestion(options.root, options.realRoot, snapshot, fs);
50
42
  const read = readResponses(snapshot);
51
43
  const register = registerQuestion(options, snapshot, locate, read);
52
44
  function record(draft: DraftInput) {
@@ -80,7 +72,7 @@ export const Responses = defineService({
80
72
  question,
81
73
  result.responses.reduce((time, saved) => Math.max(time, saved.response.recordedAt + 1), yield* Clock.currentTimeMillis),
82
74
  );
83
- yield* publish(options.root, options.changes.realRoot, response.id, responseMarkdown(response)).pipe(
75
+ yield* publish(options.root, options.realRoot, response.id, responseMarkdown(response)).pipe(
84
76
  Effect.ensuring(options.changes.refresh ?? Effect.void),
85
77
  );
86
78
  return response;
@@ -107,6 +99,6 @@ export const Responses = defineService({
107
99
  register: (input: Parameters<typeof register>[0]) => lock.withPermit(register(input)),
108
100
  };
109
101
  },
110
- requires: [ResponseConfig, FileSystem.FileSystem, Path.Path],
102
+ requires: [Workspace, FileSystem.FileSystem, Path.Path],
111
103
  });
112
104
  export type Responses = typeof Responses.Identifier;
@@ -86,6 +86,7 @@ export function inboxItems(snapshot: Snapshot) {
86
86
  id: id ?? document.file,
87
87
  problem,
88
88
  reason,
89
+ request: undefined,
89
90
  title: titles.get(document.file) ?? document.file,
90
91
  unblocks: [id ?? ""],
91
92
  },
@@ -12,7 +12,7 @@ export function attentionProjection(snapshot: Snapshot): InboxProjection {
12
12
  return cached;
13
13
  }
14
14
  const requests = requestInbox(snapshot);
15
- function project({ document, unblocks: _unblocks, ...item }: ReturnType<typeof inboxItems>[number]) {
15
+ function project({ document, request: _request, unblocks: _unblocks, ...item }: ReturnType<typeof inboxItems>[number]) {
16
16
  return { ...item, file: document.file };
17
17
  }
18
18
  const you = inboxItems(snapshot).map(project);
@@ -28,6 +28,7 @@ export function requestInbox(snapshot: Snapshot) {
28
28
  modified: snapshot.modified.get(entry.document.file),
29
29
  problem: undefined,
30
30
  reason: entry.request.managed ? `Managed decision: ${entry.request.reason}` : entry.request.reason,
31
+ request: entry.request.id,
31
32
  responseFrom: entry.request.responseFrom,
32
33
  title: (target ? titles.get(target.file) : undefined) ?? entry.title,
33
34
  unblocks: entry.request.unblocks,
@@ -1,16 +1,15 @@
1
1
  import { Context, Effect, FileSystem, Layer, Path, type PlatformError } from "effect";
2
- import { DiagnosticsSources } from "#diagnostics/source.ts";
2
+ import { DiagnosticsConfig, DiagnosticsSources } from "#diagnostics/source.ts";
3
3
  import type { BoardOptions } from "#server/board.ts";
4
4
  import { boardServices } from "#server/boardServices.ts";
5
5
  import { daemonDiagnostics } from "#server/diagnostics/daemon.ts";
6
- import { readDocument } from "#server/document/read.ts";
7
6
  import { watchChanges } from "#server/files/changes.ts";
8
7
  import { homeIn } from "#server/files/home.ts";
9
8
  import { checkBrowserAssets, mermaidRoot } from "#server/http/assets.ts";
10
9
  import { Highlighter } from "#server/render/Highlighter.ts";
11
10
  import type { HandlerOptions } from "#server/rpc/HandlerOptions.ts";
12
- import { handlers } from "#server/rpc/handlers.ts";
13
11
  import { type Snapshot, searchSnapshot } from "#server/search/snapshot.ts";
12
+ import { Workspace, WorkspaceConfig } from "#server/workspace.ts";
14
13
 
15
14
  export const boardRuntime = Effect.fn("WorkBoard.boardRuntime")(function* (options: BoardOptions, server?: HandlerOptions["server"]) {
16
15
  const home = yield* homeIn(options.root, options.home);
@@ -28,30 +27,26 @@ export const boardRuntime = Effect.fn("WorkBoard.boardRuntime")(function* (optio
28
27
  );
29
28
  const provided = yield* Effect.serviceOption(DiagnosticsSources);
30
29
  const services = yield* Layer.build(
31
- boardServices(
32
- { changes, enabled: options.responses === true, index: Effect.provideContext(index, platform), root: options.root },
33
- provided._tag === "Some" ? { _tag: "Sources", sources: provided.value } : { _tag: "Read", read: daemonDiagnostics(options.fleetPort) },
34
- ),
35
- );
36
- const context = Context.merge(platform, services);
37
- const rpcHandlers = yield* handlers({
38
- ...(options.fleetPort === undefined ? {} : { fleetPort: options.fleetPort }),
39
- changes,
40
- document: (url) =>
41
- Effect.provideContext(
42
- readDocument({
30
+ boardServices({
31
+ ...(options.fleetPort === undefined ? {} : { fleetPort: options.fleetPort }),
32
+ ...(server === undefined ? {} : { server }),
33
+ }).pipe(
34
+ Layer.provideMerge(Workspace.layer),
35
+ Layer.provide([
36
+ Layer.succeed(WorkspaceConfig, {
43
37
  changes,
44
38
  home,
45
- index: Effect.provideContext(index, context),
39
+ index: Effect.provideContext(index, platform),
46
40
  root: options.root,
47
41
  writable: options.responses === true,
48
- })(url),
49
- context,
50
- ),
51
- home,
52
- index: Effect.provideContext(index, context),
53
- ...(server === undefined ? {} : { server }),
54
- writable: options.responses === true,
55
- }).pipe(Effect.provideContext(context));
56
- return { changes, context, home, index, mermaid, rpcHandlers };
42
+ }),
43
+ Layer.succeed(
44
+ DiagnosticsConfig,
45
+ provided._tag === "Some" ? { _tag: "Sources", sources: provided.value } : { _tag: "Read", read: daemonDiagnostics(options.fleetPort) },
46
+ ),
47
+ ]),
48
+ ),
49
+ );
50
+ const context = Context.merge(platform, services);
51
+ return { changes, context, home, index, mermaid, rpcHandlers: Layer.succeedContext(services) };
57
52
  });
@@ -1,10 +1,10 @@
1
- import { type Context, Layer } from "effect";
2
- import { Diagnostics, DiagnosticsConfig } from "#diagnostics/source.ts";
1
+ import { Layer } from "effect";
2
+ import { Diagnostics } from "#diagnostics/source.ts";
3
3
  import { HandoffsService } from "#server/handoffs/Service.ts";
4
- import { ResponseConfig, type ResponseOptions, Responses } from "#server/responses/service.ts";
4
+ import { Responses } from "#server/Responses.ts";
5
+ import type { HandlerOptions } from "#server/rpc/HandlerOptions.ts";
6
+ import { handlers } from "#server/rpc/handlers.ts";
5
7
 
6
- export function boardServices(options: ResponseOptions, diagnostics: Context.Service.Shape<typeof DiagnosticsConfig>) {
7
- return Layer.mergeAll(Responses.layer, HandoffsService.layer, Diagnostics.layer).pipe(
8
- Layer.provide(Layer.merge(Layer.succeed(ResponseConfig, options), Layer.succeed(DiagnosticsConfig, diagnostics))),
9
- );
8
+ export function boardServices(options: HandlerOptions = {}) {
9
+ return Layer.unwrap(handlers(options)).pipe(Layer.provideMerge(Layer.mergeAll(Responses.layer, HandoffsService.layer, Diagnostics.layer)));
10
10
  }
@@ -2,24 +2,17 @@ import { Effect, FileSystem } from "effect";
2
2
  import { Conflict, NotFound, ServiceUnavailable } from "@shivaedev/platform/errors/taxonomy.ts";
3
3
  import type { Document } from "#browser/document/schema.ts";
4
4
  import { currentDocument } from "#server/currentDocument.ts";
5
- import type { Changes } from "#server/files/changes.ts";
6
5
  import type { MarkdownFile } from "#server/files/list.ts";
7
6
  import { markdownPath } from "#server/files/markdownPath.ts";
7
+ import { locate } from "#server/responses/locate.ts";
8
8
  import { readingActions } from "#server/responses/readingActions.ts";
9
9
  import type { Snapshot } from "#server/search/snapshot.ts";
10
+ import type { WorkspaceSettings } from "#server/workspace.ts";
10
11
  import { answerBody } from "./answerBody.ts";
11
12
  import { readingContext } from "./readingContext.ts";
12
13
  import { documentRequests } from "./requests.ts";
13
14
  import { documentTargets } from "./targets.ts";
14
15
 
15
- export interface DocumentOptions {
16
- readonly changes: Changes;
17
- readonly home: string | undefined;
18
- readonly index: Effect.Effect<Snapshot, unknown>;
19
- readonly root: string;
20
- readonly writable: boolean;
21
- }
22
-
23
16
  const selected = Effect.fn("WorkBoard.selected")(function* (
24
17
  url: string,
25
18
  snapshot: Snapshot,
@@ -45,12 +38,11 @@ const selected = Effect.fn("WorkBoard.selected")(function* (
45
38
  return { file, item };
46
39
  });
47
40
 
48
- export function readDocument({ changes, home, index, root, writable }: DocumentOptions) {
49
- return Effect.fn("WorkBoard.document")(function* (url: string) {
50
- const snapshot = yield* index;
41
+ export function readDocument({ changes, home, root, realRoot, writable }: WorkspaceSettings) {
42
+ return Effect.fn("WorkBoard.document")(function* (url: string, snapshot: Snapshot) {
51
43
  const files = yield* changes.files;
52
44
  const { file, item } = yield* selected(url, snapshot, files, home);
53
- const real = yield* markdownPath(root, changes.realRoot, file.path);
45
+ const real = yield* markdownPath(root, realRoot, file.path);
54
46
  if (!real) {
55
47
  return yield* Effect.fail(new NotFound({ message: "The document is missing." }));
56
48
  }
@@ -65,7 +57,7 @@ export function readDocument({ changes, home, index, root, writable }: DocumentO
65
57
  if (item && identity !== item) {
66
58
  return yield* Effect.fail(new NotFound({ message: "The document is missing." }));
67
59
  }
68
- const questionActions = yield* readingActions(document, source, writable, identity);
60
+ const questionActions = yield* readingActions(document, source, writable, identity, locate(root, realRoot, Effect.succeed(snapshot), fs));
69
61
  const titles = new Map(current.snapshot.entries.filter((entry) => entry.kind === "document").map((entry) => [entry.file, entry.title]));
70
62
  const reading = readingContext(document, model, file.path === home, titles);
71
63
  const targets = documentTargets(
@@ -1,6 +1,5 @@
1
1
  import { ServiceUnavailable } from "@shivaedev/platform/errors/taxonomy.ts";
2
2
  import type { DocumentRequest } from "#browser/document/schema.ts";
3
- import { responseUrl } from "#responses/url.ts";
4
3
  import { authoredQuestion, openItem } from "#server/attention/inbox.ts";
5
4
  import { requestInbox } from "#server/attention/requestInbox.ts";
6
5
  import { attentionRequests } from "#server/attention/requests.ts";
@@ -84,6 +83,6 @@ function requestOpenInSource(document: MetadataDocument, id: string): boolean {
84
83
 
85
84
  function activeSource(document: MetadataDocument, snapshot: Snapshot, item: string, id: string) {
86
85
  const managed = document.parsed.fields.attention?.find((entry) => entry.id === id)?.managed;
87
- const eligible = !managed || requestInbox(snapshot).items.some((entry) => entry.href === responseUrl(item, id));
86
+ const eligible = !managed || requestInbox(snapshot).items.some((entry) => entry.document.file === document.file && entry.request === id);
88
87
  return eligible ? requestSource(snapshot, item, id) : undefined;
89
88
  }