@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.
- package/CHANGELOG.md +27 -0
- package/README.md +27 -25
- package/assets/react.css +1 -1
- package/assets/react.js +37 -36
- package/dist/browser/responses/cardContext.d.ts +8 -1
- package/dist/browser/responses/cardContext.d.ts.map +1 -1
- package/dist/browser/responses/cardContext.js +12 -7
- package/dist/browser/responses/cardContext.js.map +1 -1
- package/dist/browser/views/now.d.ts +5 -0
- package/dist/browser/views/now.d.ts.map +1 -1
- package/dist/browser/views/now.js +38 -2
- package/dist/browser/views/now.js.map +1 -1
- package/dist/browser/views/schema.d.ts +11 -0
- package/dist/browser/views/schema.d.ts.map +1 -1
- package/dist/browser/views/schema.js +1 -0
- package/dist/browser/views/schema.js.map +1 -1
- package/dist/client/document/View.js +1 -1
- package/dist/client/document/View.js.map +1 -1
- package/dist/client/formStatus.d.ts +2 -2
- package/dist/client/formStatus.d.ts.map +1 -1
- package/dist/client/formStatus.js +2 -2
- package/dist/client/formStatus.js.map +1 -1
- package/dist/client/react/AnswerContext.d.ts.map +1 -1
- package/dist/client/react/AnswerContext.js +5 -2
- package/dist/client/react/AnswerContext.js.map +1 -1
- package/dist/client/react/AnswerForm.d.ts.map +1 -1
- package/dist/client/react/AnswerForm.js +6 -2
- package/dist/client/react/AnswerForm.js.map +1 -1
- package/dist/client/react/AnswerMarkdown.d.ts +6 -0
- package/dist/client/react/AnswerMarkdown.d.ts.map +1 -0
- package/dist/client/react/AnswerMarkdown.js +7 -0
- package/dist/client/react/AnswerMarkdown.js.map +1 -0
- package/dist/client/react/AnswerMore.d.ts.map +1 -1
- package/dist/client/react/AnswerMore.js +2 -1
- package/dist/client/react/AnswerMore.js.map +1 -1
- package/dist/client/react/AnswerPanel.d.ts.map +1 -1
- package/dist/client/react/AnswerPanel.js +3 -2
- package/dist/client/react/AnswerPanel.js.map +1 -1
- package/dist/client/react/AnswerPrompts.d.ts.map +1 -1
- package/dist/client/react/AnswerPrompts.js +16 -10
- package/dist/client/react/AnswerPrompts.js.map +1 -1
- package/dist/client/react/BoardShell.js +2 -2
- package/dist/client/react/BoardShell.js.map +1 -1
- package/dist/client/react/DocumentMain.d.ts.map +1 -1
- package/dist/client/react/DocumentMain.js +2 -2
- package/dist/client/react/DocumentMain.js.map +1 -1
- package/dist/client/react/FleetMain.js +1 -1
- package/dist/client/react/FleetMain.js.map +1 -1
- package/dist/client/react/FormField.js +1 -1
- package/dist/client/react/FormField.js.map +1 -1
- package/dist/client/react/GoalPage.js +2 -2
- package/dist/client/react/GoalPage.js.map +1 -1
- package/dist/client/react/GoalProgressHeader.js +2 -2
- package/dist/client/react/GoalProgressHeader.js.map +1 -1
- package/dist/client/react/Header.d.ts.map +1 -1
- package/dist/client/react/Header.js +3 -2
- package/dist/client/react/Header.js.map +1 -1
- package/dist/client/react/LibraryContents.js +1 -1
- package/dist/client/react/LibraryContents.js.map +1 -1
- package/dist/client/react/LibraryMain.d.ts.map +1 -1
- package/dist/client/react/LibraryMain.js +1 -1
- package/dist/client/react/LibraryMain.js.map +1 -1
- package/dist/client/react/LibraryPage.js +2 -2
- package/dist/client/react/LibraryPage.js.map +1 -1
- package/dist/client/react/LiveControls.js +1 -1
- package/dist/client/react/LiveControls.js.map +1 -1
- package/dist/client/react/ProjectionMain.d.ts.map +1 -1
- package/dist/client/react/ProjectionMain.js +1 -1
- package/dist/client/react/ProjectionMain.js.map +1 -1
- package/dist/client/react/PromptOption.d.ts.map +1 -1
- package/dist/client/react/PromptOption.js +7 -5
- package/dist/client/react/PromptOption.js.map +1 -1
- package/dist/client/react/ReadingHistory.js +1 -1
- package/dist/client/react/ReadingHistory.js.map +1 -1
- package/dist/client/react/RemainingMain.d.ts.map +1 -1
- package/dist/client/react/RemainingMain.js +1 -1
- package/dist/client/react/RemainingMain.js.map +1 -1
- package/dist/client/react/ResponseHistory.d.ts.map +1 -1
- package/dist/client/react/ResponseHistory.js +11 -5
- package/dist/client/react/ResponseHistory.js.map +1 -1
- package/dist/client/react/SearchPalette.js +1 -1
- package/dist/client/react/SearchPalette.js.map +1 -1
- package/dist/client/react/Sidebar.d.ts.map +1 -1
- package/dist/client/react/Sidebar.js +9 -2
- package/dist/client/react/Sidebar.js.map +1 -1
- package/dist/client/react/SystemFleetLane.d.ts.map +1 -1
- package/dist/client/react/SystemFleetLane.js +2 -1
- package/dist/client/react/SystemFleetLane.js.map +1 -1
- package/dist/client/react/SystemHealth.d.ts +2 -1
- package/dist/client/react/SystemHealth.d.ts.map +1 -1
- package/dist/client/react/SystemHealth.js +3 -2
- package/dist/client/react/SystemHealth.js.map +1 -1
- package/dist/client/react/SystemMain.d.ts.map +1 -1
- package/dist/client/react/SystemMain.js +6 -2
- package/dist/client/react/SystemMain.js.map +1 -1
- package/dist/client/react/SystemPage.d.ts +2 -1
- package/dist/client/react/SystemPage.d.ts.map +1 -1
- package/dist/client/react/SystemPage.js +2 -2
- package/dist/client/react/SystemPage.js.map +1 -1
- package/dist/client/react/TodayNeeds.d.ts +5 -0
- package/dist/client/react/TodayNeeds.d.ts.map +1 -1
- package/dist/client/react/TodayNeeds.js +6 -3
- package/dist/client/react/TodayNeeds.js.map +1 -1
- package/dist/client/react/TodayOptionButton.d.ts +14 -0
- package/dist/client/react/TodayOptionButton.d.ts.map +1 -0
- package/dist/client/react/TodayOptionButton.js +10 -0
- package/dist/client/react/TodayOptionButton.js.map +1 -0
- package/dist/client/react/TodayOptions.d.ts.map +1 -1
- package/dist/client/react/TodayOptions.js +18 -14
- package/dist/client/react/TodayOptions.js.map +1 -1
- package/dist/client/react/TodayPage.d.ts.map +1 -1
- package/dist/client/react/TodayPage.js +20 -5
- package/dist/client/react/TodayPage.js.map +1 -1
- package/dist/client/react/WorkCard.d.ts.map +1 -1
- package/dist/client/react/WorkCard.js +7 -2
- package/dist/client/react/WorkCard.js.map +1 -1
- package/dist/client/react/WorkLane.d.ts.map +1 -1
- package/dist/client/react/WorkLane.js +1 -1
- package/dist/client/react/WorkLane.js.map +1 -1
- package/dist/client/react/WorkNow.js +1 -1
- package/dist/client/react/WorkNow.js.map +1 -1
- package/dist/client/react/activeRead.js +1 -1
- package/dist/client/react/activeRead.js.map +1 -1
- package/dist/client/react/destinationName.d.ts +2 -0
- package/dist/client/react/destinationName.d.ts.map +1 -0
- package/dist/client/react/destinationName.js +4 -0
- package/dist/client/react/destinationName.js.map +1 -0
- package/dist/client/react/loadBoardPage.d.ts.map +1 -1
- package/dist/client/react/loadBoardPage.js +2 -2
- package/dist/client/react/loadBoardPage.js.map +1 -1
- package/dist/client/react/loadProjection.d.ts +135 -135
- package/dist/client/react/loadProjection.d.ts.map +1 -1
- package/dist/client/react/loadProjection.js +2 -2
- package/dist/client/react/loadProjection.js.map +1 -1
- package/dist/client/react/loadRemaining.d.ts +184 -184
- package/dist/client/react/loadRemaining.d.ts.map +1 -1
- package/dist/client/react/loadRemaining.js +2 -2
- package/dist/client/react/loadRemaining.js.map +1 -1
- package/dist/client/react/pageQueries.js +1 -1
- package/dist/client/react/pageQueries.js.map +1 -1
- package/dist/client/react/resolvedDocumentFailure.d.ts.map +1 -0
- package/dist/client/react/resolvedDocumentFailure.js.map +1 -0
- package/dist/client/react/routeQuery.d.ts.map +1 -0
- package/dist/client/react/routeQuery.js.map +1 -0
- package/dist/client/react/useHandoffForm.js +1 -1
- package/dist/client/react/useHandoffForm.js.map +1 -1
- package/dist/client/react/useProjectionData.d.ts +1 -1
- package/dist/client/react/useProjectionData.js +1 -1
- package/dist/client/react/useProjectionData.js.map +1 -1
- package/dist/client/react/useResponseForm.d.ts.map +1 -1
- package/dist/client/react/useResponseForm.js +3 -3
- package/dist/client/react/useResponseForm.js.map +1 -1
- package/dist/client/responses/summary.d.ts +1 -0
- package/dist/client/responses/summary.d.ts.map +1 -1
- package/dist/client/responses/summary.js +12 -1
- package/dist/client/responses/summary.js.map +1 -1
- package/dist/diagnostics/source.js +1 -1
- package/dist/diagnostics/source.js.map +1 -1
- package/dist/rpc/contract.d.ts +1951 -1918
- package/dist/rpc/contract.d.ts.map +1 -1
- package/dist/server/Responses.js +2 -2
- package/dist/server/Responses.js.map +1 -1
- package/dist/server/attention/inbox.d.ts +13 -2
- package/dist/server/attention/inbox.d.ts.map +1 -1
- package/dist/server/attention/inbox.js +44 -27
- package/dist/server/attention/inbox.js.map +1 -1
- package/dist/server/attention/managedRequestReceipt.d.ts +10 -0
- package/dist/server/attention/managedRequestReceipt.d.ts.map +1 -1
- package/dist/server/attention/managedRequestReceipt.js +29 -10
- package/dist/server/attention/managedRequestReceipt.js.map +1 -1
- package/dist/server/attention/projection.d.ts.map +1 -1
- package/dist/server/attention/projection.js +2 -1
- package/dist/server/attention/projection.js.map +1 -1
- package/dist/server/attention/requestInbox.d.ts +3 -3
- package/dist/server/attention/requestInbox.js +2 -2
- package/dist/server/attention/requestInbox.js.map +1 -1
- package/dist/server/attention/requests.d.ts +1 -0
- package/dist/server/attention/requests.d.ts.map +1 -1
- package/dist/server/attention/requests.js +7 -3
- package/dist/server/attention/requests.js.map +1 -1
- package/dist/server/board.js +1 -1
- package/dist/server/board.js.map +1 -1
- package/dist/server/document/answerBody.d.ts.map +1 -1
- package/dist/server/document/answerBody.js +9 -1
- package/dist/server/document/answerBody.js.map +1 -1
- package/dist/server/document/reviewedWork.d.ts +3 -0
- package/dist/server/document/reviewedWork.d.ts.map +1 -0
- package/dist/server/document/reviewedWork.js +23 -0
- package/dist/server/document/reviewedWork.js.map +1 -0
- package/dist/server/files/changes.d.ts.map +1 -1
- package/dist/server/files/changes.js +1 -1
- package/dist/server/files/changes.js.map +1 -1
- package/dist/server/files/watchDirectories.d.ts +1 -1
- package/dist/server/files/watchDirectories.d.ts.map +1 -1
- package/dist/server/files/watchDirectories.js +71 -12
- package/dist/server/files/watchDirectories.js.map +1 -1
- package/dist/server/handoffs/Service.js +2 -2
- package/dist/server/handoffs/Service.js.map +1 -1
- package/dist/server/handoffs/source.js +1 -1
- package/dist/server/handoffs/source.js.map +1 -1
- package/dist/server/http/boardRoutes.js +1 -1
- package/dist/server/http/boardRoutes.js.map +1 -1
- package/dist/server/mcp/client.d.ts +11 -0
- package/dist/server/mcp/client.d.ts.map +1 -1
- package/dist/server/mcp/queries.d.ts +2851 -2818
- package/dist/server/mcp/queries.d.ts.map +1 -1
- package/dist/server/packageInfo.d.ts +1 -1
- package/dist/server/packageInfo.js +1 -1
- package/dist/server/render/Highlighter.js +1 -1
- package/dist/server/render/Highlighter.js.map +1 -1
- package/dist/server/responses/locate.js +1 -1
- package/dist/server/responses/locate.js.map +1 -1
- package/dist/server/rpc/handlers.js +2 -2
- package/dist/server/rpc/handlers.js.map +1 -1
- package/dist/server/serve.js +1 -1
- package/dist/server/serve.js.map +1 -1
- package/dist/server/views/projectionQueries.d.ts +11 -0
- package/dist/server/views/projectionQueries.d.ts.map +1 -1
- package/dist/server/workspace.js +1 -1
- package/dist/server/workspace.js.map +1 -1
- package/docs/delivery/README.md +2 -2
- package/package.json +18 -362
- package/src/browser/responses/cardContext.ts +13 -7
- package/src/browser/views/now.ts +42 -2
- package/src/browser/views/schema.ts +1 -0
- package/src/client/document/View.ts +1 -1
- package/src/client/formStatus.ts +3 -3
- package/src/client/react/AnswerContext.tsx +9 -4
- package/src/client/react/AnswerForm.tsx +25 -6
- package/src/client/react/AnswerMarkdown.tsx +15 -0
- package/src/client/react/AnswerMore.tsx +58 -46
- package/src/client/react/AnswerPanel.tsx +14 -8
- package/src/client/react/AnswerPrompts.tsx +33 -18
- package/src/client/react/BoardShell.tsx +2 -2
- package/src/client/react/DocumentMain.tsx +2 -2
- package/src/client/react/FleetMain.tsx +1 -1
- package/src/client/react/FormField.tsx +1 -1
- package/src/client/react/GoalPage.tsx +2 -2
- package/src/client/react/GoalProgressHeader.tsx +2 -2
- package/src/client/react/Header.tsx +3 -2
- package/src/client/react/LibraryContents.tsx +1 -1
- package/src/client/react/LibraryMain.tsx +1 -1
- package/src/client/react/LibraryPage.tsx +2 -2
- package/src/client/react/LiveControls.tsx +1 -1
- package/src/client/react/ProjectionMain.tsx +1 -1
- package/src/client/react/PromptOption.tsx +39 -24
- package/src/client/react/ReadingHistory.tsx +1 -1
- package/src/client/react/RemainingMain.tsx +1 -1
- package/src/client/react/ResponseHistory.tsx +53 -14
- package/src/client/react/SearchPalette.tsx +1 -1
- package/src/client/react/Sidebar.tsx +10 -2
- package/src/client/react/SystemFleetLane.tsx +2 -1
- package/src/client/react/SystemHealth.tsx +24 -4
- package/src/client/react/SystemMain.tsx +6 -2
- package/src/client/react/SystemPage.tsx +8 -2
- package/src/client/react/TodayNeeds.tsx +21 -6
- package/src/client/react/TodayOptionButton.tsx +35 -0
- package/src/client/react/TodayOptions.tsx +42 -33
- package/src/client/react/TodayPage.tsx +36 -7
- package/src/client/react/WorkCard.tsx +16 -8
- package/src/client/react/WorkLane.tsx +8 -2
- package/src/client/react/WorkNow.tsx +1 -1
- package/src/client/react/activeRead.ts +1 -1
- package/src/client/react/destinationName.ts +3 -0
- package/src/client/react/entry.css +0 -6
- package/src/client/react/loadBoardPage.ts +2 -2
- package/src/client/react/loadProjection.ts +2 -2
- package/src/client/react/loadRemaining.ts +2 -2
- package/src/client/react/pageQueries.ts +1 -1
- package/src/client/react/useHandoffForm.ts +1 -1
- package/src/client/react/useProjectionData.ts +1 -1
- package/src/client/react/useResponseForm.ts +5 -3
- package/src/client/responses/summary.ts +13 -1
- package/src/diagnostics/source.ts +1 -1
- package/src/server/Responses.ts +2 -2
- package/src/server/attention/inbox.ts +51 -27
- package/src/server/attention/managedRequestReceipt.ts +41 -10
- package/src/server/attention/projection.ts +5 -2
- package/src/server/attention/requestInbox.ts +2 -2
- package/src/server/attention/requests.ts +7 -2
- package/src/server/board.ts +1 -1
- package/src/server/document/answerBody.ts +10 -1
- package/src/server/document/reviewedWork.ts +24 -0
- package/src/server/files/changes.ts +1 -0
- package/src/server/files/watchDirectories.ts +90 -27
- package/src/server/handoffs/Service.ts +2 -2
- package/src/server/handoffs/source.ts +1 -1
- package/src/server/http/boardRoutes.ts +1 -1
- package/src/server/packageInfo.ts +1 -1
- package/src/server/render/Highlighter.ts +1 -1
- package/src/server/responses/locate.ts +1 -1
- package/src/server/rpc/handlers.ts +2 -2
- package/src/server/serve.ts +1 -1
- package/src/server/workspace.ts +1 -1
- package/dist/client/page/resolvedDocumentFailure.d.ts.map +0 -1
- package/dist/client/page/resolvedDocumentFailure.js.map +0 -1
- package/dist/client/page/routeQuery.d.ts.map +0 -1
- package/dist/client/page/routeQuery.js.map +0 -1
- /package/dist/client/{page → react}/resolvedDocumentFailure.d.ts +0 -0
- /package/dist/client/{page → react}/resolvedDocumentFailure.js +0 -0
- /package/dist/client/{page → react}/routeQuery.d.ts +0 -0
- /package/dist/client/{page → react}/routeQuery.js +0 -0
- /package/src/client/{page → react}/resolvedDocumentFailure.ts +0 -0
- /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/
|
|
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 : "
|
|
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
|
|
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
|
|
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">{
|
|
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">
|
|
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-
|
|
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
|
|
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
|
|
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> }) {
|
|
@@ -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/
|
|
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/
|
|
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/
|
|
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 "
|
|
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/
|
|
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) =>
|
|
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
|
|
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/
|
|
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
|
|
package/src/server/Responses.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Clock, Effect, FileSystem, Path, Schema, Semaphore } from "effect";
|
|
2
|
-
import { defineService } from "@shivaedev/effect-service/
|
|
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
|
-
|
|
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
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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
|
-
|
|
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
|
|
99
|
-
return [...ordinary, ...you].sort(
|
|
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
|
-
|
|
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
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
const
|
|
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
|
-
!
|
|
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(
|
|
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
|
|
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:
|
|
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 {
|
|
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
|
},
|
package/src/server/board.ts
CHANGED
|
@@ -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
|
+
}
|