@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,8 +1,8 @@
|
|
|
1
1
|
import { Option } from "effect";
|
|
2
2
|
import { useState } from "react";
|
|
3
|
-
import { useQuery } from "@shivaedev/effect-react/
|
|
4
|
-
import { routeQueryOptions } from "#client/page/routeQuery.ts";
|
|
3
|
+
import { useQuery } from "@shivaedev/effect-react/client/resultState.ts";
|
|
5
4
|
import { LibraryContents } from "./LibraryContents.tsx";
|
|
5
|
+
import { routeQueryOptions } from "./routeQuery.ts";
|
|
6
6
|
import type { ShellApi } from "./ShellApi.ts";
|
|
7
7
|
|
|
8
8
|
export function LibraryPage({ api, url = "/_board/library" }: { readonly api: ShellApi; readonly url?: string }) {
|
|
@@ -2,7 +2,7 @@ import { useAtomValue } from "@effect/atom-react";
|
|
|
2
2
|
import { Option } from "effect";
|
|
3
3
|
import * as Atom from "effect/reactivity/Atom";
|
|
4
4
|
import { useMemo } from "react";
|
|
5
|
-
import { useQuery } from "@shivaedev/effect-react/
|
|
5
|
+
import { useQuery } from "@shivaedev/effect-react/client/resultState.ts";
|
|
6
6
|
import type { BrowserClient } from "#client/client.ts";
|
|
7
7
|
import { activeRead } from "./activeRead.ts";
|
|
8
8
|
import { liveStatus } from "./liveStatus.ts";
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { documentSourceId } from "#client/document/layout.ts";
|
|
2
|
-
import { routeQueryOptions } from "#client/page/routeQuery.ts";
|
|
3
2
|
import { HomeBoard } from "./HomeBoard.tsx";
|
|
4
3
|
import type { MainLocation } from "./mainLocation.ts";
|
|
5
4
|
import { ProjectionFrame } from "./ProjectionFrame.tsx";
|
|
6
5
|
import type { ProjectionRoute } from "./projectionRoute.ts";
|
|
6
|
+
import { routeQueryOptions } from "./routeQuery.ts";
|
|
7
7
|
import type { ShellApi } from "./ShellApi.ts";
|
|
8
8
|
import { TodayPage } from "./TodayPage.tsx";
|
|
9
9
|
import { WorkPage } from "./WorkPage.tsx";
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { useId } from "react";
|
|
2
2
|
import type { Document } from "#browser/document/schema.ts";
|
|
3
|
-
import {
|
|
3
|
+
import { optionText } from "#browser/responses/cardContext.ts";
|
|
4
4
|
import type { Prompt } from "#browser/responses/Prompt.ts";
|
|
5
|
-
import {
|
|
5
|
+
import { Badge } from "#client/react/components/ui/Badge.tsx";
|
|
6
|
+
import { cn } from "#client/react/lib/cn.ts";
|
|
7
|
+
|
|
6
8
|
export function PromptOption({
|
|
7
9
|
document,
|
|
8
10
|
prompt,
|
|
@@ -20,37 +22,50 @@ export function PromptOption({
|
|
|
20
22
|
}) {
|
|
21
23
|
const source = document.metadata.options?.[choice];
|
|
22
24
|
const id = useId();
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
data-option-source={option.source}
|
|
27
|
-
hidden
|
|
28
|
-
id={id}
|
|
29
|
-
name={`choice-${prompt.id}`}
|
|
30
|
-
onChange={(event) => choose(option.id, event.currentTarget.checked)}
|
|
31
|
-
onInput={(event) => choose(option.id, event.currentTarget.checked)}
|
|
32
|
-
type={prompt.mode === "one" ? "radio" : "checkbox"}
|
|
33
|
-
value={option.id}
|
|
34
|
-
/>
|
|
35
|
-
);
|
|
36
|
-
const label = source?.label ?? optionLabel(option.id, option.source, document.requests.find((request) => request.open)?.preview?.reason ?? "");
|
|
25
|
+
const authored = optionText(option.id, option.source);
|
|
26
|
+
const label = source?.label ?? authored.label;
|
|
27
|
+
const description = source?.description ?? authored.description;
|
|
37
28
|
return (
|
|
38
29
|
<div>
|
|
39
|
-
|
|
30
|
+
<input
|
|
31
|
+
checked={checked}
|
|
32
|
+
data-option-source={option.source}
|
|
33
|
+
hidden
|
|
34
|
+
id={id}
|
|
35
|
+
name={`choice-${prompt.id}`}
|
|
36
|
+
onChange={(event) => choose(option.id, event.currentTarget.checked)}
|
|
37
|
+
onInput={(event) => choose(option.id, event.currentTarget.checked)}
|
|
38
|
+
type={prompt.mode === "one" ? "radio" : "checkbox"}
|
|
39
|
+
value={option.id}
|
|
40
|
+
/>
|
|
40
41
|
<button
|
|
41
42
|
aria-pressed={checked}
|
|
42
|
-
className=
|
|
43
|
+
className={cn(
|
|
44
|
+
"question-choice flex w-full items-start gap-3 rounded-lg border bg-background px-4 py-3 text-left transition-colors hover:bg-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
45
|
+
checked && "border-primary bg-accent",
|
|
46
|
+
)}
|
|
43
47
|
data-choice={option.id}
|
|
44
48
|
onClick={() => choose(option.id, prompt.mode !== "many" || !checked)}
|
|
45
49
|
type="button"
|
|
46
50
|
>
|
|
47
|
-
<
|
|
48
|
-
|
|
49
|
-
{
|
|
50
|
-
|
|
51
|
-
|
|
51
|
+
<span
|
|
52
|
+
aria-hidden="true"
|
|
53
|
+
className={cn(
|
|
54
|
+
"mt-0.5 grid size-4 shrink-0 place-items-center border border-muted-foreground",
|
|
55
|
+
prompt.mode === "many" ? "rounded-sm" : "rounded-full",
|
|
56
|
+
checked && "border-primary",
|
|
57
|
+
)}
|
|
58
|
+
>
|
|
59
|
+
{checked ? <span className={cn("size-2 bg-primary", prompt.mode === "many" ? "rounded-[1px]" : "rounded-full")} /> : null}
|
|
60
|
+
</span>
|
|
61
|
+
<span className="grid gap-1">
|
|
62
|
+
<strong className="flex flex-wrap items-center gap-2 font-medium text-sm">
|
|
63
|
+
{label}
|
|
64
|
+
{label === document.metadata.recommended && <Badge variant="outline">Recommended</Badge>}
|
|
65
|
+
</strong>
|
|
66
|
+
{Boolean(description) && <small className="text-muted-foreground text-sm">{description}</small>}
|
|
67
|
+
</span>
|
|
52
68
|
</button>
|
|
53
|
-
{source === undefined && <ResponseMarkdown file={document.file} source={option.source.replace(/^#{1,6}\s+.+$/mu, "")} />}
|
|
54
69
|
</div>
|
|
55
70
|
);
|
|
56
71
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Option } from "effect";
|
|
2
|
-
import { useQuery } from "@shivaedev/effect-react/
|
|
2
|
+
import { useQuery } from "@shivaedev/effect-react/client/resultState.ts";
|
|
3
3
|
import type { Item } from "#client/react/LibraryContext.ts";
|
|
4
4
|
import { fileUrl } from "#files/url.ts";
|
|
5
5
|
import type { ShellApi } from "./ShellApi.ts";
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { routeQueryOptions } from "#client/page/routeQuery.ts";
|
|
2
1
|
import { GoalsPage } from "./GoalsPage.tsx";
|
|
3
2
|
import { HandoffPage } from "./HandoffPage.tsx";
|
|
4
3
|
import { LibraryMain } from "./LibraryMain.tsx";
|
|
@@ -6,6 +5,7 @@ import type { MainLocation } from "./mainLocation.ts";
|
|
|
6
5
|
import { ProjectionFrame } from "./ProjectionFrame.tsx";
|
|
7
6
|
import { ResultPage } from "./ResultPage.tsx";
|
|
8
7
|
import type { RemainingRoute } from "./remainingRoute.ts";
|
|
8
|
+
import { routeQueryOptions } from "./routeQuery.ts";
|
|
9
9
|
import type { ShellApi } from "./ShellApi.ts";
|
|
10
10
|
import { StartPage } from "./StartPage.tsx";
|
|
11
11
|
import { SystemMain } from "./SystemMain.tsx";
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import type { DocumentRequest } from "#browser/document/schema.ts";
|
|
2
|
-
import {
|
|
2
|
+
import type { RecordedResponse } from "#browser/responses/schema.ts";
|
|
3
|
+
import { chosenLabels } from "#client/responses/summary.ts";
|
|
4
|
+
import { Age } from "./Age.tsx";
|
|
5
|
+
import { AnswerMarkdown } from "./AnswerMarkdown.tsx";
|
|
3
6
|
import { shellIds } from "./shellIds.ts";
|
|
7
|
+
|
|
8
|
+
const KINDS = { "answer": "answered", "clarify": "asked back", "not_now": "said not now" } as const;
|
|
9
|
+
|
|
4
10
|
export function ResponseHistory({
|
|
5
11
|
request,
|
|
6
12
|
id = shellIds.responseHistory,
|
|
@@ -11,34 +17,67 @@ export function ResponseHistory({
|
|
|
11
17
|
const records = request?.responses ?? [];
|
|
12
18
|
const preview = request?.preview;
|
|
13
19
|
return (
|
|
14
|
-
<section id={id}>
|
|
15
|
-
<h2>Recorded responses</h2>
|
|
20
|
+
<section className="space-y-3 border-t pt-4" id={id}>
|
|
21
|
+
<h2 className="font-medium text-sm">Recorded responses</h2>
|
|
16
22
|
{records.length === 0 ? (
|
|
17
|
-
<p>No response is recorded
|
|
23
|
+
<p className="text-muted-foreground text-sm">No response is recorded yet.</p>
|
|
18
24
|
) : (
|
|
19
|
-
records.map((record) =>
|
|
25
|
+
records.map((record) => (
|
|
26
|
+
<ResponseEntry current={record.response.question === preview?.id} key={record.id} record={record} request={request} />
|
|
27
|
+
))
|
|
20
28
|
)}
|
|
21
29
|
</section>
|
|
22
30
|
);
|
|
23
31
|
}
|
|
24
|
-
function ResponseEntry({
|
|
32
|
+
function ResponseEntry({
|
|
33
|
+
record,
|
|
34
|
+
current,
|
|
35
|
+
request,
|
|
36
|
+
}: {
|
|
37
|
+
readonly record: RecordedResponse;
|
|
38
|
+
readonly current: boolean;
|
|
39
|
+
readonly request: DocumentRequest | undefined;
|
|
40
|
+
}) {
|
|
41
|
+
const answers = record.response.answers;
|
|
42
|
+
const chosen = chosenLabels(request?.prompts ?? [], answers);
|
|
43
|
+
const notes = (answers ?? []).map((answer) => answer.text.trim()).filter(Boolean);
|
|
25
44
|
return (
|
|
26
|
-
<article id={record.id}>
|
|
27
|
-
<h3>
|
|
28
|
-
<a href={`/responses/${record.id}.md`}>
|
|
29
|
-
{record.response.author}
|
|
45
|
+
<article className="space-y-2 rounded-lg bg-muted/50 px-4 py-3 text-sm" id={record.id}>
|
|
46
|
+
<h3 className="flex flex-wrap items-baseline gap-x-1.5 font-normal">
|
|
47
|
+
<a className="font-medium underline-offset-4 hover:underline" href={`/responses/${record.id}.md`}>
|
|
48
|
+
{record.response.author}
|
|
30
49
|
</a>
|
|
50
|
+
<span className="text-muted-foreground">{KINDS[record.response.type]}</span>
|
|
51
|
+
<Age modified={record.response.recordedAt} short />
|
|
31
52
|
</h3>
|
|
32
|
-
|
|
53
|
+
{answers === undefined ? (
|
|
54
|
+
<AnswerMarkdown file={`responses/${record.id}.md`} source={record.body} />
|
|
55
|
+
) : (
|
|
56
|
+
<>
|
|
57
|
+
<p>
|
|
58
|
+
<span className="text-muted-foreground">Chose </span>
|
|
59
|
+
<strong className="font-medium">{chosen || "no listed option"}</strong>
|
|
60
|
+
</p>
|
|
61
|
+
{notes.map((note) => (
|
|
62
|
+
<p className="whitespace-pre-wrap" key={note}>
|
|
63
|
+
{note}
|
|
64
|
+
</p>
|
|
65
|
+
))}
|
|
66
|
+
<details className="text-muted-foreground text-xs">
|
|
67
|
+
<summary className="cursor-pointer select-none">Full response</summary>
|
|
68
|
+
<AnswerMarkdown className="mt-2 text-foreground text-sm" file={`responses/${record.id}.md`} source={record.body} />
|
|
69
|
+
</details>
|
|
70
|
+
</>
|
|
71
|
+
)}
|
|
72
|
+
<p className="text-muted-foreground text-xs" title={record.response.reviewedRevision}>
|
|
33
73
|
{current ? "This reviewed revision" : "Earlier reviewed context — does not approve the current source"} ·{" "}
|
|
34
|
-
<code>{record.response.reviewedRevision}</code>
|
|
74
|
+
<code>{record.response.reviewedRevision.slice(0, 12)}</code>
|
|
35
75
|
</p>
|
|
36
76
|
{Boolean(record.response.supersedes) && (
|
|
37
|
-
<p>
|
|
77
|
+
<p className="text-muted-foreground text-xs">
|
|
38
78
|
Explicitly supersedes <a href={`/responses/${record.response.supersedes}.md`}>{record.response.supersedes}</a>.
|
|
39
79
|
</p>
|
|
40
80
|
)}
|
|
41
|
-
<ResponseMarkdown file={`responses/${record.id}.md`} source={record.body} />
|
|
42
81
|
</article>
|
|
43
82
|
);
|
|
44
83
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Option } from "effect";
|
|
2
2
|
import { useEffect, useRef, useState } from "react";
|
|
3
|
-
import { useQuery } from "@shivaedev/effect-react/
|
|
3
|
+
import { useQuery } from "@shivaedev/effect-react/client/resultState.ts";
|
|
4
4
|
import { SearchResults } from "#client/react/SearchResults.tsx";
|
|
5
5
|
import type { ShellApi } from "#client/react/ShellApi.ts";
|
|
6
6
|
import { matchingCommands, type SearchItem } from "#client/react/searchCommands.ts";
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { Option } from "effect";
|
|
2
2
|
import { PanelsTopLeft } from "lucide-react";
|
|
3
3
|
import { useContext, useEffect } from "react";
|
|
4
|
-
import { useQuery } from "@shivaedev/effect-react/
|
|
4
|
+
import { useQuery } from "@shivaedev/effect-react/client/resultState.ts";
|
|
5
5
|
import type { Navigation } from "#browser/Navigation.ts";
|
|
6
6
|
import { Badge } from "#client/react/components/ui/Badge.tsx";
|
|
7
7
|
import { buttonVariants } from "#client/react/components/ui/buttonVariants.ts";
|
|
8
|
+
import { destinationName } from "#client/react/destinationName.ts";
|
|
8
9
|
import { GoalProgress } from "#client/react/GoalProgress.tsx";
|
|
9
10
|
import { InboxCount } from "#client/react/Header.tsx";
|
|
10
11
|
import { type Item, LibraryContext } from "#client/react/LibraryContext.ts";
|
|
@@ -38,6 +39,12 @@ export function Sidebar({
|
|
|
38
39
|
today: data?.inboxCount,
|
|
39
40
|
work: data?.counts.now,
|
|
40
41
|
};
|
|
42
|
+
function name(view: (typeof workspaceDestinations)[number]["view"], label: string) {
|
|
43
|
+
if (view === "today") {
|
|
44
|
+
return destinationName(label, counts.today, "need you");
|
|
45
|
+
}
|
|
46
|
+
return view === "work" ? destinationName(label, counts.work, "open tasks") : label;
|
|
47
|
+
}
|
|
41
48
|
useEffect(() => {
|
|
42
49
|
const sidebar = document.getElementById(shellIds.sidebar);
|
|
43
50
|
if (controls.narrow && controls.open) {
|
|
@@ -66,6 +73,7 @@ export function Sidebar({
|
|
|
66
73
|
{workspaceDestinations.map(({ label, href, view, id }) => (
|
|
67
74
|
<a
|
|
68
75
|
aria-current={(view === "today" ? home : main.view === view) ? "page" : undefined}
|
|
76
|
+
aria-label={name(view, label)}
|
|
69
77
|
className={cn(
|
|
70
78
|
buttonVariants({ variant: "ghost" }),
|
|
71
79
|
"justify-between no-underline",
|
|
@@ -77,7 +85,7 @@ export function Sidebar({
|
|
|
77
85
|
>
|
|
78
86
|
<span>{label}</span>
|
|
79
87
|
{view === "today" && <InboxCount count={counts.today ?? null} id={shellIds.inboxCount} />}
|
|
80
|
-
{view === "work" && <Badge>{counts.work
|
|
88
|
+
{view === "work" && counts.work !== undefined && <Badge aria-hidden="true">{counts.work}</Badge>}
|
|
81
89
|
</a>
|
|
82
90
|
))}
|
|
83
91
|
</nav>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { FleetItemsProjection } from "#browser/views/FleetItemsProjection.ts";
|
|
2
|
+
import { fleetHolder } from "#browser/views/now.ts";
|
|
2
3
|
import { Badge } from "#client/react/components/ui/Badge.tsx";
|
|
3
4
|
import { Card } from "#client/react/components/ui/Card.tsx";
|
|
4
5
|
|
|
@@ -29,7 +30,7 @@ function FleetRow({ item }: { readonly item: Item }) {
|
|
|
29
30
|
{title}
|
|
30
31
|
</a>
|
|
31
32
|
<Badge variant="outline">{item.stage}</Badge>
|
|
32
|
-
|
|
33
|
+
<span className="text-muted-foreground text-xs">{fleetHolder(item)}</span>
|
|
33
34
|
{Boolean(item.prUrl) && (
|
|
34
35
|
<a aria-label={`Pull request for ${title}`} className="text-sm underline" href={item.prUrl}>
|
|
35
36
|
PR ↗
|
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
import type { FleetHealth } from "#diagnostics/health.ts";
|
|
2
2
|
import { ObservedTime } from "./diagnosticsValues.tsx";
|
|
3
|
-
|
|
3
|
+
|
|
4
|
+
const waitingOn = { automation: "Waiting on Fleet agents", orchestrator: "Waiting on the orchestrator" } as const;
|
|
5
|
+
|
|
6
|
+
export function SystemHealth({
|
|
7
|
+
value,
|
|
8
|
+
now,
|
|
9
|
+
needsYou,
|
|
10
|
+
}: {
|
|
11
|
+
readonly value: FleetHealth;
|
|
12
|
+
readonly now: number;
|
|
13
|
+
readonly needsYou?: number | null | undefined;
|
|
14
|
+
}) {
|
|
4
15
|
let freshness = "Current snapshot";
|
|
5
16
|
if (now < value.observedAt) {
|
|
6
17
|
freshness = "Publisher clock is ahead";
|
|
@@ -26,11 +37,20 @@ export function SystemHealth({ value, now }: { readonly value: FleetHealth; read
|
|
|
26
37
|
<strong>{value.queued}</strong>
|
|
27
38
|
</dd>
|
|
28
39
|
</div>
|
|
29
|
-
|
|
40
|
+
<div data-needs-you>
|
|
41
|
+
<dt>Needs you</dt>
|
|
42
|
+
<dd className="flex flex-wrap items-baseline gap-2">
|
|
43
|
+
<strong>{needsYou ?? "Unavailable"}</strong>
|
|
44
|
+
<a className="text-xs" href="/#needs-you">
|
|
45
|
+
Open Today
|
|
46
|
+
</a>
|
|
47
|
+
</dd>
|
|
48
|
+
</div>
|
|
49
|
+
{(["orchestrator", "automation"] as const).map((owner) => (
|
|
30
50
|
<div key={owner}>
|
|
31
|
-
<dt>
|
|
51
|
+
<dt>{waitingOn[owner]}</dt>
|
|
32
52
|
<dd>
|
|
33
|
-
<strong>{
|
|
53
|
+
<strong>{value.waiting[owner]}</strong>
|
|
34
54
|
</dd>
|
|
35
55
|
</div>
|
|
36
56
|
))}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import { Option } from "effect";
|
|
1
2
|
import { useContext } from "react";
|
|
2
|
-
import {
|
|
3
|
+
import { useQuery } from "@shivaedev/effect-react/client/resultState.ts";
|
|
3
4
|
import type { MainLocation } from "./mainLocation.ts";
|
|
4
5
|
import { ProjectionFrame } from "./ProjectionFrame.tsx";
|
|
6
|
+
import { navigationQuery, routeQueryOptions } from "./routeQuery.ts";
|
|
5
7
|
import { SessionContext } from "./SessionContext.ts";
|
|
6
8
|
import type { ShellApi } from "./ShellApi.ts";
|
|
7
9
|
import { SystemPage } from "./SystemPage.tsx";
|
|
@@ -18,12 +20,14 @@ export function SystemMain({
|
|
|
18
20
|
}) {
|
|
19
21
|
const atom = api.diagnostics.query(undefined, routeQueryOptions);
|
|
20
22
|
useDiagnosticsPolling(useContext(SessionContext), atom, api.fleetItems.query(undefined, routeQueryOptions));
|
|
23
|
+
const navigation = useQuery(navigationQuery(api), { keepPreviousData: true });
|
|
24
|
+
const needsYou = Option.getOrUndefined(navigation.data)?.inboxCount;
|
|
21
25
|
return (
|
|
22
26
|
<ProjectionFrame
|
|
23
27
|
atom={atom}
|
|
24
28
|
location={() => ({ file: "System", status: 200, view: "system" })}
|
|
25
29
|
onPage={onPage}
|
|
26
|
-
render={(data) => <SystemPage data={data} />}
|
|
30
|
+
render={(data) => <SystemPage data={data} needsYou={needsYou} />}
|
|
27
31
|
route="system"
|
|
28
32
|
url={url}
|
|
29
33
|
/>
|
|
@@ -7,7 +7,13 @@ import { FleetItemsContext } from "./FleetItemsContext.ts";
|
|
|
7
7
|
import { githubLimited } from "./githubLimited.ts";
|
|
8
8
|
import { SystemFleetLane } from "./SystemFleetLane.tsx";
|
|
9
9
|
import { SystemHealth } from "./SystemHealth.tsx";
|
|
10
|
-
export function SystemPage({
|
|
10
|
+
export function SystemPage({
|
|
11
|
+
data: { sources: data, now },
|
|
12
|
+
needsYou,
|
|
13
|
+
}: {
|
|
14
|
+
readonly data: DiagnosticsProjection;
|
|
15
|
+
readonly needsYou?: number | null | undefined;
|
|
16
|
+
}) {
|
|
11
17
|
const items = useContext(FleetItemsContext);
|
|
12
18
|
const fleet = data.fleet.state === "ready" ? { ...data.fleet, observedAt: data.fleet.value.observedAt } : data.fleet;
|
|
13
19
|
return (
|
|
@@ -38,7 +44,7 @@ export function SystemPage({ data: { sources: data, now } }: { readonly data: Di
|
|
|
38
44
|
|| now < fleet.value.observedAt)
|
|
39
45
|
}
|
|
40
46
|
name="Fleet"
|
|
41
|
-
render={(value) => <SystemHealth now={now} value={value} />}
|
|
47
|
+
render={(value) => <SystemHealth needsYou={needsYou} now={now} value={value} />}
|
|
42
48
|
source={fleet}
|
|
43
49
|
stale={fleet.state === "ready" && now - fleet.value.observedAt > fleet.value.staleAfterMs}
|
|
44
50
|
/>
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
1
2
|
import type { InboxProjection } from "#browser/views/schema.ts";
|
|
2
3
|
import { StatusChip } from "#client/react/components/ui/StatusChip.tsx";
|
|
3
4
|
import { Age } from "./Age.tsx";
|
|
@@ -13,7 +14,9 @@ export function TodayNeeds({ api, data }: { readonly api: ShellApi; readonly dat
|
|
|
13
14
|
<ul aria-label="Needs you" className="m-0 list-none divide-y rounded-lg border border-border p-0">
|
|
14
15
|
{data.you.map((item) => (
|
|
15
16
|
<li className="h-12 max-h-12" data-attention-id={item.id} key={`${item.file}:${item.href}`}>
|
|
16
|
-
<TodayRow
|
|
17
|
+
<TodayRow item={item} status="needs-you">
|
|
18
|
+
<TodayOptions api={api} item={item} />
|
|
19
|
+
</TodayRow>
|
|
17
20
|
</li>
|
|
18
21
|
))}
|
|
19
22
|
</ul>
|
|
@@ -22,13 +25,25 @@ export function TodayNeeds({ api, data }: { readonly api: ShellApi; readonly dat
|
|
|
22
25
|
);
|
|
23
26
|
}
|
|
24
27
|
|
|
25
|
-
function
|
|
28
|
+
export function TodayLater({ items }: { readonly items: readonly Item[] }) {
|
|
26
29
|
return (
|
|
27
|
-
<
|
|
30
|
+
<ul aria-label="Later" className="m-0 list-none divide-y rounded-lg border border-border p-0">
|
|
31
|
+
{items.map((item) => (
|
|
32
|
+
<li className="h-12 max-h-12" key={item.file}>
|
|
33
|
+
<TodayRow item={item} status="deferred" />
|
|
34
|
+
</li>
|
|
35
|
+
))}
|
|
36
|
+
</ul>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function TodayRow({ children, item, status }: { readonly children?: ReactNode; readonly item: Item; readonly status: "deferred" | "needs-you" }) {
|
|
41
|
+
return (
|
|
42
|
+
<div className="today-row flex h-full min-w-0 items-center gap-2 px-2" data-attention-row={status === "needs-you" ? "" : undefined}>
|
|
28
43
|
<StatusChip
|
|
29
|
-
aria-label="Needs you"
|
|
44
|
+
aria-label={status === "needs-you" ? "Needs you" : "Deferred"}
|
|
30
45
|
className="shrink-0 max-sm:size-6 max-sm:justify-center max-sm:gap-0 max-sm:px-1 max-sm:text-[0px] max-sm:[&_svg]:shrink-0"
|
|
31
|
-
status=
|
|
46
|
+
status={status}
|
|
32
47
|
/>
|
|
33
48
|
<div className="min-w-0 flex-1">
|
|
34
49
|
<div className="flex min-w-0 items-baseline gap-2">
|
|
@@ -48,7 +63,7 @@ function TodayRow({ api, item }: { readonly api: ShellApi; readonly item: Item }
|
|
|
48
63
|
<span className="shrink-0 text-[10px] text-muted-foreground tabular-nums">
|
|
49
64
|
{item.modified === undefined ? "Age ?" : <Age modified={item.modified} short />}
|
|
50
65
|
</span>
|
|
51
|
-
|
|
66
|
+
{children}
|
|
52
67
|
</div>
|
|
53
68
|
);
|
|
54
69
|
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { optionText } from "#browser/responses/cardContext.ts";
|
|
2
|
+
import { Button } from "#client/react/components/ui/Button.tsx";
|
|
3
|
+
|
|
4
|
+
export function TodayOptionButton({
|
|
5
|
+
disabled,
|
|
6
|
+
onChoose,
|
|
7
|
+
option,
|
|
8
|
+
recommended,
|
|
9
|
+
source,
|
|
10
|
+
}: {
|
|
11
|
+
readonly disabled: boolean;
|
|
12
|
+
readonly onChoose: () => void;
|
|
13
|
+
readonly option: { readonly id: string; readonly source: string };
|
|
14
|
+
readonly recommended: string | undefined;
|
|
15
|
+
readonly source: { readonly label: string; readonly description: string } | undefined;
|
|
16
|
+
}) {
|
|
17
|
+
const authored = optionText(option.id, option.source);
|
|
18
|
+
const text = source?.label ?? authored.label;
|
|
19
|
+
const description = source?.description ?? authored.description;
|
|
20
|
+
return (
|
|
21
|
+
<Button
|
|
22
|
+
className="h-auto w-full flex-col items-start gap-0.5 whitespace-normal px-3 py-2 text-left"
|
|
23
|
+
disabled={disabled}
|
|
24
|
+
onClick={onChoose}
|
|
25
|
+
size="sm"
|
|
26
|
+
variant="outline"
|
|
27
|
+
>
|
|
28
|
+
<span>
|
|
29
|
+
{text}
|
|
30
|
+
{recommended === text ? " · Recommended" : null}
|
|
31
|
+
</span>
|
|
32
|
+
{description ? <span className="font-normal text-muted-foreground text-xs">{description}</span> : null}
|
|
33
|
+
</Button>
|
|
34
|
+
);
|
|
35
|
+
}
|
|
@@ -1,21 +1,23 @@
|
|
|
1
1
|
import { RegistryContext } from "@effect/atom-react";
|
|
2
2
|
import { Cause, Effect, Option } from "effect";
|
|
3
|
-
import { useContext, useMemo, useState } from "react";
|
|
4
|
-
import { useAction, useQuery } from "@shivaedev/effect-react/
|
|
3
|
+
import { useContext, useMemo, useRef, useState } from "react";
|
|
4
|
+
import { useAction, useQuery } from "@shivaedev/effect-react/client/resultState.ts";
|
|
5
5
|
import type { Document, DocumentRequest } from "#browser/document/schema.ts";
|
|
6
6
|
import { answerBody } from "#browser/responses/answerBody.ts";
|
|
7
|
-
import {
|
|
7
|
+
import { promptHeading } from "#browser/responses/cardContext.ts";
|
|
8
|
+
import { reasonMarkdown } from "#browser/responses/reasonMarkdown.ts";
|
|
8
9
|
import type { InboxProjection } from "#browser/views/schema.ts";
|
|
9
|
-
import { documentQuery, navigationQuery, routeQueryOptions } from "#client/page/routeQuery.ts";
|
|
10
|
-
import { Button } from "#client/react/components/ui/Button.tsx";
|
|
11
10
|
import { Popover } from "#client/react/components/ui/Popover.tsx";
|
|
12
11
|
import { authorLabel } from "#client/responses/authorLabel.ts";
|
|
12
|
+
import { chosenLabels } from "#client/responses/summary.ts";
|
|
13
13
|
import { saveFailure } from "#client/saveFailure.ts";
|
|
14
14
|
import { fileUrl } from "#files/url.ts";
|
|
15
15
|
import { responseUrl } from "#responses/url.ts";
|
|
16
16
|
import { createResponse } from "./createResponse.ts";
|
|
17
17
|
import { useFormClient } from "./FormContext.ts";
|
|
18
|
+
import { documentQuery, navigationQuery, routeQueryOptions } from "./routeQuery.ts";
|
|
18
19
|
import type { ShellApi } from "./ShellApi.ts";
|
|
20
|
+
import { TodayOptionButton } from "./TodayOptionButton.tsx";
|
|
19
21
|
|
|
20
22
|
export function TodayOptions({ api, item }: { readonly api: ShellApi; readonly item: InboxProjection["you"][number] }) {
|
|
21
23
|
const query = useQuery(documentQuery(api, fileUrl(item.file)), { keepPreviousData: true });
|
|
@@ -28,11 +30,11 @@ export function TodayOptions({ api, item }: { readonly api: ShellApi; readonly i
|
|
|
28
30
|
&& !request.promptUnavailable
|
|
29
31
|
&& request.prompts?.length === 1
|
|
30
32
|
&& request.prompts[0]?.mode === "one" ? (
|
|
31
|
-
<Popover label={`Answer ${item.title}`} trigger="Answer" triggerClassName="h-8 w-auto px-1 text-xs">
|
|
32
|
-
<a className="mb-3 block text-xs" href={item.href}>
|
|
33
|
-
Open full question
|
|
34
|
-
</a>
|
|
33
|
+
<Popover className="w-80" label={`Answer ${item.title}`} trigger="Answer" triggerClassName="h-8 w-auto px-1 text-xs">
|
|
35
34
|
<InlineOptions api={api} document={data} key={request.preview.reviewedRevision} request={request} />
|
|
35
|
+
<a className="mt-3 block border-t px-2 pt-2 text-muted-foreground text-xs hover:text-foreground" href={item.href}>
|
|
36
|
+
Open the full question with its context →
|
|
37
|
+
</a>
|
|
36
38
|
</Popover>
|
|
37
39
|
) : (
|
|
38
40
|
<a aria-label={`Answer ${item.title}`} className="shrink-0 text-xs" href={item.href}>
|
|
@@ -43,6 +45,8 @@ export function TodayOptions({ api, item }: { readonly api: ShellApi; readonly i
|
|
|
43
45
|
|
|
44
46
|
function InlineOptions({ api, document: data, request }: { readonly api: ShellApi; readonly document: Document; readonly request: DocumentRequest }) {
|
|
45
47
|
const registry = useContext(RegistryContext);
|
|
48
|
+
const requestRef = useRef(request);
|
|
49
|
+
requestRef.current = request;
|
|
46
50
|
const client = useFormClient();
|
|
47
51
|
const [label] = useState(() => authorLabel(() => localStorage, document.documentElement.dataset.workspace));
|
|
48
52
|
const [author, setAuthor] = useState(() => label.read("") || "maintainer");
|
|
@@ -50,8 +54,8 @@ function InlineOptions({ api, document: data, request }: { readonly api: ShellAp
|
|
|
50
54
|
() =>
|
|
51
55
|
client.runtime.fn(
|
|
52
56
|
Effect.fn("Board.answerOption")(function* (choice: string) {
|
|
53
|
-
const preview =
|
|
54
|
-
const prompt =
|
|
57
|
+
const preview = requestRef.current.preview;
|
|
58
|
+
const prompt = requestRef.current.prompts?.[0];
|
|
55
59
|
if (!(preview && prompt && author.trim())) {
|
|
56
60
|
return yield* Effect.fail("Add your local author label.");
|
|
57
61
|
}
|
|
@@ -73,12 +77,14 @@ function InlineOptions({ api, document: data, request }: { readonly api: ShellAp
|
|
|
73
77
|
return saved;
|
|
74
78
|
}),
|
|
75
79
|
),
|
|
76
|
-
[client, api, registry,
|
|
80
|
+
[client, api, registry, author, data.file, label.save],
|
|
77
81
|
);
|
|
78
82
|
const action = useAction(actionAtom);
|
|
79
83
|
const failure = Option.getOrUndefined(Option.flatMap(action.cause, Cause.findErrorOption));
|
|
80
84
|
const saved = Option.getOrUndefined(action.data);
|
|
81
|
-
let status = saved
|
|
85
|
+
let status = saved
|
|
86
|
+
? `Saved: your answer “${chosenLabels(request.prompts ?? [], saved.answers)}” is recorded. The agent reads it the next time it checks.`
|
|
87
|
+
: "";
|
|
82
88
|
if (failure) {
|
|
83
89
|
status = typeof failure === "string" ? failure : saveFailure(failure, "response").text;
|
|
84
90
|
}
|
|
@@ -86,33 +92,36 @@ function InlineOptions({ api, document: data, request }: { readonly api: ShellAp
|
|
|
86
92
|
status = "Pending: registering reviewed context, then recording response…";
|
|
87
93
|
}
|
|
88
94
|
const prompt = request.prompts?.[0];
|
|
95
|
+
const reason = reasonMarkdown(request.preview?.reason ?? "").split("\n")[0];
|
|
89
96
|
return (
|
|
90
|
-
<fieldset aria-label={`Options for ${data.identity}`} className="m-0 min-w-0 space-y-
|
|
91
|
-
<
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
</Button>
|
|
105
|
-
);
|
|
106
|
-
})}
|
|
97
|
+
<fieldset aria-label={`Options for ${data.identity}`} className="m-0 min-w-0 space-y-3 border-0 px-2 pb-1">
|
|
98
|
+
{Boolean(reason) && <p className="m-0 font-medium text-sm">{reason}</p>}
|
|
99
|
+
{prompt ? <p className="m-0 text-muted-foreground text-xs">{promptHeading(prompt.source).title ?? "Choose one option."}</p> : null}
|
|
100
|
+
<div className="grid gap-2">
|
|
101
|
+
{prompt?.options.map((option, index) => (
|
|
102
|
+
<TodayOptionButton
|
|
103
|
+
disabled={!(data.writable && author.trim()) || action.pending || Boolean(saved)}
|
|
104
|
+
key={option.id}
|
|
105
|
+
onChoose={() => action.dispatch(option.id)}
|
|
106
|
+
option={option}
|
|
107
|
+
recommended={data.metadata.recommended}
|
|
108
|
+
source={data.metadata.options?.[index]}
|
|
109
|
+
/>
|
|
110
|
+
))}
|
|
107
111
|
</div>
|
|
108
112
|
<details className="text-muted-foreground text-xs">
|
|
109
113
|
<summary className="cursor-pointer">Answer as {author || "…"}</summary>
|
|
110
|
-
<label>
|
|
114
|
+
<label className="mt-2 flex items-center gap-2">
|
|
111
115
|
Local author label{" "}
|
|
112
|
-
<input
|
|
116
|
+
<input
|
|
117
|
+
className="rounded border bg-background px-2 py-1"
|
|
118
|
+
maxLength={200}
|
|
119
|
+
onChange={(event) => setAuthor(event.currentTarget.value)}
|
|
120
|
+
value={author}
|
|
121
|
+
/>
|
|
113
122
|
</label>
|
|
114
123
|
</details>
|
|
115
|
-
{!data.writable && <p className="m-0 text-muted-foreground text-xs">
|
|
124
|
+
{!data.writable && <p className="m-0 text-muted-foreground text-xs">This Board is read-only. Start it with --responses to record answers.</p>}
|
|
116
125
|
{Boolean(status) && (
|
|
117
126
|
<p className="m-0 text-xs" role="status">
|
|
118
127
|
{status}
|