@shivaedev/work-board 0.14.2 → 0.16.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 +26 -0
- package/README.md +1 -1
- package/assets/agent.js +1 -1
- package/assets/native.js +25 -25
- package/assets/react.css +1 -1
- package/assets/react.js +37 -36
- package/dist/browser/diagnostics/schema.d.ts +15 -12
- package/dist/browser/diagnostics/schema.d.ts.map +1 -1
- package/dist/browser/diagnostics/schema.js +12 -2
- package/dist/browser/diagnostics/schema.js.map +1 -1
- package/dist/browser/views/remaining.d.ts +20 -12
- package/dist/browser/views/remaining.d.ts.map +1 -1
- package/dist/client/client.d.ts +1 -0
- package/dist/client/client.d.ts.map +1 -1
- package/dist/client/client.js +1 -1
- package/dist/client/client.js.map +1 -1
- package/dist/client/document/Mermaid.d.ts.map +1 -1
- package/dist/client/document/Mermaid.js +4 -10
- package/dist/client/document/Mermaid.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 +12 -0
- package/dist/client/formStatus.d.ts.map +1 -0
- package/dist/client/formStatus.js +27 -0
- package/dist/client/formStatus.js.map +1 -0
- package/dist/client/react/BoardShell.d.ts.map +1 -1
- package/dist/client/react/BoardShell.js +36 -37
- package/dist/client/react/BoardShell.js.map +1 -1
- package/dist/client/react/DocumentContext.d.ts.map +1 -1
- package/dist/client/react/DocumentContext.js +12 -7
- package/dist/client/react/DocumentContext.js.map +1 -1
- package/dist/client/react/FormContext.d.ts +5 -0
- package/dist/client/react/FormContext.d.ts.map +1 -0
- package/dist/client/react/FormContext.js +10 -0
- package/dist/client/react/FormContext.js.map +1 -0
- package/dist/client/react/LiveControls.d.ts +4 -3
- package/dist/client/react/LiveControls.d.ts.map +1 -1
- package/dist/client/react/LiveControls.js +5 -24
- package/dist/client/react/LiveControls.js.map +1 -1
- package/dist/client/react/MainLocationContext.d.ts +3 -0
- package/dist/client/react/MainLocationContext.d.ts.map +1 -0
- package/dist/client/react/MainLocationContext.js +7 -0
- package/dist/client/react/MainLocationContext.js.map +1 -0
- package/dist/client/react/PhoneNavigation.d.ts +3 -1
- package/dist/client/react/PhoneNavigation.d.ts.map +1 -1
- package/dist/client/react/PhoneNavigation.js +8 -13
- package/dist/client/react/PhoneNavigation.js.map +1 -1
- package/dist/client/react/ProjectionFrame.d.ts +1 -2
- package/dist/client/react/ProjectionFrame.d.ts.map +1 -1
- package/dist/client/react/ProjectionFrame.js +10 -4
- package/dist/client/react/ProjectionFrame.js.map +1 -1
- package/dist/client/react/ReadingLibrary.d.ts.map +1 -1
- package/dist/client/react/ReadingLibrary.js +9 -14
- package/dist/client/react/ReadingLibrary.js.map +1 -1
- package/dist/client/react/ReadingReadyContext.d.ts +6 -0
- package/dist/client/react/ReadingReadyContext.d.ts.map +1 -0
- package/dist/client/react/ReadingReadyContext.js +7 -0
- package/dist/client/react/ReadingReadyContext.js.map +1 -0
- package/dist/client/react/SessionContext.d.ts +3 -0
- package/dist/client/react/SessionContext.d.ts.map +1 -0
- package/dist/client/react/SessionContext.js +3 -0
- package/dist/client/react/SessionContext.js.map +1 -0
- package/dist/client/react/SystemMain.d.ts.map +1 -1
- package/dist/client/react/SystemMain.js +3 -1
- package/dist/client/react/SystemMain.js.map +1 -1
- package/dist/client/react/SystemPage.d.ts.map +1 -1
- package/dist/client/react/SystemPage.js +2 -1
- package/dist/client/react/SystemPage.js.map +1 -1
- package/dist/client/react/ThemeContext.d.ts +2 -0
- package/dist/client/react/ThemeContext.d.ts.map +1 -0
- package/dist/client/react/ThemeContext.js +3 -0
- package/dist/client/react/ThemeContext.js.map +1 -0
- package/dist/client/react/TodayOptions.d.ts.map +1 -1
- package/dist/client/react/TodayOptions.js +23 -14
- package/dist/client/react/TodayOptions.js.map +1 -1
- package/dist/client/react/VisualViewer.d.ts.map +1 -1
- package/dist/client/react/VisualViewer.js +6 -5
- package/dist/client/react/VisualViewer.js.map +1 -1
- package/dist/client/react/boardRouter.d.ts +3 -2
- package/dist/client/react/boardRouter.d.ts.map +1 -1
- package/dist/client/react/boardRouter.js +2 -1
- package/dist/client/react/boardRouter.js.map +1 -1
- package/dist/client/react/createResponse.d.ts +32 -4
- package/dist/client/react/createResponse.d.ts.map +1 -1
- package/dist/client/react/createResponse.js +7 -28
- package/dist/client/react/createResponse.js.map +1 -1
- package/dist/client/react/diagnosticsValues.d.ts.map +1 -1
- package/dist/client/react/diagnosticsValues.js +5 -7
- package/dist/client/react/diagnosticsValues.js.map +1 -1
- package/dist/client/react/entry.d.ts.map +1 -1
- package/dist/client/react/entry.js +2 -34
- package/dist/client/react/entry.js.map +1 -1
- package/dist/client/react/githubLimited.d.ts +3 -0
- package/dist/client/react/githubLimited.d.ts.map +1 -0
- package/dist/client/react/githubLimited.js +4 -0
- package/dist/client/react/githubLimited.js.map +1 -0
- package/dist/client/react/loadProjection.d.ts +135 -135
- package/dist/client/react/loadRemaining.d.ts +20 -12
- package/dist/client/react/loadRemaining.d.ts.map +1 -1
- package/dist/client/react/mainLocation.d.ts +4 -2
- package/dist/client/react/mainLocation.d.ts.map +1 -1
- package/dist/client/react/mainLocation.js +1 -8
- package/dist/client/react/mainLocation.js.map +1 -1
- package/dist/client/react/mountBoard.d.ts +3 -0
- package/dist/client/react/mountBoard.d.ts.map +1 -0
- package/dist/client/react/mountBoard.js +45 -0
- package/dist/client/react/mountBoard.js.map +1 -0
- package/dist/client/react/navigationLocation.d.ts +3 -1
- package/dist/client/react/navigationLocation.d.ts.map +1 -1
- package/dist/client/react/navigationLocation.js +11 -1
- package/dist/client/react/navigationLocation.js.map +1 -1
- package/dist/client/react/retryPage.d.ts +2 -1
- package/dist/client/react/retryPage.d.ts.map +1 -1
- package/dist/client/react/retryPage.js +3 -4
- package/dist/client/react/retryPage.js.map +1 -1
- package/dist/client/react/scheduleReading.d.ts.map +1 -1
- package/dist/client/react/scheduleReading.js +1 -10
- package/dist/client/react/scheduleReading.js.map +1 -1
- package/dist/client/react/useAppearance.d.ts +2 -1
- package/dist/client/react/useAppearance.d.ts.map +1 -1
- package/dist/client/react/useAppearance.js +1 -2
- package/dist/client/react/useAppearance.js.map +1 -1
- package/dist/client/react/useBoardNavigation.d.ts +1 -1
- package/dist/client/react/useBoardNavigation.d.ts.map +1 -1
- package/dist/client/react/useBoardNavigation.js +28 -22
- package/dist/client/react/useBoardNavigation.js.map +1 -1
- package/dist/client/react/useDiagnosticsPolling.d.ts +4 -1
- package/dist/client/react/useDiagnosticsPolling.d.ts.map +1 -1
- package/dist/client/react/useDiagnosticsPolling.js +9 -7
- package/dist/client/react/useDiagnosticsPolling.js.map +1 -1
- package/dist/client/react/useDocumentPage.d.ts +1 -1
- package/dist/client/react/useDocumentPage.d.ts.map +1 -1
- package/dist/client/react/useDocumentPage.js +9 -6
- package/dist/client/react/useDocumentPage.js.map +1 -1
- package/dist/client/react/useHandoffForm.d.ts +56 -30
- package/dist/client/react/useHandoffForm.d.ts.map +1 -1
- package/dist/client/react/useHandoffForm.js +19 -30
- package/dist/client/react/useHandoffForm.js.map +1 -1
- package/dist/client/react/useResponseForm.d.ts +72 -43
- package/dist/client/react/useResponseForm.d.ts.map +1 -1
- package/dist/client/react/useResponseForm.js +20 -17
- package/dist/client/react/useResponseForm.js.map +1 -1
- package/dist/client/react/useSidebar.d.ts.map +1 -1
- package/dist/client/react/useSidebar.js +5 -3
- package/dist/client/react/useSidebar.js.map +1 -1
- package/dist/client/react/visualSources.d.ts.map +1 -1
- package/dist/client/react/visualSources.js +10 -8
- package/dist/client/react/visualSources.js.map +1 -1
- package/dist/client/saveFailure.d.ts +8 -0
- package/dist/client/saveFailure.d.ts.map +1 -0
- package/dist/client/saveFailure.js +8 -0
- package/dist/client/saveFailure.js.map +1 -0
- package/dist/diagnostics/daemonContract.d.ts +65 -38
- package/dist/diagnostics/daemonContract.d.ts.map +1 -1
- package/dist/diagnostics/daemonContract.js +2 -1
- package/dist/diagnostics/daemonContract.js.map +1 -1
- package/dist/diagnostics/health.d.ts +10 -0
- package/dist/diagnostics/health.d.ts.map +1 -1
- package/dist/diagnostics/health.js +2 -0
- package/dist/diagnostics/health.js.map +1 -1
- package/dist/diagnostics/source.d.ts +55 -36
- package/dist/diagnostics/source.d.ts.map +1 -1
- package/dist/diagnostics/source.js +1 -2
- package/dist/diagnostics/source.js.map +1 -1
- package/dist/render/{failed.d.ts → Failed.d.ts} +1 -1
- package/dist/render/Failed.d.ts.map +1 -0
- package/dist/render/{failed.js → Failed.js} +1 -1
- package/dist/render/Failed.js.map +1 -0
- package/dist/render/code.d.ts +2 -2
- package/dist/render/code.d.ts.map +1 -1
- package/dist/render/code.js +15 -13
- package/dist/render/code.js.map +1 -1
- package/dist/render/footnoteIds.d.ts +3 -0
- package/dist/render/footnoteIds.d.ts.map +1 -0
- package/dist/render/{footnotes.js → footnoteIds.js} +11 -9
- package/dist/render/footnoteIds.js.map +1 -0
- package/dist/rpc/contract.d.ts +2346 -2322
- package/dist/rpc/contract.d.ts.map +1 -1
- package/dist/rpc/handoffContract.d.ts +33 -33
- package/dist/rpc/responseContract.d.ts +56 -56
- package/dist/server/board.d.ts +1 -1
- package/dist/server/diagnostics/daemon.d.ts +41 -25
- package/dist/server/diagnostics/daemon.d.ts.map +1 -1
- package/dist/server/diagnostics/fleetHealth.d.ts +10 -0
- package/dist/server/diagnostics/fleetHealth.d.ts.map +1 -1
- package/dist/server/diagnostics/githubLimits.d.ts +12 -13
- package/dist/server/diagnostics/githubLimits.d.ts.map +1 -1
- package/dist/server/diagnostics/githubLimits.js +6 -6
- package/dist/server/diagnostics/githubLimits.js.map +1 -1
- package/dist/server/integration/diagnostics.d.ts +74 -38
- package/dist/server/integration/diagnostics.d.ts.map +1 -1
- package/dist/server/mcp/client.d.ts +1155 -1147
- package/dist/server/mcp/client.d.ts.map +1 -1
- package/dist/server/mcp/queries.d.ts +60 -36
- package/dist/server/mcp/queries.d.ts.map +1 -1
- package/dist/server/metadata/fields.js +1 -1
- package/dist/server/metadata/fields.js.map +1 -1
- package/dist/server/packageInfo.d.ts +1 -1
- package/dist/server/packageInfo.js +1 -1
- package/dist/server/render/Highlighter.d.ts +1 -1
- package/dist/server/render/Highlighter.js +1 -1
- package/dist/server/render/markdown.d.ts +1 -1
- package/dist/server/render/markdown.js +2 -2
- package/dist/server/render/markdown.js.map +1 -1
- package/dist/server/serve.d.ts +1 -1
- package/dist/server/views/remainingQueries.d.ts +20 -12
- package/dist/server/views/remainingQueries.d.ts.map +1 -1
- package/package.json +9 -39
- package/src/browser/diagnostics/schema.ts +12 -2
- package/src/client/client.ts +2 -1
- package/src/client/document/Mermaid.ts +4 -10
- package/src/client/document/View.ts +1 -1
- package/src/client/formStatus.ts +35 -0
- package/src/client/react/BoardShell.tsx +49 -43
- package/src/client/react/DocumentContext.tsx +12 -7
- package/src/client/react/FormContext.ts +13 -0
- package/src/client/react/LiveControls.tsx +5 -38
- package/src/client/react/MainLocationContext.ts +8 -0
- package/src/client/react/PhoneNavigation.tsx +20 -15
- package/src/client/react/ProjectionFrame.tsx +13 -5
- package/src/client/react/ReadingLibrary.tsx +9 -14
- package/src/client/react/ReadingReadyContext.ts +11 -0
- package/src/client/react/SessionContext.ts +4 -0
- package/src/client/react/SystemMain.tsx +3 -1
- package/src/client/react/SystemPage.tsx +2 -1
- package/src/client/react/ThemeContext.ts +3 -0
- package/src/client/react/TodayOptions.tsx +40 -33
- package/src/client/react/VisualViewer.tsx +6 -6
- package/src/client/react/boardRouter.ts +3 -2
- package/src/client/react/createResponse.ts +10 -35
- package/src/client/react/diagnosticsValues.tsx +20 -12
- package/src/client/react/entry.tsx +2 -37
- package/src/client/react/githubLimited.ts +5 -0
- package/src/client/react/mainLocation.ts +5 -8
- package/src/client/react/mountBoard.tsx +49 -0
- package/src/client/react/navigationLocation.ts +14 -2
- package/src/client/react/retryPage.ts +4 -4
- package/src/client/react/scheduleReading.ts +1 -10
- package/src/client/react/useAppearance.ts +2 -3
- package/src/client/react/useBoardNavigation.ts +30 -23
- package/src/client/react/useDiagnosticsPolling.ts +14 -7
- package/src/client/react/useDocumentPage.ts +12 -6
- package/src/client/react/useHandoffForm.ts +25 -33
- package/src/client/react/useResponseForm.ts +20 -18
- package/src/client/react/useSidebar.ts +5 -3
- package/src/client/react/visualSources.ts +10 -8
- package/src/client/saveFailure.ts +14 -0
- package/src/diagnostics/daemonContract.ts +2 -1
- package/src/diagnostics/health.ts +2 -0
- package/src/diagnostics/source.ts +1 -2
- package/src/render/code.ts +3 -4
- package/src/render/{footnotes.ts → footnoteIds.ts} +12 -10
- package/src/server/board.ts +1 -1
- package/src/server/diagnostics/githubLimits.ts +9 -11
- package/src/server/metadata/fields.ts +1 -1
- package/src/server/packageInfo.ts +1 -1
- package/src/server/render/Highlighter.ts +1 -1
- package/src/server/render/markdown.ts +2 -2
- package/dist/client/handoffs/failureStatus.d.ts +0 -2
- package/dist/client/handoffs/failureStatus.d.ts.map +0 -1
- package/dist/client/handoffs/failureStatus.js +0 -7
- package/dist/client/handoffs/failureStatus.js.map +0 -1
- package/dist/client/responses/failure.d.ts +0 -5
- package/dist/client/responses/failure.d.ts.map +0 -1
- package/dist/client/responses/failure.js +0 -13
- package/dist/client/responses/failure.js.map +0 -1
- package/dist/render/failed.d.ts.map +0 -1
- package/dist/render/failed.js.map +0 -1
- package/dist/render/footnotes.d.ts +0 -3
- package/dist/render/footnotes.d.ts.map +0 -1
- package/dist/render/footnotes.js.map +0 -1
- package/dist/server/diagnostics/readCommand.d.ts +0 -4
- package/dist/server/diagnostics/readCommand.d.ts.map +0 -1
- package/dist/server/diagnostics/readCommand.js +0 -20
- package/dist/server/diagnostics/readCommand.js.map +0 -1
- package/src/client/handoffs/failureStatus.ts +0 -6
- package/src/client/responses/failure.ts +0 -13
- package/src/server/diagnostics/readCommand.ts +0 -24
- /package/src/render/{failed.ts → Failed.ts} +0 -0
|
@@ -1,21 +1,26 @@
|
|
|
1
1
|
import { useRouterState } from "@tanstack/react-router";
|
|
2
2
|
import { Option } from "effect";
|
|
3
3
|
import type * as AtomRegistry from "effect/reactivity/AtomRegistry";
|
|
4
|
-
import { useCallback, useEffect, useState } from "react";
|
|
4
|
+
import { useCallback, useContext, useEffect, useState } from "react";
|
|
5
5
|
import { useQuery } from "@shivaedev/effect-react/result-state.ts";
|
|
6
6
|
import type { BrowserClient } from "#client/client.ts";
|
|
7
7
|
import { navigationQuery } from "#client/page/routeQuery.ts";
|
|
8
8
|
import { AgeTicker } from "./Age.tsx";
|
|
9
9
|
import { FleetMain } from "./FleetMain.tsx";
|
|
10
|
+
import { FormContext } from "./FormContext.ts";
|
|
10
11
|
import { Header } from "./Header.tsx";
|
|
11
12
|
import { LiveControls } from "./LiveControls.tsx";
|
|
12
|
-
import {
|
|
13
|
+
import { MainLocationContext, useMainLocation } from "./MainLocationContext.ts";
|
|
14
|
+
import { emptyMainLocation, type MainLocation } from "./mainLocation.ts";
|
|
13
15
|
import { PhoneNavigation } from "./PhoneNavigation.tsx";
|
|
14
16
|
import { ReadingLibrary } from "./ReadingLibrary.tsx";
|
|
17
|
+
import { ReadingReadyContext, readingReady } from "./ReadingReadyContext.ts";
|
|
15
18
|
import { SearchDialog } from "./SearchDialog.tsx";
|
|
19
|
+
import { SessionContext } from "./SessionContext.ts";
|
|
16
20
|
import type { ShellApi } from "./ShellApi.ts";
|
|
17
21
|
import { Sidebar } from "./Sidebar.tsx";
|
|
18
22
|
import { shellIds } from "./shellIds.ts";
|
|
23
|
+
import { ThemeContext } from "./ThemeContext.ts";
|
|
19
24
|
import { UpdatesToggle } from "./UpdatesToggle.tsx";
|
|
20
25
|
import { useAppearance } from "./useAppearance.ts";
|
|
21
26
|
import { useBoardNavigation } from "./useBoardNavigation.ts";
|
|
@@ -30,61 +35,61 @@ export interface ShellOptions {
|
|
|
30
35
|
readonly registry: AtomRegistry.AtomRegistry;
|
|
31
36
|
}
|
|
32
37
|
export function BoardShell(options: ShellOptions) {
|
|
33
|
-
const
|
|
38
|
+
const [main, onPage] = useState(emptyMainLocation);
|
|
34
39
|
const appearance = useAppearance();
|
|
40
|
+
const [element, setElement] = useState<HTMLElement | null>(null);
|
|
41
|
+
const report = useCallback((next: HTMLElement, ready: boolean) => {
|
|
42
|
+
setElement((current) => {
|
|
43
|
+
if (ready) {
|
|
44
|
+
return next;
|
|
45
|
+
}
|
|
46
|
+
return current === next ? null : current;
|
|
47
|
+
});
|
|
48
|
+
}, []);
|
|
49
|
+
return (
|
|
50
|
+
<MainLocationContext.Provider value={main}>
|
|
51
|
+
<ReadingReadyContext.Provider value={{ element, report }}>
|
|
52
|
+
<SessionContext.Provider value={options.client}>
|
|
53
|
+
<ThemeContext.Provider value={appearance.scheme}>
|
|
54
|
+
<BoardWorkspace {...options} appearance={appearance} onPage={onPage} />
|
|
55
|
+
</ThemeContext.Provider>
|
|
56
|
+
</SessionContext.Provider>
|
|
57
|
+
</ReadingReadyContext.Provider>
|
|
58
|
+
</MainLocationContext.Provider>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
function BoardWorkspace(
|
|
62
|
+
options: ShellOptions & { readonly appearance: ReturnType<typeof useAppearance>; readonly onPage: (location: MainLocation) => void },
|
|
63
|
+
) {
|
|
64
|
+
const { api, ready, client, onPage, appearance } = options;
|
|
65
|
+
const main = useMainLocation();
|
|
35
66
|
const sidebar = useSidebar(appearance);
|
|
36
|
-
const
|
|
67
|
+
const [phoneOpen, setPhoneOpen] = useState(false);
|
|
68
|
+
const closeCurrentDestination = useCallback(() => {
|
|
69
|
+
sidebar.close();
|
|
70
|
+
setPhoneOpen(false);
|
|
71
|
+
}, [sidebar.close]);
|
|
72
|
+
const status = useBoardNavigation(options, closeCurrentDestination);
|
|
37
73
|
const search = useSearchDialog();
|
|
38
74
|
const [libraryOpen, setLibraryOpen] = useState(false);
|
|
39
75
|
const location = useRouterState({ select: (state) => state.location });
|
|
40
|
-
const [main, setMain] = useState<MainLocation>({ file: "", view: "" });
|
|
41
76
|
const query = useQuery(navigationQuery(api), { keepPreviousData: true });
|
|
42
77
|
const data = Option.getOrUndefined(query.data);
|
|
43
|
-
const
|
|
78
|
+
const { element } = useContext(ReadingReadyContext);
|
|
44
79
|
useEffect(() => {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
if (data && doc?.dataset.route && (!context || context.dataset.readingReady === "true")) {
|
|
49
|
-
ready?.();
|
|
50
|
-
}
|
|
80
|
+
const doc = document.getElementById("doc");
|
|
81
|
+
if (data && main.route && doc && readingReady(doc, element)) {
|
|
82
|
+
ready?.();
|
|
51
83
|
}
|
|
52
|
-
|
|
53
|
-
document.addEventListener("board-page", start);
|
|
54
|
-
document.addEventListener("board-reading-ready", start);
|
|
55
|
-
return () => {
|
|
56
|
-
document.removeEventListener("board-page", start);
|
|
57
|
-
document.removeEventListener("board-reading-ready", start);
|
|
58
|
-
};
|
|
59
|
-
}, [ready, data]);
|
|
60
|
-
useEffect(() => {
|
|
61
|
-
function sync() {
|
|
62
|
-
const doc = document.getElementById("doc");
|
|
63
|
-
if (doc) {
|
|
64
|
-
onPage(displayedLocation(doc));
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
document.addEventListener("board-page", sync);
|
|
68
|
-
return () => document.removeEventListener("board-page", sync);
|
|
69
|
-
}, [onPage]);
|
|
70
|
-
useEffect(() => {
|
|
71
|
-
if (data) {
|
|
72
|
-
document.dispatchEvent(new Event("board-index-change"));
|
|
73
|
-
}
|
|
74
|
-
}, [data]);
|
|
84
|
+
}, [ready, data, main, element]);
|
|
75
85
|
const live = client ? (
|
|
76
|
-
<LiveControls
|
|
77
|
-
client={client}
|
|
78
|
-
current={location.pathname + location.searchStr}
|
|
79
|
-
navigationFailed={Option.isSome(query.cause)}
|
|
80
|
-
navigationPending={query.pending}
|
|
81
|
-
/>
|
|
86
|
+
<LiveControls client={client} navigationFailed={Option.isSome(query.cause)} navigationPending={query.pending} />
|
|
82
87
|
) : (
|
|
83
88
|
<span className="font-mono text-muted-foreground text-xs" id={shellIds.live} role="status">
|
|
84
89
|
connecting
|
|
85
90
|
</span>
|
|
86
91
|
);
|
|
87
|
-
|
|
92
|
+
const content = (
|
|
88
93
|
<AgeTicker>
|
|
89
94
|
<ReadingLibrary api={api} close={() => setLibraryOpen(false)} open={libraryOpen}>
|
|
90
95
|
<a className="sr-only rounded-md bg-background p-3 focus:not-sr-only focus:absolute focus:z-50" href="#doc">
|
|
@@ -117,7 +122,7 @@ export function BoardShell(options: ShellOptions) {
|
|
|
117
122
|
updates={client ? <UpdatesToggle client={client} /> : null}
|
|
118
123
|
/>
|
|
119
124
|
<FleetMain api={api} home={data?.home} onPage={onPage} url={location.pathname + location.searchStr} />
|
|
120
|
-
<PhoneNavigation main={main} openSearch={search.show} />
|
|
125
|
+
<PhoneNavigation main={main} more={phoneOpen} onMoreChange={setPhoneOpen} openSearch={search.show} />
|
|
121
126
|
</div>
|
|
122
127
|
</div>
|
|
123
128
|
{Option.isSome(query.cause) && <p role="status">Navigation refresh failed. Your displayed files are retained.</p>}
|
|
@@ -133,4 +138,5 @@ export function BoardShell(options: ShellOptions) {
|
|
|
133
138
|
</ReadingLibrary>
|
|
134
139
|
</AgeTicker>
|
|
135
140
|
);
|
|
141
|
+
return client ? <FormContext.Provider value={client}>{content}</FormContext.Provider> : content;
|
|
136
142
|
}
|
|
@@ -1,15 +1,22 @@
|
|
|
1
|
-
import { useEffect, useLayoutEffect, useRef, useState } from "react";
|
|
1
|
+
import { useContext, useEffect, useLayoutEffect, useRef, useState } from "react";
|
|
2
2
|
import type { Document } from "#browser/document/schema.ts";
|
|
3
3
|
import { Tabs } from "#client/react/components/ui/Tabs.tsx";
|
|
4
4
|
import { Age } from "./Age.tsx";
|
|
5
5
|
import { DocumentMetadata } from "./DocumentMetadata.tsx";
|
|
6
6
|
import { DocumentSource } from "./DocumentSource.tsx";
|
|
7
|
+
import { useMainLocation } from "./MainLocationContext.ts";
|
|
8
|
+
import { ReadingReadyContext } from "./ReadingReadyContext.ts";
|
|
7
9
|
|
|
8
10
|
export function DocumentContext({ document: data }: { readonly document: Document }) {
|
|
11
|
+
const page = useMainLocation();
|
|
12
|
+
const { report } = useContext(ReadingReadyContext);
|
|
9
13
|
const [headings, setHeadings] = useState<readonly { readonly id: string; readonly title: string; readonly depth: number }[]>([]);
|
|
10
14
|
const contextRef = useRef<HTMLElement>(null);
|
|
11
15
|
const [tab, setTab] = useState("details");
|
|
12
16
|
useEffect(() => {
|
|
17
|
+
if (page.route && page.projection === "document" && page.file !== data.file) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
13
20
|
function outline() {
|
|
14
21
|
const entries = [
|
|
15
22
|
...(contextRef.current
|
|
@@ -21,10 +28,8 @@ export function DocumentContext({ document: data }: { readonly document: Documen
|
|
|
21
28
|
.map((heading) => ({ depth: Number(heading.tagName.slice(1)), id: heading.id, title: heading.textContent?.replace(/#$/u, "") ?? "" }));
|
|
22
29
|
setHeadings((current) => (JSON.stringify(current) === JSON.stringify(entries) ? current : entries));
|
|
23
30
|
}
|
|
24
|
-
document.addEventListener("board-page", outline);
|
|
25
31
|
outline();
|
|
26
|
-
|
|
27
|
-
}, []);
|
|
32
|
+
}, [page, data]);
|
|
28
33
|
|
|
29
34
|
useLayoutEffect(() => {
|
|
30
35
|
const context = contextRef.current;
|
|
@@ -39,9 +44,9 @@ export function DocumentContext({ document: data }: { readonly document: Documen
|
|
|
39
44
|
if (count !== headings.length) {
|
|
40
45
|
return;
|
|
41
46
|
}
|
|
42
|
-
context
|
|
43
|
-
|
|
44
|
-
}, [headings]);
|
|
47
|
+
report(context, true);
|
|
48
|
+
return () => report(context, false);
|
|
49
|
+
}, [headings, report]);
|
|
45
50
|
const tabs = [
|
|
46
51
|
{ content: <Outline headings={headings} />, label: "Details", value: "details" },
|
|
47
52
|
{ content: <SourcePanel document={data} />, label: "Source", value: "source" },
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { createContext, useContext } from "react";
|
|
2
|
+
import type { BrowserClient } from "#client/client.ts";
|
|
3
|
+
|
|
4
|
+
export type FormClient = Pick<BrowserClient, "handoffs" | "responses" | "runtime">;
|
|
5
|
+
export const FormContext = createContext<FormClient | undefined>(undefined);
|
|
6
|
+
|
|
7
|
+
export function useFormClient() {
|
|
8
|
+
const client = useContext(FormContext);
|
|
9
|
+
if (!client) {
|
|
10
|
+
throw new Error("Board forms require the shell's RPC client");
|
|
11
|
+
}
|
|
12
|
+
return client;
|
|
13
|
+
}
|
|
@@ -1,26 +1,21 @@
|
|
|
1
1
|
import { useAtomValue } from "@effect/atom-react";
|
|
2
2
|
import { Option } from "effect";
|
|
3
3
|
import * as Atom from "effect/reactivity/Atom";
|
|
4
|
-
import {
|
|
4
|
+
import { useMemo } from "react";
|
|
5
5
|
import { useQuery } from "@shivaedev/effect-react/result-state.ts";
|
|
6
6
|
import type { BrowserClient } from "#client/client.ts";
|
|
7
7
|
import { activeRead } from "./activeRead.ts";
|
|
8
8
|
import { liveStatus } from "./liveStatus.ts";
|
|
9
|
+
import { useMainLocation } from "./MainLocationContext.ts";
|
|
9
10
|
import { shellIds } from "./shellIds.ts";
|
|
10
11
|
|
|
11
12
|
const opening = Atom.make({ failed: false, pending: true });
|
|
12
|
-
interface Page {
|
|
13
|
-
readonly kind: string;
|
|
14
|
-
readonly url: string;
|
|
15
|
-
}
|
|
16
13
|
export function LiveControls({
|
|
17
14
|
client,
|
|
18
|
-
current,
|
|
19
15
|
navigationPending,
|
|
20
16
|
navigationFailed,
|
|
21
17
|
}: {
|
|
22
|
-
readonly client: BrowserClient;
|
|
23
|
-
readonly current: string;
|
|
18
|
+
readonly client: Pick<BrowserClient, "api" | "registry"> & { readonly updates: Pick<BrowserClient["updates"], "paused" | "status"> };
|
|
24
19
|
readonly navigationPending: boolean;
|
|
25
20
|
readonly navigationFailed: boolean;
|
|
26
21
|
}) {
|
|
@@ -28,23 +23,8 @@ export function LiveControls({
|
|
|
28
23
|
const paused = useAtomValue(client.updates.paused);
|
|
29
24
|
const watcher = useQuery(client.api.watcher.query());
|
|
30
25
|
const watching = Option.getOrUndefined(watcher.data)?.watching === true;
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
function sync() {
|
|
34
|
-
const doc = document.getElementById("doc");
|
|
35
|
-
if (doc?.dataset.route !== current) {
|
|
36
|
-
return;
|
|
37
|
-
}
|
|
38
|
-
setPage({
|
|
39
|
-
kind: doc?.dataset.projection ?? "document",
|
|
40
|
-
url: current,
|
|
41
|
-
});
|
|
42
|
-
}
|
|
43
|
-
sync();
|
|
44
|
-
document.addEventListener("board-page", sync);
|
|
45
|
-
return () => document.removeEventListener("board-page", sync);
|
|
46
|
-
}, [current]);
|
|
47
|
-
const atom = useMemo(() => (page ? activeRead(client.api, page.kind, page.url) : opening), [client, page]);
|
|
26
|
+
const page = useMainLocation();
|
|
27
|
+
const atom = useMemo(() => (page.route ? activeRead(client.api, page.projection, page.route) : opening), [client, page.projection, page.route]);
|
|
48
28
|
const reading = useAtomValue(atom);
|
|
49
29
|
const displayed = liveStatus({
|
|
50
30
|
connection: status.connection,
|
|
@@ -55,19 +35,6 @@ export function LiveControls({
|
|
|
55
35
|
waiting: navigationPending || reading.pending,
|
|
56
36
|
watching,
|
|
57
37
|
});
|
|
58
|
-
useEffect(
|
|
59
|
-
() =>
|
|
60
|
-
client.registry.subscribe(
|
|
61
|
-
client.updates.paused,
|
|
62
|
-
(value) => {
|
|
63
|
-
if (!value) {
|
|
64
|
-
document.dispatchEvent(new Event("board-updates-resumed"));
|
|
65
|
-
}
|
|
66
|
-
},
|
|
67
|
-
{ immediate: true },
|
|
68
|
-
),
|
|
69
|
-
[client],
|
|
70
|
-
);
|
|
71
38
|
return (
|
|
72
39
|
<span
|
|
73
40
|
className="font-mono text-muted-foreground text-xs data-[state=down]:text-destructive data-[state=stale]:text-destructive"
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { createContext, useContext } from "react";
|
|
2
|
+
import { emptyMainLocation } from "./mainLocation.ts";
|
|
3
|
+
|
|
4
|
+
export const MainLocationContext = createContext(emptyMainLocation);
|
|
5
|
+
|
|
6
|
+
export function useMainLocation() {
|
|
7
|
+
return useContext(MainLocationContext);
|
|
8
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { useRouter } from "@tanstack/react-router";
|
|
1
2
|
import { CalendarDays, Ellipsis, Flag, Search, Zap } from "lucide-react";
|
|
2
|
-
import { useEffect
|
|
3
|
+
import { useEffect } from "react";
|
|
3
4
|
import { Button } from "#client/react/components/ui/Button.tsx";
|
|
4
5
|
import { Modal } from "./Modal.tsx";
|
|
5
6
|
import type { MainLocation } from "./mainLocation.ts";
|
|
@@ -11,21 +12,25 @@ const icons = [CalendarDays, Flag, Zap];
|
|
|
11
12
|
const TAB_CLASS =
|
|
12
13
|
"flex min-h-12 min-w-0 flex-col items-center justify-center gap-1 rounded-md border-0 bg-transparent p-0 text-foreground text-xs no-underline aria-[current=page]:bg-accent aria-[current=page]:text-primary";
|
|
13
14
|
|
|
14
|
-
export function PhoneNavigation({
|
|
15
|
-
|
|
15
|
+
export function PhoneNavigation({
|
|
16
|
+
main,
|
|
17
|
+
more,
|
|
18
|
+
onMoreChange,
|
|
19
|
+
openSearch,
|
|
20
|
+
}: {
|
|
21
|
+
readonly main: MainLocation;
|
|
22
|
+
readonly more: boolean;
|
|
23
|
+
readonly onMoreChange: (open: boolean) => void;
|
|
24
|
+
readonly openSearch: () => void;
|
|
25
|
+
}) {
|
|
26
|
+
const router = useRouter();
|
|
16
27
|
const narrow = useMedia("(max-width: 767px)");
|
|
17
28
|
useEffect(() => {
|
|
18
29
|
if (!narrow) {
|
|
19
|
-
|
|
30
|
+
onMoreChange(false);
|
|
20
31
|
}
|
|
21
|
-
}, [narrow]);
|
|
22
|
-
useEffect(() =>
|
|
23
|
-
function close() {
|
|
24
|
-
setMore(false);
|
|
25
|
-
}
|
|
26
|
-
document.addEventListener("board-navigate", close);
|
|
27
|
-
return () => document.removeEventListener("board-navigate", close);
|
|
28
|
-
}, []);
|
|
32
|
+
}, [narrow, onMoreChange]);
|
|
33
|
+
useEffect(() => router.subscribe("onBeforeNavigate", () => onMoreChange(false)), [router, onMoreChange]);
|
|
29
34
|
return (
|
|
30
35
|
<>
|
|
31
36
|
<nav
|
|
@@ -50,19 +55,19 @@ export function PhoneNavigation({ main, openSearch }: { readonly main: MainLocat
|
|
|
50
55
|
aria-expanded={more}
|
|
51
56
|
aria-haspopup="dialog"
|
|
52
57
|
className={TAB_CLASS}
|
|
53
|
-
onClick={() =>
|
|
58
|
+
onClick={() => onMoreChange(true)}
|
|
54
59
|
type="button"
|
|
55
60
|
>
|
|
56
61
|
<Ellipsis aria-hidden="true" className="size-5" />
|
|
57
62
|
More
|
|
58
63
|
</button>
|
|
59
64
|
</nav>
|
|
60
|
-
<Modal className="phone-more" close={() =>
|
|
65
|
+
<Modal className="phone-more" close={() => onMoreChange(false)} id={shellIds.phoneMore} open={more} title={shellIds.phoneMoreTitle}>
|
|
61
66
|
<div className="mb-3 flex items-center justify-between">
|
|
62
67
|
<h2 className="font-semibold" id={shellIds.phoneMoreTitle}>
|
|
63
68
|
More
|
|
64
69
|
</h2>
|
|
65
|
-
<Button onClick={() =>
|
|
70
|
+
<Button onClick={() => onMoreChange(false)} variant="ghost">
|
|
66
71
|
Close
|
|
67
72
|
</Button>
|
|
68
73
|
</div>
|
|
@@ -21,7 +21,7 @@ export function ProjectionFrame<A, E>({
|
|
|
21
21
|
url,
|
|
22
22
|
}: {
|
|
23
23
|
readonly atom: Atom.Atom<AsyncResult.AsyncResult<A, E>>;
|
|
24
|
-
readonly location: (data: A) => MainLocation & { readonly identity?: string | undefined
|
|
24
|
+
readonly location: (data: A) => Omit<MainLocation, "route" | "projection"> & { readonly identity?: string | undefined };
|
|
25
25
|
readonly onPage: (location: MainLocation) => void;
|
|
26
26
|
readonly render: (data: A) => ReactNode;
|
|
27
27
|
readonly source?: (data: A) => string | undefined;
|
|
@@ -36,7 +36,7 @@ export function ProjectionFrame<A, E>({
|
|
|
36
36
|
const failed = Option.isSome(query.cause);
|
|
37
37
|
useLayoutEffect(() => {
|
|
38
38
|
const element = hostRef.current;
|
|
39
|
-
if (element
|
|
39
|
+
if (element) {
|
|
40
40
|
delete element.dataset.route;
|
|
41
41
|
}
|
|
42
42
|
if (!element || query.pending || (data === undefined && !failed)) {
|
|
@@ -56,8 +56,16 @@ export function ProjectionFrame<A, E>({
|
|
|
56
56
|
}
|
|
57
57
|
renderedRef.current = projectionChanges(mainElement, renderedRef.current, url);
|
|
58
58
|
mainElement.dataset.route = url;
|
|
59
|
-
flushSync(() =>
|
|
60
|
-
|
|
59
|
+
flushSync(() =>
|
|
60
|
+
onPage({
|
|
61
|
+
file: main.file,
|
|
62
|
+
projection: route,
|
|
63
|
+
route: url,
|
|
64
|
+
status: main.status,
|
|
65
|
+
title: mainElement.querySelector("h1")?.textContent?.trim(),
|
|
66
|
+
view: main.view,
|
|
67
|
+
}),
|
|
68
|
+
);
|
|
61
69
|
});
|
|
62
70
|
}
|
|
63
71
|
const observer = new MutationObserver((records) => {
|
|
@@ -78,7 +86,7 @@ export function ProjectionFrame<A, E>({
|
|
|
78
86
|
active = false;
|
|
79
87
|
observer.disconnect();
|
|
80
88
|
};
|
|
81
|
-
}, [data, failed, query.pending, expectedSource, main.file, main.view, onPage, url]);
|
|
89
|
+
}, [data, failed, query.pending, expectedSource, main.file, main.status, main.view, onPage, route, url]);
|
|
82
90
|
return (
|
|
83
91
|
<>
|
|
84
92
|
<main
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { type ReactNode, useCallback,
|
|
1
|
+
import { type ReactNode, useCallback, useLayoutEffect, useRef, useState } from "react";
|
|
2
2
|
import { LibraryContext, restoreLibrary } from "#client/react/LibraryContext.ts";
|
|
3
|
+
import { useMainLocation } from "./MainLocationContext.ts";
|
|
3
4
|
import { Modal } from "./Modal.tsx";
|
|
4
5
|
import { ReadingHistory } from "./ReadingHistory.tsx";
|
|
5
6
|
import type { ShellApi } from "./ShellApi.ts";
|
|
@@ -16,6 +17,7 @@ export function ReadingLibrary({
|
|
|
16
17
|
readonly close: () => void;
|
|
17
18
|
readonly children: ReactNode;
|
|
18
19
|
}) {
|
|
20
|
+
const page = useMainLocation();
|
|
19
21
|
const key = `work-board:library:${document.documentElement.dataset.workspace}`;
|
|
20
22
|
const [saved, setSaved] = useState(() => restoreLibrary(key));
|
|
21
23
|
const [current, setCurrent] = useState("");
|
|
@@ -34,18 +36,17 @@ export function ReadingLibrary({
|
|
|
34
36
|
},
|
|
35
37
|
[key],
|
|
36
38
|
);
|
|
37
|
-
|
|
39
|
+
useLayoutEffect(() => {
|
|
38
40
|
function sync() {
|
|
39
|
-
|
|
40
|
-
if (!doc?.dataset.route) {
|
|
41
|
+
if (!page.route) {
|
|
41
42
|
return;
|
|
42
43
|
}
|
|
43
|
-
const file =
|
|
44
|
-
const title =
|
|
44
|
+
const file = page.file;
|
|
45
|
+
const title = page.title || file;
|
|
45
46
|
document.title = title ? `${title} · Work Board` : "Work Board";
|
|
46
47
|
|
|
47
48
|
setCurrent(file);
|
|
48
|
-
if (file &&
|
|
49
|
+
if (file && page.view === "" && visitedRef.current !== `${file}:${title}`) {
|
|
49
50
|
visitedRef.current = `${file}:${title}`;
|
|
50
51
|
save({
|
|
51
52
|
favorites: savedRef.current.favorites,
|
|
@@ -54,13 +55,7 @@ export function ReadingLibrary({
|
|
|
54
55
|
}
|
|
55
56
|
}
|
|
56
57
|
sync();
|
|
57
|
-
|
|
58
|
-
document.addEventListener("board-index-change", sync);
|
|
59
|
-
return () => {
|
|
60
|
-
document.removeEventListener("board-page", sync);
|
|
61
|
-
document.removeEventListener("board-index-change", sync);
|
|
62
|
-
};
|
|
63
|
-
}, [save]);
|
|
58
|
+
}, [save, page]);
|
|
64
59
|
|
|
65
60
|
return (
|
|
66
61
|
<LibraryContext.Provider
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { createContext } from "react";
|
|
2
|
+
|
|
3
|
+
export const ReadingReadyContext = createContext<{
|
|
4
|
+
readonly element: HTMLElement | null;
|
|
5
|
+
readonly report: (element: HTMLElement, ready: boolean) => void;
|
|
6
|
+
}>({ element: null, report: () => undefined });
|
|
7
|
+
|
|
8
|
+
export function readingReady(doc: HTMLElement, element: HTMLElement | null) {
|
|
9
|
+
const context = doc.querySelector(".item-context");
|
|
10
|
+
return !context || context === element;
|
|
11
|
+
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { useContext } from "react";
|
|
1
2
|
import { routeQueryOptions } from "#client/page/routeQuery.ts";
|
|
2
3
|
import type { MainLocation } from "./mainLocation.ts";
|
|
3
4
|
import { ProjectionFrame } from "./ProjectionFrame.tsx";
|
|
5
|
+
import { SessionContext } from "./SessionContext.ts";
|
|
4
6
|
import type { ShellApi } from "./ShellApi.ts";
|
|
5
7
|
import { SystemPage } from "./SystemPage.tsx";
|
|
6
8
|
import { useDiagnosticsPolling } from "./useDiagnosticsPolling.ts";
|
|
@@ -15,7 +17,7 @@ export function SystemMain({
|
|
|
15
17
|
readonly url: string;
|
|
16
18
|
}) {
|
|
17
19
|
const atom = api.diagnostics.query(undefined, routeQueryOptions);
|
|
18
|
-
useDiagnosticsPolling(atom, api.fleetItems.query(undefined, routeQueryOptions));
|
|
20
|
+
useDiagnosticsPolling(useContext(SessionContext), atom, api.fleetItems.query(undefined, routeQueryOptions));
|
|
19
21
|
return (
|
|
20
22
|
<ProjectionFrame
|
|
21
23
|
atom={atom}
|
|
@@ -4,6 +4,7 @@ import type { DiagnosticsProjection } from "#browser/views/remaining.ts";
|
|
|
4
4
|
import { Card as Surface } from "#client/react/components/ui/Card.tsx";
|
|
5
5
|
import { DiagnosticsGithub, DiagnosticsWindows, ObservedTime } from "./diagnosticsValues.tsx";
|
|
6
6
|
import { FleetItemsContext } from "./FleetItemsContext.ts";
|
|
7
|
+
import { githubLimited } from "./githubLimited.ts";
|
|
7
8
|
import { SystemFleetLane } from "./SystemFleetLane.tsx";
|
|
8
9
|
import { SystemHealth } from "./SystemHealth.tsx";
|
|
9
10
|
export function SystemPage({ data: { sources: data, now } }: { readonly data: DiagnosticsProjection }) {
|
|
@@ -24,7 +25,7 @@ export function SystemPage({ data: { sources: data, now } }: { readonly data: Di
|
|
|
24
25
|
source={data.codex}
|
|
25
26
|
/>
|
|
26
27
|
<Card
|
|
27
|
-
attention={data.github.state === "ready" &&
|
|
28
|
+
attention={data.github.state === "ready" && data.github.value.some((window) => githubLimited(window, now))}
|
|
28
29
|
name="GitHub"
|
|
29
30
|
render={(value) => <DiagnosticsGithub now={now} value={value} />}
|
|
30
31
|
source={data.github}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { RegistryContext } from "@effect/atom-react";
|
|
2
|
-
import { Effect, Option } from "effect";
|
|
3
|
-
import * as Atom from "effect/reactivity/Atom";
|
|
2
|
+
import { Cause, Effect, Option } from "effect";
|
|
4
3
|
import { useContext, useMemo, useState } from "react";
|
|
5
4
|
import { useAction, useQuery } from "@shivaedev/effect-react/result-state.ts";
|
|
6
5
|
import type { Document, DocumentRequest } from "#browser/document/schema.ts";
|
|
@@ -11,8 +10,10 @@ import { documentQuery, navigationQuery, routeQueryOptions } from "#client/page/
|
|
|
11
10
|
import { Button } from "#client/react/components/ui/Button.tsx";
|
|
12
11
|
import { Popover } from "#client/react/components/ui/Popover.tsx";
|
|
13
12
|
import { authorLabel } from "#client/responses/authorLabel.ts";
|
|
13
|
+
import { saveFailure } from "#client/saveFailure.ts";
|
|
14
14
|
import { fileUrl } from "#files/url.ts";
|
|
15
15
|
import { createResponse } from "./createResponse.ts";
|
|
16
|
+
import { useFormClient } from "./FormContext.ts";
|
|
16
17
|
import type { ShellApi } from "./ShellApi.ts";
|
|
17
18
|
|
|
18
19
|
export function TodayOptions({ api, item }: { readonly api: ShellApi; readonly item: InboxProjection["you"][number] }) {
|
|
@@ -42,42 +43,48 @@ export function TodayOptions({ api, item }: { readonly api: ShellApi; readonly i
|
|
|
42
43
|
|
|
43
44
|
function InlineOptions({ api, document: data, request }: { readonly api: ShellApi; readonly document: Document; readonly request: DocumentRequest }) {
|
|
44
45
|
const registry = useContext(RegistryContext);
|
|
45
|
-
const
|
|
46
|
-
const [outcome, setOutcome] = useState("");
|
|
46
|
+
const client = useFormClient();
|
|
47
47
|
const [label] = useState(() => authorLabel(() => localStorage, document.documentElement.dataset.workspace));
|
|
48
48
|
const [author, setAuthor] = useState(() => label.read("") || "maintainer");
|
|
49
49
|
const actionAtom = useMemo(
|
|
50
50
|
() =>
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
}),
|
|
78
|
-
[api, registry, request, author, data.file, label.save],
|
|
51
|
+
client.runtime.fn(
|
|
52
|
+
Effect.fn("Board.answerOption")(function* (choice: string) {
|
|
53
|
+
const preview = request.preview;
|
|
54
|
+
const prompt = request.prompts?.[0];
|
|
55
|
+
if (!(preview && prompt && author.trim())) {
|
|
56
|
+
return yield* Effect.fail("Add your local author label.");
|
|
57
|
+
}
|
|
58
|
+
const answers = [{ prompt: prompt.id, selected: [choice], text: "" }];
|
|
59
|
+
const submission = {
|
|
60
|
+
answers,
|
|
61
|
+
author: author.trim(),
|
|
62
|
+
body: answerBody([prompt], answers, ""),
|
|
63
|
+
id: `response.${crypto.randomUUID()}`,
|
|
64
|
+
question: preview.id,
|
|
65
|
+
revision: preview.reviewedRevision,
|
|
66
|
+
type: "answer" as const,
|
|
67
|
+
};
|
|
68
|
+
const saved = yield* createResponse(client, preview, submission);
|
|
69
|
+
label.save(author);
|
|
70
|
+
registry.refresh(api.inbox.query(undefined, routeQueryOptions));
|
|
71
|
+
registry.refresh(navigationQuery(api));
|
|
72
|
+
registry.refresh(documentQuery(api, fileUrl(data.file)));
|
|
73
|
+
return saved;
|
|
74
|
+
}),
|
|
75
|
+
),
|
|
76
|
+
[client, api, registry, request, author, data.file, label.save],
|
|
79
77
|
);
|
|
80
78
|
const action = useAction(actionAtom);
|
|
79
|
+
const failure = Option.getOrUndefined(Option.flatMap(action.cause, Cause.findErrorOption));
|
|
80
|
+
const saved = Option.getOrUndefined(action.data);
|
|
81
|
+
let status = saved ? `Saved: responses/${saved.id}.md. This records feedback, not acceptance.` : "";
|
|
82
|
+
if (failure) {
|
|
83
|
+
status = typeof failure === "string" ? failure : saveFailure(failure, "response").text;
|
|
84
|
+
}
|
|
85
|
+
if (action.pending) {
|
|
86
|
+
status = "Pending: registering reviewed context, then recording response…";
|
|
87
|
+
}
|
|
81
88
|
const prompt = request.prompts?.[0];
|
|
82
89
|
return (
|
|
83
90
|
<fieldset aria-label={`Options for ${data.identity}`} className="m-0 min-w-0 space-y-2 border-0 px-2 pb-2">
|
|
@@ -86,7 +93,7 @@ function InlineOptions({ api, document: data, request }: { readonly api: ShellAp
|
|
|
86
93
|
const text = data.metadata.options?.[index]?.label ?? optionLabel(option.id, option.source, request.preview?.reason ?? "");
|
|
87
94
|
return (
|
|
88
95
|
<Button
|
|
89
|
-
disabled={!(data.writable && author.trim()) || action.pending ||
|
|
96
|
+
disabled={!(data.writable && author.trim()) || action.pending || Boolean(saved)}
|
|
90
97
|
key={option.id}
|
|
91
98
|
onClick={() => action.dispatch(option.id)}
|
|
92
99
|
size="sm"
|