@shivaedev/work-board 0.17.1 → 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 +13 -0
- package/README.md +23 -21
- package/assets/react.css +1 -1
- package/assets/react.js +37 -37
- package/dist/browser/responses/cardContext.d.ts +1 -1
- package/dist/browser/responses/cardContext.d.ts.map +1 -1
- package/dist/browser/responses/cardContext.js +1 -4
- 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/client/document/View.js +1 -1
- package/dist/client/document/View.js.map +1 -1
- package/dist/client/formStatus.d.ts +1 -1
- package/dist/client/formStatus.d.ts.map +1 -1
- package/dist/client/react/BoardShell.js +1 -1
- package/dist/client/react/BoardShell.js.map +1 -1
- package/dist/client/react/DocumentMain.js +1 -1
- 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 +1 -1
- package/dist/client/react/GoalPage.js.map +1 -1
- package/dist/client/react/GoalProgressHeader.js +1 -1
- 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/LibraryPage.js +1 -1
- 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/PromptOption.js +1 -1
- 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/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/TodayOptionButton.d.ts +1 -2
- package/dist/client/react/TodayOptionButton.d.ts.map +1 -1
- package/dist/client/react/TodayOptionButton.js +2 -2
- package/dist/client/react/TodayOptionButton.js.map +1 -1
- package/dist/client/react/TodayOptions.js +8 -6
- package/dist/client/react/TodayOptions.js.map +1 -1
- package/dist/client/react/TodayPage.js +14 -2
- 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/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.js +1 -1
- package/dist/client/react/loadBoardPage.js.map +1 -1
- package/dist/client/react/loadProjection.d.ts +135 -135
- package/dist/client/react/loadProjection.js +1 -1
- package/dist/client/react/loadProjection.js.map +1 -1
- package/dist/client/react/loadRemaining.d.ts +17 -17
- package/dist/client/react/loadRemaining.js +1 -1
- package/dist/client/react/loadRemaining.js.map +1 -1
- 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.js +1 -1
- package/dist/client/react/useResponseForm.js.map +1 -1
- package/dist/client/responses/summary.js +1 -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 +1930 -1930
- 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/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 +896 -896
- package/dist/server/mcp/client.d.ts.map +1 -1
- package/dist/server/mcp/queries.d.ts +2947 -2947
- 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/workspace.js +1 -1
- package/dist/server/workspace.js.map +1 -1
- package/package.json +18 -362
- package/src/browser/responses/cardContext.ts +1 -4
- package/src/browser/views/now.ts +42 -2
- package/src/client/document/View.ts +1 -1
- package/src/client/formStatus.ts +1 -1
- package/src/client/react/BoardShell.tsx +1 -1
- package/src/client/react/DocumentMain.tsx +1 -1
- package/src/client/react/FleetMain.tsx +1 -1
- package/src/client/react/FormField.tsx +1 -1
- package/src/client/react/GoalPage.tsx +1 -1
- package/src/client/react/GoalProgressHeader.tsx +1 -1
- package/src/client/react/Header.tsx +3 -2
- package/src/client/react/LibraryContents.tsx +1 -1
- package/src/client/react/LibraryPage.tsx +1 -1
- package/src/client/react/LiveControls.tsx +1 -1
- package/src/client/react/PromptOption.tsx +1 -1
- package/src/client/react/ReadingHistory.tsx +1 -1
- 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/TodayOptionButton.tsx +1 -3
- package/src/client/react/TodayOptions.tsx +7 -6
- package/src/client/react/TodayPage.tsx +18 -4
- 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/destinationName.ts +3 -0
- package/src/client/react/loadBoardPage.ts +1 -1
- package/src/client/react/loadProjection.ts +1 -1
- package/src/client/react/loadRemaining.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 +1 -1
- package/src/client/responses/summary.ts +1 -1
- package/src/diagnostics/source.ts +1 -1
- package/src/server/Responses.ts +2 -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/src/client/formStatus.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Option } from "effect";
|
|
2
|
-
import type { SaveState } from "@shivaedev/effect-react/
|
|
2
|
+
import type { SaveState } from "@shivaedev/effect-react/client/saveState.ts";
|
|
3
3
|
import { type SaveFailure, saveFailure } from "./saveFailure.ts";
|
|
4
4
|
|
|
5
5
|
export type DraftInteraction = "draft" | "cleared" | "submitted";
|
|
@@ -2,7 +2,7 @@ import { useRouterState } from "@tanstack/react-router";
|
|
|
2
2
|
import { Option } from "effect";
|
|
3
3
|
import type * as AtomRegistry from "effect/reactivity/AtomRegistry";
|
|
4
4
|
import { useCallback, useContext, useEffect, useState } from "react";
|
|
5
|
-
import { useQuery } from "@shivaedev/effect-react/
|
|
5
|
+
import { useQuery } from "@shivaedev/effect-react/client/resultState.ts";
|
|
6
6
|
import type { BrowserClient } from "#client/client.ts";
|
|
7
7
|
import { AgeTicker } from "./Age.tsx";
|
|
8
8
|
import { FleetMain } from "./FleetMain.tsx";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Cause, Option } from "effect";
|
|
2
2
|
import { useEffect, useState } from "react";
|
|
3
|
-
import { useQuery } from "@shivaedev/effect-react/
|
|
3
|
+
import { useQuery } from "@shivaedev/effect-react/client/resultState.ts";
|
|
4
4
|
import type { Document } from "#browser/document/schema.ts";
|
|
5
5
|
import { DocumentPage } from "./DocumentPage.tsx";
|
|
6
6
|
import type { MainLocation } from "./mainLocation.ts";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Option } from "effect";
|
|
2
2
|
import type { ComponentProps } from "react";
|
|
3
|
-
import { useQuery } from "@shivaedev/effect-react/
|
|
3
|
+
import { useQuery } from "@shivaedev/effect-react/client/resultState.ts";
|
|
4
4
|
import { BoardMain } from "./BoardMain.tsx";
|
|
5
5
|
import { FleetItemsContext } from "./FleetItemsContext.ts";
|
|
6
6
|
export function FleetMain(props: ComponentProps<typeof BoardMain>) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type ReactNode, useId, useLayoutEffect, useRef } from "react";
|
|
2
|
-
import { useField } from "@shivaedev/effect-form/react.ts";
|
|
2
|
+
import { useField } from "@shivaedev/effect-form/client/react.ts";
|
|
3
3
|
import type { Fields, Form, Name } from "@shivaedev/effect-form/shape.ts";
|
|
4
4
|
|
|
5
5
|
type Control = HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement;
|
|
@@ -1,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 { Document } from "#browser/document/schema.ts";
|
|
4
4
|
import { GoalCriteria } from "./GoalCriteria.tsx";
|
|
5
5
|
import { GoalWorkTable } from "./GoalWorkTable.tsx";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Option } from "effect";
|
|
2
2
|
import { useId } from "react";
|
|
3
|
-
import { useQuery } from "@shivaedev/effect-react/
|
|
3
|
+
import { useQuery } from "@shivaedev/effect-react/client/resultState.ts";
|
|
4
4
|
import type { Document } from "#browser/document/schema.ts";
|
|
5
5
|
import { GoalProgressSummary } from "./GoalProgressSummary.tsx";
|
|
6
6
|
import { routeQueryOptions } from "./routeQuery.ts";
|
|
@@ -3,6 +3,7 @@ import type { ReactNode } from "react";
|
|
|
3
3
|
import { Badge } from "#client/react/components/ui/Badge.tsx";
|
|
4
4
|
import { Button } from "#client/react/components/ui/Button.tsx";
|
|
5
5
|
import { buttonVariants } from "#client/react/components/ui/buttonVariants.ts";
|
|
6
|
+
import { destinationName } from "#client/react/destinationName.ts";
|
|
6
7
|
import { cn } from "#client/react/lib/cn.ts";
|
|
7
8
|
import { KeyboardHints } from "./KeyboardHints.tsx";
|
|
8
9
|
import type { MainLocation } from "./mainLocation.ts";
|
|
@@ -12,7 +13,7 @@ import type { AppearanceControls } from "./useAppearance.ts";
|
|
|
12
13
|
import type { SidebarControls } from "./useSidebar.ts";
|
|
13
14
|
export function InboxCount({ count, id }: { readonly count: number | null; readonly id: string }) {
|
|
14
15
|
return (
|
|
15
|
-
<Badge aria-label={
|
|
16
|
+
<Badge aria-label={count === null ? "Needs you unavailable" : `${count} need you`} id={id} role="status">
|
|
16
17
|
{count ?? "?"}
|
|
17
18
|
</Badge>
|
|
18
19
|
);
|
|
@@ -57,7 +58,7 @@ export function Header({
|
|
|
57
58
|
<Breadcrumbs file={main.title ?? main.file} />
|
|
58
59
|
<a
|
|
59
60
|
aria-current={main.view === "today" ? "page" : undefined}
|
|
60
|
-
aria-label="Today"
|
|
61
|
+
aria-label={destinationName("Today", count, "need you")}
|
|
61
62
|
className={cn(buttonVariants({ size: "sm", variant: "ghost" }), "hidden md:inline-flex")}
|
|
62
63
|
href="/"
|
|
63
64
|
id={shellIds.inboxTopbar}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Option } from "effect";
|
|
2
2
|
import { useState } from "react";
|
|
3
|
-
import { useQuery } from "@shivaedev/effect-react/
|
|
3
|
+
import { useQuery } from "@shivaedev/effect-react/client/resultState.ts";
|
|
4
4
|
import type { Library, LibraryFile } from "#browser/navigation/library.ts";
|
|
5
5
|
import { Age } from "./Age.tsx";
|
|
6
6
|
import type { ShellApi } from "./ShellApi.ts";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Option } from "effect";
|
|
2
2
|
import { useState } from "react";
|
|
3
|
-
import { useQuery } from "@shivaedev/effect-react/
|
|
3
|
+
import { useQuery } from "@shivaedev/effect-react/client/resultState.ts";
|
|
4
4
|
import { LibraryContents } from "./LibraryContents.tsx";
|
|
5
5
|
import { routeQueryOptions } from "./routeQuery.ts";
|
|
6
6
|
import type { ShellApi } from "./ShellApi.ts";
|
|
@@ -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";
|
|
@@ -22,7 +22,7 @@ export function PromptOption({
|
|
|
22
22
|
}) {
|
|
23
23
|
const source = document.metadata.options?.[choice];
|
|
24
24
|
const id = useId();
|
|
25
|
-
const authored = optionText(option.id, option.source
|
|
25
|
+
const authored = optionText(option.id, option.source);
|
|
26
26
|
const label = source?.label ?? authored.label;
|
|
27
27
|
const description = source?.description ?? authored.description;
|
|
28
28
|
return (
|
|
@@ -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,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";
|
|
3
|
+
import { useQuery } from "@shivaedev/effect-react/client/resultState.ts";
|
|
2
4
|
import type { MainLocation } from "./mainLocation.ts";
|
|
3
5
|
import { ProjectionFrame } from "./ProjectionFrame.tsx";
|
|
4
|
-
import { routeQueryOptions } from "./routeQuery.ts";
|
|
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
|
/>
|
|
@@ -5,18 +5,16 @@ export function TodayOptionButton({
|
|
|
5
5
|
disabled,
|
|
6
6
|
onChoose,
|
|
7
7
|
option,
|
|
8
|
-
reason,
|
|
9
8
|
recommended,
|
|
10
9
|
source,
|
|
11
10
|
}: {
|
|
12
11
|
readonly disabled: boolean;
|
|
13
12
|
readonly onChoose: () => void;
|
|
14
13
|
readonly option: { readonly id: string; readonly source: string };
|
|
15
|
-
readonly reason: string;
|
|
16
14
|
readonly recommended: string | undefined;
|
|
17
15
|
readonly source: { readonly label: string; readonly description: string } | undefined;
|
|
18
16
|
}) {
|
|
19
|
-
const authored = optionText(option.id, option.source
|
|
17
|
+
const authored = optionText(option.id, option.source);
|
|
20
18
|
const text = source?.label ?? authored.label;
|
|
21
19
|
const description = source?.description ?? authored.description;
|
|
22
20
|
return (
|
|
@@ -1,7 +1,7 @@
|
|
|
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
7
|
import { promptHeading } from "#browser/responses/cardContext.ts";
|
|
@@ -45,6 +45,8 @@ export function TodayOptions({ api, item }: { readonly api: ShellApi; readonly i
|
|
|
45
45
|
|
|
46
46
|
function InlineOptions({ api, document: data, request }: { readonly api: ShellApi; readonly document: Document; readonly request: DocumentRequest }) {
|
|
47
47
|
const registry = useContext(RegistryContext);
|
|
48
|
+
const requestRef = useRef(request);
|
|
49
|
+
requestRef.current = request;
|
|
48
50
|
const client = useFormClient();
|
|
49
51
|
const [label] = useState(() => authorLabel(() => localStorage, document.documentElement.dataset.workspace));
|
|
50
52
|
const [author, setAuthor] = useState(() => label.read("") || "maintainer");
|
|
@@ -52,8 +54,8 @@ function InlineOptions({ api, document: data, request }: { readonly api: ShellAp
|
|
|
52
54
|
() =>
|
|
53
55
|
client.runtime.fn(
|
|
54
56
|
Effect.fn("Board.answerOption")(function* (choice: string) {
|
|
55
|
-
const preview =
|
|
56
|
-
const prompt =
|
|
57
|
+
const preview = requestRef.current.preview;
|
|
58
|
+
const prompt = requestRef.current.prompts?.[0];
|
|
57
59
|
if (!(preview && prompt && author.trim())) {
|
|
58
60
|
return yield* Effect.fail("Add your local author label.");
|
|
59
61
|
}
|
|
@@ -75,7 +77,7 @@ function InlineOptions({ api, document: data, request }: { readonly api: ShellAp
|
|
|
75
77
|
return saved;
|
|
76
78
|
}),
|
|
77
79
|
),
|
|
78
|
-
[client, api, registry,
|
|
80
|
+
[client, api, registry, author, data.file, label.save],
|
|
79
81
|
);
|
|
80
82
|
const action = useAction(actionAtom);
|
|
81
83
|
const failure = Option.getOrUndefined(Option.flatMap(action.cause, Cause.findErrorOption));
|
|
@@ -102,7 +104,6 @@ function InlineOptions({ api, document: data, request }: { readonly api: ShellAp
|
|
|
102
104
|
key={option.id}
|
|
103
105
|
onChoose={() => action.dispatch(option.id)}
|
|
104
106
|
option={option}
|
|
105
|
-
reason={request.preview?.reason ?? ""}
|
|
106
107
|
recommended={data.metadata.recommended}
|
|
107
108
|
source={data.metadata.options?.[index]}
|
|
108
109
|
/>
|
|
@@ -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 { InboxProjection } from "#browser/views/schema.ts";
|
|
4
4
|
import { fileUrl } from "#files/url.ts";
|
|
5
5
|
import { inProgressItems } from "./inProgressItems.ts";
|
|
@@ -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">
|
|
@@ -82,3 +82,17 @@ function Later({ items }: { readonly items: InboxProjection["later"] }) {
|
|
|
82
82
|
</section>
|
|
83
83
|
);
|
|
84
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 { 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/
|
|
4
|
+
import { loadQuery } from "@shivaedev/effect-react/loadQuery.ts";
|
|
5
5
|
import type { ShellOptions } from "./BoardShell.tsx";
|
|
6
6
|
import { documentRoute } from "./documentRoute.ts";
|
|
7
7
|
import { loadProjection } from "./loadProjection.ts";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type * as AtomRegistry from "effect/reactivity/AtomRegistry";
|
|
2
|
-
import { loadQuery } from "@shivaedev/effect-react/
|
|
2
|
+
import { loadQuery } from "@shivaedev/effect-react/loadQuery.ts";
|
|
3
3
|
import { pageQueries } from "./pageQueries.ts";
|
|
4
4
|
import type { ProjectionRoute } from "./projectionRoute.ts";
|
|
5
5
|
import { routeQueryOptions } from "./routeQuery.ts";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type * as AtomRegistry from "effect/reactivity/AtomRegistry";
|
|
2
|
-
import { loadQuery } from "@shivaedev/effect-react/
|
|
2
|
+
import { loadQuery } from "@shivaedev/effect-react/loadQuery.ts";
|
|
3
3
|
import type { RemainingRoute } from "./remainingRoute.ts";
|
|
4
4
|
import { routeQueryOptions } from "./routeQuery.ts";
|
|
5
5
|
import type { ShellApi } from "./ShellApi.ts";
|
|
@@ -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";
|
|
@@ -31,7 +31,7 @@ export function chosenLabels(prompts: readonly Prompt[], answers: readonly Promp
|
|
|
31
31
|
return prompts
|
|
32
32
|
.flatMap((prompt) => {
|
|
33
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
|
|
34
|
+
return prompt.options.filter((option) => selected.includes(option.id)).map((option) => optionText(option.id, option.source).label);
|
|
35
35
|
})
|
|
36
36
|
.join(", ");
|
|
37
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";
|
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
|
+
}
|