@manta-eu/horns-client 0.1.0 → 0.1.1
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/component-api.d.ts +7 -0
- package/dist/app/coverage-run.js +4 -3
- package/dist/app/home-page.js +6 -4
- package/dist/app/horn-page.js +7 -10
- package/dist/app/horn-studio.js +63 -10
- package/dist/app/horn-view-page.js +13 -9
- package/dist/app/horns-page.js +6 -9
- package/dist/app/new-horn-page.js +52 -10
- package/dist/app/paths.d.ts +15 -10
- package/dist/app/paths.js +18 -29
- package/dist/app/plans-page.d.ts +1 -0
- package/dist/app/plans-page.js +59 -0
- package/dist/app/reference-page.d.ts +1 -0
- package/dist/app/reference-page.js +55 -0
- package/dist/app/routes.js +7 -5
- package/dist/app/rule-page.js +18 -11
- package/dist/app/rules-page.js +12 -12
- package/dist/app/shape-entity-page.js +7 -9
- package/dist/app/shape-zoo-page.js +9 -10
- package/dist/app/shapes-page.js +13 -15
- package/dist/app/showcase-page.js +8 -11
- package/dist/components/app-shell.js +8 -7
- package/dist/components/catalog-card-view.js +4 -5
- package/dist/components/explain-dialog.d.ts +5 -0
- package/dist/components/explain-dialog.js +26 -0
- package/dist/components/plan-runs.js +2 -2
- package/dist/components/read-the-code.d.ts +19 -0
- package/dist/components/read-the-code.js +17 -3
- package/dist/index.d.ts +2 -3
- package/dist/index.js +2 -3
- package/dist/lib/horn-review.d.ts +7 -0
- package/dist/lib/horn-review.js +15 -0
- package/dist/lib/horn-variables.d.ts +8 -0
- package/dist/lib/horn-variables.js +39 -0
- package/dist/lib/manta/sign-in.d.ts +2 -0
- package/dist/lib/manta/sign-in.js +9 -0
- package/dist/lib/manta/sparql-select.d.ts +6 -2
- package/dist/lib/manta/sparql-select.js +17 -14
- package/dist/lib/reference/fake-sparql.d.ts +4 -0
- package/dist/lib/reference/fake-sparql.js +216 -0
- package/dist/lib/shapes/ldkit-source.d.ts +1 -1
- package/dist/lib/shapes/ldkit-source.js +1 -1
- package/dist/lib/studio-progress.js +3 -1
- package/dist/lib/surface/declarations.d.ts +5 -0
- package/dist/lib/surface/declarations.js +77 -0
- package/dist/lib/written-surface.js +13 -2
- package/dist/standalone/app.d.ts +2 -0
- package/dist/standalone/app.js +57 -0
- package/dist/standalone/client.d.ts +15 -0
- package/dist/{client.js → standalone/client.js} +12 -8
- package/dist/standalone/gates.d.ts +10 -0
- package/dist/standalone/gates.js +15 -0
- package/dist/styles.css +1 -1
- package/dist/vite/component-api.d.ts +8 -0
- package/dist/vite/component-api.js +24 -0
- package/dist/vite/index.d.ts +3 -0
- package/dist/vite/index.js +3 -0
- package/dist/vite/shape-catalog.d.ts +10 -0
- package/dist/vite/shape-catalog.js +35 -0
- package/dist/vite/typecheck-fs.d.ts +6 -0
- package/dist/vite/typecheck-fs.js +68 -0
- package/package.json +36 -12
- package/shape-catalog.d.ts +9 -0
- package/typecheck-fs.d.ts +6 -0
- package/dist/app/session-gate.d.ts +0 -6
- package/dist/app/session-gate.js +0 -19
- package/dist/client.d.ts +0 -10
- package/dist/lib/manta/providers.d.ts +0 -10
- package/dist/lib/manta/providers.js +0 -48
- package/dist/lib/manta/session.d.ts +0 -47
- package/dist/lib/manta/session.js +0 -204
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { isPresent } from "@manta-eu/lib/present";
|
|
3
|
-
import { Link, Outlet, useLocation
|
|
4
|
-
import { Boxes, Columns3, Database, LayoutGrid, Sigma } from "lucide-react";
|
|
3
|
+
import { Link, Outlet, useLocation } from "react-router";
|
|
4
|
+
import { Boxes, Columns3, Database, LayoutGrid, ListChecks, Sigma } from "lucide-react";
|
|
5
5
|
import { MainContent, MantaLogo, NavUser, Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupContent, SidebarGroupLabel, SidebarHeader, SidebarMenu, SidebarMenuButton, SidebarMenuItem, SidebarProvider, SidebarTrigger, } from "@manta-eu/react";
|
|
6
|
-
import { hornsPath, rulesPath, shapesPath, showcasePath } from "../app/paths";
|
|
7
|
-
import {
|
|
6
|
+
import { hornsPath, plansPath, rulesPath, shapesPath, showcasePath } from "../app/paths";
|
|
7
|
+
import { useActiveDatastore } from "@manta-eu/react/providers";
|
|
8
|
+
import { useAuthContext } from "@manta-eu/react/providers/AuthContext";
|
|
8
9
|
function NavLink({ to, icon, label, active }) {
|
|
9
10
|
return (_jsx(SidebarMenuItem, { children: _jsx(SidebarMenuButton, { asChild: true, isActive: active, tooltip: label, children: _jsxs(Link, { to: to, "aria-current": active ? "page" : undefined, children: [icon, _jsx("span", { children: label })] }) }) }));
|
|
10
11
|
}
|
|
11
12
|
export function AppShell() {
|
|
12
|
-
const { datastoreId } =
|
|
13
|
+
const { datastoreId } = useActiveDatastore();
|
|
13
14
|
const { pathname } = useLocation();
|
|
14
|
-
const { user, datastore, datastores } =
|
|
15
|
+
const { user, datastore, datastores = [] } = useAuthContext();
|
|
15
16
|
const current = datastores.find((candidate) => candidate.id === datastoreId) ?? datastore;
|
|
16
17
|
const inDatastore = isPresent(datastoreId) && isPresent(current);
|
|
17
|
-
return (_jsxs(SidebarProvider, { children: [_jsxs(Sidebar, { collapsible: "icon", children: [_jsx(SidebarHeader, { children: _jsxs(Link, { to: "/", className: "flex items-center gap-2 px-2 py-1.5", "aria-label": "Manta Horns", children: [_jsx(MantaLogo, { size: 24 }), _jsx("span", { className: "font-semibold group-data-[collapsible=icon]:hidden", children: "Horns" })] }) }), _jsx(SidebarContent, { children: _jsxs("nav", { "aria-label": "Datastore", className: "flex flex-col gap-2", children: [isPresent(datastoreId) && inDatastore ? (_jsxs(_Fragment, { children: [_jsxs(SidebarGroup, { children: [_jsx(SidebarGroupLabel, { children: current.name }), _jsx(SidebarGroupContent, { children: _jsxs(SidebarMenu, { children: [_jsx(NavLink, { to: showcasePath(
|
|
18
|
+
return (_jsxs(SidebarProvider, { children: [_jsxs(Sidebar, { collapsible: "icon", children: [_jsx(SidebarHeader, { children: _jsxs(Link, { to: "/", className: "flex items-center gap-2 px-2 py-1.5", "aria-label": "Manta Horns", children: [_jsx(MantaLogo, { size: 24 }), _jsx("span", { className: "font-semibold group-data-[collapsible=icon]:hidden", children: "Horns" })] }) }), _jsx(SidebarContent, { children: _jsxs("nav", { "aria-label": "Datastore", className: "flex flex-col gap-2", children: [isPresent(datastoreId) && inDatastore ? (_jsxs(_Fragment, { children: [_jsxs(SidebarGroup, { children: [_jsx(SidebarGroupLabel, { children: current.name }), _jsx(SidebarGroupContent, { children: _jsxs(SidebarMenu, { children: [_jsx(NavLink, { to: showcasePath(), icon: _jsx(LayoutGrid, { "aria-hidden": "true" }), label: "Showcase", active: pathname === showcasePath() }), _jsx(NavLink, { to: hornsPath(), icon: _jsx(Columns3, { "aria-hidden": "true" }), label: "Horns", active: pathname === hornsPath() })] }) })] }), _jsxs(SidebarGroup, { children: [_jsx(SidebarGroupLabel, { children: "For builders" }), _jsx(SidebarGroupContent, { children: _jsxs(SidebarMenu, { children: [_jsx(NavLink, { to: shapesPath(), icon: _jsx(Boxes, { "aria-hidden": "true" }), label: "Data shapes", active: pathname.startsWith(shapesPath()) }), _jsx(NavLink, { to: rulesPath(), icon: _jsx(Sigma, { "aria-hidden": "true" }), label: "Rules", active: pathname.startsWith(rulesPath()) }), _jsx(NavLink, { to: plansPath(), icon: _jsx(ListChecks, { "aria-hidden": "true" }), label: "Action plans", active: pathname.startsWith(plansPath()) })] }) })] })] })) : null, _jsxs(SidebarGroup, { children: [_jsx(SidebarGroupLabel, { children: "All datastores" }), _jsx(SidebarGroupContent, { children: _jsx(SidebarMenu, { children: _jsx(NavLink, { to: "/", icon: _jsx(Database, { "aria-hidden": "true" }), label: `Datastores (${datastores.length})`, active: pathname === "/" }) }) })] })] }) }), _jsx(SidebarFooter, { children: _jsx(NavUser, { name: user?.name ?? user?.email ?? "Signed in", currentDatastore: { name: current?.name ?? "No datastore" }, onNavigateToAccount: () => window.location.assign("/"), onLogout: () => window.location.assign("/logout") }) })] }), _jsx(MainContent, { className: "pb-8", children: _jsxs("div", { className: "flex w-full flex-col gap-4 px-4 sm:px-6", children: [_jsx(SidebarTrigger, { "aria-label": "Toggle Sidebar" }), _jsx(Outlet, {})] }) })] }));
|
|
18
19
|
}
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { lazy, Suspense } from "react";
|
|
3
3
|
import { Loader } from "@manta-eu/react";
|
|
4
|
-
import {
|
|
4
|
+
import { EmbeddedChatProviders } from "@manta-eu/react/providers";
|
|
5
5
|
// Behind `lazy`: the body pulls in the graph routing and every typed entity card.
|
|
6
6
|
const CatalogCardBody = lazy(async () => {
|
|
7
7
|
const body = await import("./catalog-card-body");
|
|
8
8
|
return { default: body.CatalogCardBody };
|
|
9
9
|
});
|
|
10
|
-
// The
|
|
11
|
-
//
|
|
12
|
-
|
|
13
|
-
export const catalogCardView = (content, _view, context) => (_jsx(MantaSparqlProvider, { datastoreId: context.datastoreId, children: _jsx(Suspense, { fallback: _jsx(Loader, {}), children: _jsx(CatalogCardBody, { content: content }) }) }));
|
|
10
|
+
// The providers come with it: a card fetches its own entity, and a server-rendered Horn document hydrates
|
|
11
|
+
// embeds and nothing else. The embedded set, because a card must not navigate its host away on a 401.
|
|
12
|
+
export const catalogCardView = (content, _view, context) => (_jsx(EmbeddedChatProviders, { mantaBaseUrl: window.location.origin, datastoreId: context.datastoreId, children: _jsx(Suspense, { fallback: _jsx(Loader, {}), children: _jsx(CatalogCardBody, { content: content }) }) }));
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback, useState } from "react";
|
|
3
|
+
import { isRecord } from "@manta-eu/horns";
|
|
4
|
+
import { Dialog, DialogContent, DialogTitle } from "@manta-eu/react";
|
|
5
|
+
import { ExplainFact } from "@manta-eu/react/components/explain-fact/ExplainFact";
|
|
6
|
+
// The explanation a `WhyThis` mark asks for. One per page rather than one per mark: the kit draws the marks
|
|
7
|
+
// and the host owns the modal, because drawing a proof tree needs the design system the kit has not got.
|
|
8
|
+
/**
|
|
9
|
+
* Whether what a view handed over is an explanation this can draw.
|
|
10
|
+
*
|
|
11
|
+
* Checked to the depth the type declares — a map of facts, or of lists of them — because the data came out
|
|
12
|
+
* of model-written source and the component walks it without asking.
|
|
13
|
+
*/
|
|
14
|
+
function isExplanation(value) {
|
|
15
|
+
if (!isRecord(value))
|
|
16
|
+
return false;
|
|
17
|
+
return Object.values(value).every((one) => Array.isArray(one) ? one.every((fact) => isRecord(fact)) : isRecord(one));
|
|
18
|
+
}
|
|
19
|
+
export function useExplainDialog() {
|
|
20
|
+
const [shown, setShown] = useState();
|
|
21
|
+
// Stable, because it reaches the kit through a provider value and would otherwise redraw every view.
|
|
22
|
+
const open = useCallback((fact) => setShown(fact), []);
|
|
23
|
+
const explanation = isExplanation(shown?.data) ? shown.data : undefined;
|
|
24
|
+
const dialog = (_jsx(Dialog, { open: shown !== undefined, onOpenChange: (next) => (next ? undefined : setShown(undefined)), children: _jsxs(DialogContent, { children: [_jsx(DialogTitle, { text: shown?.title ?? "Why this value" }), shown === undefined ? null : (_jsx(ExplainFact, { mode: "compact", subject: shown.subject, object: shown.object, ...(shown.predicate === undefined ? {} : { predicate: shown.predicate }), ...(explanation === undefined ? {} : { data: explanation }) }))] }) }));
|
|
25
|
+
return { open, dialog };
|
|
26
|
+
}
|
|
@@ -3,7 +3,7 @@ import { useEffect, useRef, useState } from "react";
|
|
|
3
3
|
import { planStatusQuery, readPlanStatus } from "@manta-eu/horns/render";
|
|
4
4
|
import { planSummary } from "@manta-eu/horns-kit";
|
|
5
5
|
import { Button, Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@manta-eu/react";
|
|
6
|
-
import {
|
|
6
|
+
import { useSparqlSelect } from "../lib/manta/sparql-select";
|
|
7
7
|
import { planPollAttempts, planPollInterval, runSeen, runSettled, startPlanRun } from "../lib/plan-runs";
|
|
8
8
|
/**
|
|
9
9
|
* Previews and runs of the action plans a Horn shows. A preview starts straight away: it changes nothing. A real
|
|
@@ -11,6 +11,7 @@ import { planPollAttempts, planPollInterval, runSeen, runSettled, startPlanRun }
|
|
|
11
11
|
* and a real run that finished redraws the Horn, because the things it changed now look different.
|
|
12
12
|
*/
|
|
13
13
|
export function usePlanRuns(args) {
|
|
14
|
+
const select = useSparqlSelect();
|
|
14
15
|
const { datastoreId, realm, onChanged } = args;
|
|
15
16
|
const [status, setStatus] = useState({});
|
|
16
17
|
const [pending, setPending] = useState(new Set());
|
|
@@ -26,7 +27,6 @@ export function usePlanRuns(args) {
|
|
|
26
27
|
};
|
|
27
28
|
}, []);
|
|
28
29
|
const follow = async (plan, preview, startedBefore) => {
|
|
29
|
-
const select = browserSparqlSelect(datastoreId);
|
|
30
30
|
for (let attempt = 0; attempt < planPollAttempts && alive.current; attempt += 1) {
|
|
31
31
|
await new Promise((resolve) => setTimeout(resolve, planPollInterval));
|
|
32
32
|
if (!alive.current)
|
|
@@ -10,5 +10,24 @@ export type ReadTheCodeProps = {
|
|
|
10
10
|
onKeep: (edit: VisualEdit) => void;
|
|
11
11
|
};
|
|
12
12
|
export declare function ReadTheCode({ viewId, title, source, state, onKeep }: ReadTheCodeProps): import("react").JSX.Element;
|
|
13
|
+
export type ReadTheCodeDialogProps = ReadTheCodeProps & {
|
|
14
|
+
open: boolean;
|
|
15
|
+
onClose: () => void;
|
|
16
|
+
/** Where this one sits when a save is walking every unread view in turn. */
|
|
17
|
+
standing?: {
|
|
18
|
+
at: number;
|
|
19
|
+
of: number;
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* The reading itself, without the button that opens it.
|
|
24
|
+
*
|
|
25
|
+
* Saving drives this too: a Horn that will not save because nobody read its code is a dead end, so Save
|
|
26
|
+
* offers the reading instead of refusing. It is still the person's action that keeps the code.
|
|
27
|
+
*
|
|
28
|
+
* Keeping does not close it — the owner decides what follows, because when a save is walking several views
|
|
29
|
+
* what follows is the next one.
|
|
30
|
+
*/
|
|
31
|
+
export declare function ReadTheCodeDialog({ viewId, title, source, state, open, onClose, onKeep, standing, }: ReadTheCodeDialogProps): import("react").JSX.Element;
|
|
13
32
|
/** The review state of every written view in a draft, by view id, for the Studio to show on each card. */
|
|
14
33
|
export declare function writtenReviewStates(horn: Horn): Promise<Record<string, ReviewState["state"]>>;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsx as _jsx,
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useState } from "react";
|
|
3
3
|
import { Badge, Button, Dialog, DialogContent, DialogHeader, DialogTitle } from "@manta-eu/react";
|
|
4
4
|
import { reviewOf, reviewStateOf } from "@manta-eu/horns/render";
|
|
@@ -24,12 +24,26 @@ const wording = {
|
|
|
24
24
|
};
|
|
25
25
|
export function ReadTheCode({ viewId, title, source, state, onKeep }) {
|
|
26
26
|
const [open, setOpen] = useState(false);
|
|
27
|
+
return (_jsxs(_Fragment, { children: [_jsx(Button, { variant: state === "reviewed" ? "ghost" : "secondary", size: "xs", onClick: () => setOpen(true), "aria-label": `${wording[state].title}: ${title}`, children: wording[state].badge }), _jsx(ReadTheCodeDialog, { viewId: viewId, title: title, source: source, state: state, open: open, onClose: () => setOpen(false), onKeep: (change) => {
|
|
28
|
+
onKeep(change);
|
|
29
|
+
setOpen(false);
|
|
30
|
+
} })] }));
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* The reading itself, without the button that opens it.
|
|
34
|
+
*
|
|
35
|
+
* Saving drives this too: a Horn that will not save because nobody read its code is a dead end, so Save
|
|
36
|
+
* offers the reading instead of refusing. It is still the person's action that keeps the code.
|
|
37
|
+
*
|
|
38
|
+
* Keeping does not close it — the owner decides what follows, because when a save is walking several views
|
|
39
|
+
* what follows is the next one.
|
|
40
|
+
*/
|
|
41
|
+
export function ReadTheCodeDialog({ viewId, title, source, state, open, onClose, onKeep, standing, }) {
|
|
27
42
|
const words = wording[state];
|
|
28
43
|
async function keep() {
|
|
29
44
|
onKeep({ kind: "keep-code", viewId, reviewed: await reviewOf(source, new Date()) });
|
|
30
|
-
setOpen(false);
|
|
31
45
|
}
|
|
32
|
-
return (
|
|
46
|
+
return (_jsx(Dialog, { open: open, onOpenChange: (next) => (next ? undefined : onClose()), children: _jsxs(DialogContent, { className: "flex h-[92vh] w-[95vw] max-w-[95vw] flex-col sm:max-w-[95vw]", children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { text: words.title }) }), _jsxs("div", { className: "flex min-h-0 flex-1 flex-col gap-3", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Badge, { children: title }), _jsx("span", { className: "text-muted-foreground text-sm", children: standing === undefined ? words.badge : `${words.badge} · ${standing.at} of ${standing.of}` })] }), _jsx("p", { className: "text-muted-foreground m-0 text-sm", children: words.lead }), _jsx("pre", { className: "border-muted-border min-h-0 flex-1 overflow-auto rounded-lg border p-4 text-sm leading-relaxed", children: _jsx("code", { children: source }) }), _jsx("div", { className: "flex justify-end gap-2", children: state === "reviewed" ? (_jsx(Button, { onClick: onClose, children: words.action })) : (_jsxs(_Fragment, { children: [_jsx(Button, { variant: "ghost", onClick: onClose, children: "Not yet" }), _jsx(Button, { onClick: () => void keep(), children: words.action })] })) })] })] }) }));
|
|
33
47
|
}
|
|
34
48
|
/** The review state of every written view in a draft, by view id, for the Studio to show on each card. */
|
|
35
49
|
export async function writtenReviewStates(horn) {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
export { AppRoutes, hornsRoutes } from "./app/routes.tsx";
|
|
2
2
|
export { AppShell } from "./components/app-shell.tsx";
|
|
3
|
-
export {
|
|
4
|
-
export {
|
|
5
|
-
export { browserSparqlSelect } from "./lib/manta/sparql-select.ts";
|
|
3
|
+
export { signInHref } from "./lib/manta/sign-in.ts";
|
|
4
|
+
export { useSparqlSelect } from "./lib/manta/sparql-select.ts";
|
|
6
5
|
export { ldkitShapeCatalog, provideShapeCatalog, shapesRealm, type ShapeCatalogSource } from "./lib/shapes/catalog.ts";
|
|
7
6
|
export { rasterizeHorn, sendHornScreenshot } from "./components/rasterize.tsx";
|
|
8
7
|
export { reviewHorn, type HornReview } from "./lib/horn-review.ts";
|
package/dist/index.js
CHANGED
|
@@ -5,9 +5,8 @@
|
|
|
5
5
|
// than compiled by the host, so a host with no Tailwind still gets the app looking like Manta.
|
|
6
6
|
export { AppRoutes, hornsRoutes } from "./app/routes.js";
|
|
7
7
|
export { AppShell } from "./components/app-shell.js";
|
|
8
|
-
export {
|
|
9
|
-
export {
|
|
10
|
-
export { browserSparqlSelect } from "./lib/manta/sparql-select.js";
|
|
8
|
+
export { signInHref } from "./lib/manta/sign-in.js";
|
|
9
|
+
export { useSparqlSelect } from "./lib/manta/sparql-select.js";
|
|
11
10
|
export { ldkitShapeCatalog, provideShapeCatalog, shapesRealm } from "./lib/shapes/catalog.js";
|
|
12
11
|
export { rasterizeHorn, sendHornScreenshot } from "./components/rasterize.js";
|
|
13
12
|
export { reviewHorn } from "./lib/horn-review.js";
|
|
@@ -12,6 +12,13 @@ export type HornReview = {
|
|
|
12
12
|
problems: RenderProblem[];
|
|
13
13
|
unavailable?: string;
|
|
14
14
|
};
|
|
15
|
+
/**
|
|
16
|
+
* What the picture calls a view.
|
|
17
|
+
*
|
|
18
|
+
* A draw failure knows the id and the reviewer only ever sees the title, so one broken view was arriving as
|
|
19
|
+
* two findings under two names, and a turn spent itself fixing the second one.
|
|
20
|
+
*/
|
|
21
|
+
export declare function nameOfView(horn: Pick<Horn, "views">, viewId: string): string;
|
|
15
22
|
/**
|
|
16
23
|
* Draw the Horn that was just made, look at it, and say what is wrong.
|
|
17
24
|
*
|
package/dist/lib/horn-review.js
CHANGED
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
import { hornSnapshotSchema } from "@manta-eu/horns/render";
|
|
2
2
|
import { rasterizeHorn } from "../components/rasterize";
|
|
3
3
|
import { isRecord } from "@manta-eu/horns";
|
|
4
|
+
/**
|
|
5
|
+
* What the picture calls a view.
|
|
6
|
+
*
|
|
7
|
+
* A draw failure knows the id and the reviewer only ever sees the title, so one broken view was arriving as
|
|
8
|
+
* two findings under two names, and a turn spent itself fixing the second one.
|
|
9
|
+
*/
|
|
10
|
+
export function nameOfView(horn, viewId) {
|
|
11
|
+
const view = horn.views.find((candidate) => candidate.id === viewId);
|
|
12
|
+
return view === undefined || "ref" in view ? viewId : view.title;
|
|
13
|
+
}
|
|
4
14
|
/**
|
|
5
15
|
* Draw the Horn that was just made, look at it, and say what is wrong.
|
|
6
16
|
*
|
|
@@ -14,6 +24,11 @@ export async function reviewHorn(args) {
|
|
|
14
24
|
const snapshot = await snapshotOf(args);
|
|
15
25
|
const drawn = await rasterizeHorn(snapshot);
|
|
16
26
|
problems = drawn.problems;
|
|
27
|
+
// A view that threw leaves a card with an error where the drawing should be. Asking a model what it
|
|
28
|
+
// thinks of that picture buys three ways of saying "this card is empty", each of which reads like a
|
|
29
|
+
// separate thing to fix. The failure is the finding, and the reviewer costs money per turn.
|
|
30
|
+
if (problems.length > 0)
|
|
31
|
+
return { advice: [], problems };
|
|
17
32
|
const image = await dataUrl(drawn.png);
|
|
18
33
|
const response = await fetch(`/api/d/${encodeURIComponent(args.datastoreId)}/horns/review`, {
|
|
19
34
|
method: "POST",
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Horn } from "@manta-eu/horns";
|
|
2
|
+
export type HornVariables = {
|
|
3
|
+
/** What each variable is set to, ready for `buildHornSnapshot`. Absent keys are the variable's default. */
|
|
4
|
+
values: Record<string, unknown>;
|
|
5
|
+
/** Narrows the Horn, or clears the variable with `undefined`. */
|
|
6
|
+
set: (id: string, value: unknown) => void;
|
|
7
|
+
};
|
|
8
|
+
export declare function useHornVariables(horn: Horn | undefined): HornVariables;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { useCallback, useMemo } from "react";
|
|
2
|
+
import { parseAsString, useQueryStates } from "nuqs";
|
|
3
|
+
/**
|
|
4
|
+
* The parameter a variable rides on, or nothing.
|
|
5
|
+
*
|
|
6
|
+
* Its `urlParam` and only that: `variablesFromSearch` reads that name and no other, so writing a variable's
|
|
7
|
+
* id instead would change the URL and leave every binding reading the value it had before.
|
|
8
|
+
*/
|
|
9
|
+
function paramOf(variable) {
|
|
10
|
+
return variable.urlParam;
|
|
11
|
+
}
|
|
12
|
+
export function useHornVariables(horn) {
|
|
13
|
+
const variables = useMemo(() => horn?.variables ?? [], [horn]);
|
|
14
|
+
const keys = useMemo(() => Object.fromEntries(variables.flatMap((variable) => {
|
|
15
|
+
const param = paramOf(variable);
|
|
16
|
+
return param === undefined ? [] : [[param, parseAsString]];
|
|
17
|
+
})), [variables]);
|
|
18
|
+
const [params, setParams] = useQueryStates(keys);
|
|
19
|
+
const values = useMemo(() => {
|
|
20
|
+
const chosen = {};
|
|
21
|
+
for (const variable of variables) {
|
|
22
|
+
const param = paramOf(variable);
|
|
23
|
+
const held = param === undefined ? undefined : params[param];
|
|
24
|
+
if (held !== null && held !== undefined)
|
|
25
|
+
chosen[variable.id] = held;
|
|
26
|
+
}
|
|
27
|
+
return chosen;
|
|
28
|
+
}, [variables, params]);
|
|
29
|
+
const set = useCallback((id, value) => {
|
|
30
|
+
const variable = variables.find((candidate) => candidate.id === id);
|
|
31
|
+
const param = variable === undefined ? undefined : paramOf(variable);
|
|
32
|
+
if (param === undefined)
|
|
33
|
+
return;
|
|
34
|
+
// null is how nuqs drops a parameter; anything else rides as text, which is what a URL holds.
|
|
35
|
+
const next = value === undefined || value === null || value === "" ? null : String(value);
|
|
36
|
+
void setParams({ [param]: next });
|
|
37
|
+
}, [variables, setParams]);
|
|
38
|
+
return { values, set };
|
|
39
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { isPresent } from "@manta-eu/lib/present";
|
|
2
|
+
/** Horns' own `/login`, which proxies to agents and stamps the marker a loop is diagnosed by. */
|
|
3
|
+
export function signInHref(redirect) {
|
|
4
|
+
const params = new URLSearchParams();
|
|
5
|
+
if (isPresent(redirect) && redirect !== "/")
|
|
6
|
+
params.set("redirect", redirect);
|
|
7
|
+
const query = params.toString();
|
|
8
|
+
return `/login${query ? `?${query}` : ""}`;
|
|
9
|
+
}
|
|
@@ -1,2 +1,6 @@
|
|
|
1
|
-
import { type SparqlSelect } from "@manta-eu/horns";
|
|
2
|
-
|
|
1
|
+
import { SparqlReadError, type SparqlSelect } from "@manta-eu/horns";
|
|
2
|
+
import type { GraphQLClient } from "graphql-request";
|
|
3
|
+
export declare function graphqlSparqlSelect(client: GraphQLClient): SparqlSelect;
|
|
4
|
+
/** The reader this app's pages use. The datastore rides on the client, from the route. */
|
|
5
|
+
export declare function useSparqlSelect(): SparqlSelect;
|
|
6
|
+
export { SparqlReadError };
|
|
@@ -1,19 +1,22 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
import { useMemo } from "react";
|
|
2
|
+
import { sparqlResultsOfGraphqlField, SparqlReadError } from "@manta-eu/horns";
|
|
3
|
+
import { SparqlQueryDocument } from "@manta-eu/client";
|
|
4
|
+
import { useMantaClient } from "@manta-eu/react/providers";
|
|
5
|
+
// Imperative rather than `useSparqlQueryQuery`: a `SparqlSelect` is called from inside query functions and
|
|
6
|
+
// from readers that issue one query after another, which a hook cannot serve.
|
|
7
|
+
export function graphqlSparqlSelect(client) {
|
|
6
8
|
return async (query, signal) => {
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
headers: { "content-type": "application/sparql-query", accept: "application/sparql-results+json" },
|
|
11
|
-
body: query,
|
|
9
|
+
const answer = await client.request({
|
|
10
|
+
document: SparqlQueryDocument,
|
|
11
|
+
variables: { query },
|
|
12
12
|
...(signal ? { signal } : {}),
|
|
13
13
|
});
|
|
14
|
-
|
|
15
|
-
throw new Error(await readJsonError(response, `The datastore answered ${response.status}.`));
|
|
16
|
-
const body = await response.json();
|
|
17
|
-
return sparqlResultsSchema.parse(body);
|
|
14
|
+
return sparqlResultsOfGraphqlField(answer.sparqlQuery);
|
|
18
15
|
};
|
|
19
16
|
}
|
|
17
|
+
/** The reader this app's pages use. The datastore rides on the client, from the route. */
|
|
18
|
+
export function useSparqlSelect() {
|
|
19
|
+
const { mantaClient } = useMantaClient();
|
|
20
|
+
return useMemo(() => graphqlSparqlSelect(mantaClient), [mantaClient]);
|
|
21
|
+
}
|
|
22
|
+
export { SparqlReadError };
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { SparqlSelect } from "@manta-eu/horns";
|
|
2
|
+
export declare const referenceEntity = "https://data.example/entity/cisco-systems";
|
|
3
|
+
/** A `SparqlSelect` over the fixture. Async because every other one is, not because anything waits. */
|
|
4
|
+
export declare const fakeSparqlSelect: SparqlSelect;
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import { xsd } from "ldkit/namespaces";
|
|
2
|
+
// A datastore that does not exist, for the reference Horn (docs/standard.md). It answers the queries that
|
|
3
|
+
// Horn's bindings compile to and nothing else, so the page can be looked at without a datastore, a session or
|
|
4
|
+
// a worker — which is the only way to judge whether the thing is any good before there is data behind it.
|
|
5
|
+
//
|
|
6
|
+
// Matching on what a query mentions rather than parsing it: these are eight known queries, and the marker is
|
|
7
|
+
// the predicate each one is about. Anything unrecognised answers with no rows, which is what a real datastore
|
|
8
|
+
// does for a thing it has not got.
|
|
9
|
+
const entity = "https://data.example/entity/";
|
|
10
|
+
const country = "https://data.example/country/";
|
|
11
|
+
const alert = "https://data.example/alert/";
|
|
12
|
+
export const referenceEntity = `${entity}cisco-systems`;
|
|
13
|
+
const uri = (value) => ({ type: "uri", value });
|
|
14
|
+
const text = (value) => ({ type: "literal", value });
|
|
15
|
+
const number = (value) => ({
|
|
16
|
+
type: "literal",
|
|
17
|
+
value: String(value),
|
|
18
|
+
datatype: Number.isInteger(value) ? xsd.integer : xsd.decimal,
|
|
19
|
+
});
|
|
20
|
+
const date = (value) => ({ type: "literal", value, datatype: xsd.dateTime });
|
|
21
|
+
const answer = (vars, bindings) => ({
|
|
22
|
+
head: { vars },
|
|
23
|
+
results: { bindings },
|
|
24
|
+
});
|
|
25
|
+
const nothing = answer([], []);
|
|
26
|
+
/** What each IRI is called. The label pass asks for these, and every view shows the name and never the IRI. */
|
|
27
|
+
const names = {
|
|
28
|
+
[referenceEntity]: "Cisco Systems, Inc.",
|
|
29
|
+
[`${entity}qualcomm`]: "QUALCOMM, Inc.",
|
|
30
|
+
[`${entity}cisco-supplier`]: "Cisco Systems, Inc.",
|
|
31
|
+
[`${entity}069hj1e`]: "069HJ1-E",
|
|
32
|
+
[`${entity}interdigital`]: "InterDigital, Inc.",
|
|
33
|
+
[`${entity}samsung-electro`]: "Samsung Electro-Mechanics Co., Ltd.",
|
|
34
|
+
[`${entity}au-optronics`]: "AU Optronics Corp.",
|
|
35
|
+
[`${entity}huawei`]: "Huawei Investment & Holding Co., Ltd.",
|
|
36
|
+
[`${country}us`]: "United States",
|
|
37
|
+
[`${country}cn`]: "China",
|
|
38
|
+
[`${country}tw`]: "Taiwan",
|
|
39
|
+
[`${country}kr`]: "South Korea",
|
|
40
|
+
[`${country}id`]: "Indonesia",
|
|
41
|
+
[`${country}jp`]: "Japan",
|
|
42
|
+
};
|
|
43
|
+
const suppliers = [
|
|
44
|
+
{ id: "qualcomm", concentration: 78.08, yearOnYear: 72.04, country: "us" },
|
|
45
|
+
{ id: "cisco-supplier", concentration: 1.48, yearOnYear: -4.23, country: "us" },
|
|
46
|
+
{ id: "069hj1e", concentration: 0.94, yearOnYear: -1.72, country: "us" },
|
|
47
|
+
{ id: "interdigital", concentration: 0.75, yearOnYear: -1.88, country: "us" },
|
|
48
|
+
{ id: "samsung-electro", concentration: 0.75, yearOnYear: -1.86, country: "kr" },
|
|
49
|
+
{ id: "au-optronics", concentration: 0.74, yearOnYear: -1.85, country: "tw" },
|
|
50
|
+
{ id: "huawei", concentration: 0.74, yearOnYear: -2.9, country: "cn" },
|
|
51
|
+
];
|
|
52
|
+
const exposures = [
|
|
53
|
+
{ id: "us", buyer: 82, supplier: 64, disruptions: 3 },
|
|
54
|
+
{ id: "cn", buyer: 41, supplier: 88, disruptions: 2 },
|
|
55
|
+
{ id: "tw", buyer: 12, supplier: 74, disruptions: 4 },
|
|
56
|
+
{ id: "kr", buyer: 18, supplier: 46, disruptions: 0 },
|
|
57
|
+
{ id: "id", buyer: 6, supplier: 21, disruptions: 5 },
|
|
58
|
+
{ id: "jp", buyer: 22, supplier: 33, disruptions: 1 },
|
|
59
|
+
];
|
|
60
|
+
const flags = [
|
|
61
|
+
{ kind: "Total supply chain alerts", total: 32, severity: "neutral" },
|
|
62
|
+
{ kind: "Concentration alerts", total: 6, qualifier: "Sources: CN", severity: "medium" },
|
|
63
|
+
{ kind: "Sanction exposures", total: 2, qualifier: "Sources: CN", severity: "high" },
|
|
64
|
+
{ kind: "Tariff exposures", total: 13, qualifier: "Sources: CN", severity: "medium" },
|
|
65
|
+
{ kind: "Disruption exposures", total: 2, qualifier: "1 high priority", severity: "high" },
|
|
66
|
+
{ kind: "ESG alerts", total: 8, qualifier: "Benchmark 4", severity: "medium" },
|
|
67
|
+
];
|
|
68
|
+
const notifications = [
|
|
69
|
+
{
|
|
70
|
+
id: "concentration-change",
|
|
71
|
+
category: "Concentration alerts",
|
|
72
|
+
headline: "Country concentration change",
|
|
73
|
+
source: "Transactions",
|
|
74
|
+
priority: "amber",
|
|
75
|
+
raised: "2026-09-28T09:14:00Z",
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
id: "quake-c9400-a",
|
|
79
|
+
category: "Disruption exposures",
|
|
80
|
+
headline: "Earthquake impacts order 2021-11-16_C9400",
|
|
81
|
+
source: "GDACS",
|
|
82
|
+
priority: "red",
|
|
83
|
+
raised: "2026-09-27T22:02:00Z",
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
id: "quake-c9400-b",
|
|
87
|
+
category: "Disruption exposures",
|
|
88
|
+
headline: "Earthquake impacts order 2022-01-24_C9400",
|
|
89
|
+
source: "GDACS",
|
|
90
|
+
priority: "red",
|
|
91
|
+
raised: "2026-09-27T18:40:00Z",
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
id: "tariff-cn",
|
|
95
|
+
category: "Tariff exposures",
|
|
96
|
+
headline: "Tariff exposure raised on 13 inbound lanes from CN",
|
|
97
|
+
source: "Trade",
|
|
98
|
+
priority: "amber",
|
|
99
|
+
raised: "2026-09-26T11:20:00Z",
|
|
100
|
+
},
|
|
101
|
+
];
|
|
102
|
+
const disruptions = [
|
|
103
|
+
{
|
|
104
|
+
id: "taiwan-quake",
|
|
105
|
+
category: "Disruption exposures",
|
|
106
|
+
headline: "Red earthquake alert (Magnitude 9.8M, Depth 35km) in Taiwan 09/11/2021 06:25 UTC, 1,030,000 people within 100km",
|
|
107
|
+
source: "GDACS",
|
|
108
|
+
priority: "red",
|
|
109
|
+
raised: "2026-09-25T06:25:00Z",
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
id: "krakatau",
|
|
113
|
+
category: "Disruption exposures",
|
|
114
|
+
headline: "Volcanic eruption is ongoing for Krakatau in Indonesia",
|
|
115
|
+
source: "GDACS",
|
|
116
|
+
priority: "green",
|
|
117
|
+
raised: "2026-09-24T14:10:00Z",
|
|
118
|
+
},
|
|
119
|
+
];
|
|
120
|
+
/** Concentration climbing through 2019–2022, the way the reference screen's history does. */
|
|
121
|
+
const history = Array.from({ length: 28 }, (_, index) => {
|
|
122
|
+
const month = new Date(Date.UTC(2019, 9 + index, 1)).toISOString().slice(0, 7);
|
|
123
|
+
return { month, concentration: Math.round((38 + index * 1.6 + Math.sin(index / 2) * 4) * 100) / 100 };
|
|
124
|
+
});
|
|
125
|
+
const raisedRows = (rows, category) => answer(["this", "headline", "source", "priority", "raised", "category"], rows
|
|
126
|
+
.filter((row) => category === undefined || row.category === category)
|
|
127
|
+
.map((row) => ({
|
|
128
|
+
this: uri(`${alert}${row.id}`),
|
|
129
|
+
headline: text(row.headline),
|
|
130
|
+
source: text(row.source),
|
|
131
|
+
priority: text(row.priority),
|
|
132
|
+
raised: date(row.raised),
|
|
133
|
+
category: text(row.category),
|
|
134
|
+
})));
|
|
135
|
+
/**
|
|
136
|
+
* What a variable was narrowed to, as the compiled query carries it.
|
|
137
|
+
*
|
|
138
|
+
* `compileBinding` writes a bound parameter in as `VALUES ?name { <iri> }` or `VALUES ?name { "text" }`, so
|
|
139
|
+
* a fixture that wants to behave like a datastore reads it back out of the text it was handed.
|
|
140
|
+
*/
|
|
141
|
+
const narrowedTo = (query, name) => {
|
|
142
|
+
const match = new RegExp(`VALUES \\?${name} \\{\\s*(?:<([^>]+)>|"([^"]*)")`).exec(query);
|
|
143
|
+
return match?.[1] ?? match?.[2];
|
|
144
|
+
};
|
|
145
|
+
/** The IRIs a label pass is asking about, in the order the `VALUES` names them. */
|
|
146
|
+
const askedFor = (query) => {
|
|
147
|
+
const values = /VALUES \?entity \{([^}]*)\}/.exec(query)?.[1] ?? "";
|
|
148
|
+
return [...values.matchAll(/<([^>]+)>/g)].flatMap((match) => (match[1] === undefined ? [] : [match[1]]));
|
|
149
|
+
};
|
|
150
|
+
function respond(query) {
|
|
151
|
+
// The label pass, which every view depends on: a row carries an IRI and shows the thing's name.
|
|
152
|
+
if (query.includes("SELECT ?entity ?name") && query.includes("VALUES ?entity")) {
|
|
153
|
+
return answer(["entity", "name"], askedFor(query).flatMap((iri) => {
|
|
154
|
+
const name = names[iri];
|
|
155
|
+
return name === undefined ? [] : [{ entity: uri(iri), name: text(name) }];
|
|
156
|
+
}));
|
|
157
|
+
}
|
|
158
|
+
if (query.includes("factsetIndustry")) {
|
|
159
|
+
return answer(["this", "name", "industry", "country", "entityType"], [
|
|
160
|
+
{
|
|
161
|
+
this: uri(referenceEntity),
|
|
162
|
+
name: text("Cisco Systems, Inc."),
|
|
163
|
+
industry: text("Factset Industry · Information Technology Services"),
|
|
164
|
+
country: uri(`${country}us`),
|
|
165
|
+
entityType: text("Public company"),
|
|
166
|
+
},
|
|
167
|
+
]);
|
|
168
|
+
}
|
|
169
|
+
if (query.includes("placedOrder")) {
|
|
170
|
+
return answer(["outgoing", "outgoingValue", "suppliers", "customers"], [
|
|
171
|
+
{
|
|
172
|
+
outgoing: number(7739),
|
|
173
|
+
outgoingValue: number(2923174261),
|
|
174
|
+
suppliers: number(8),
|
|
175
|
+
customers: number(125),
|
|
176
|
+
},
|
|
177
|
+
]);
|
|
178
|
+
}
|
|
179
|
+
if (query.includes('alertKind "notification"'))
|
|
180
|
+
return raisedRows(notifications, narrowedTo(query, "category"));
|
|
181
|
+
if (query.includes('alertKind "disruption"'))
|
|
182
|
+
return raisedRows(disruptions, narrowedTo(query, "category"));
|
|
183
|
+
if (query.includes("alertKind")) {
|
|
184
|
+
return answer(["kind", "total", "qualifier", "severity"], flags.map((flag) => ({
|
|
185
|
+
kind: text(flag.kind),
|
|
186
|
+
total: number(flag.total),
|
|
187
|
+
severity: text(flag.severity),
|
|
188
|
+
...(flag.qualifier === undefined ? {} : { qualifier: text(flag.qualifier) }),
|
|
189
|
+
})));
|
|
190
|
+
}
|
|
191
|
+
if (query.includes("buyerConcentration")) {
|
|
192
|
+
return answer(["this", "buyerConcentration", "supplierConcentration", "disruptions"], exposures.map((one) => ({
|
|
193
|
+
this: uri(`${country}${one.id}`),
|
|
194
|
+
buyerConcentration: number(one.buyer),
|
|
195
|
+
supplierConcentration: number(one.supplier),
|
|
196
|
+
disruptions: number(one.disruptions),
|
|
197
|
+
})));
|
|
198
|
+
}
|
|
199
|
+
if (query.includes("concentrationScore")) {
|
|
200
|
+
return answer(["month", "concentration"], history.map((one) => ({ month: text(one.month), concentration: number(one.concentration) })));
|
|
201
|
+
}
|
|
202
|
+
if (query.includes("ltmConcentration")) {
|
|
203
|
+
const only = narrowedTo(query, "country");
|
|
204
|
+
return answer(["this", "concentration", "yearOnYear", "country"], suppliers
|
|
205
|
+
.filter((one) => only === undefined || `${country}${one.country}` === only)
|
|
206
|
+
.map((one) => ({
|
|
207
|
+
this: uri(`${entity}${one.id}`),
|
|
208
|
+
concentration: number(one.concentration),
|
|
209
|
+
yearOnYear: number(one.yearOnYear),
|
|
210
|
+
country: uri(`${country}${one.country}`),
|
|
211
|
+
})));
|
|
212
|
+
}
|
|
213
|
+
return nothing;
|
|
214
|
+
}
|
|
215
|
+
/** A `SparqlSelect` over the fixture. Async because every other one is, not because anything waits. */
|
|
216
|
+
export const fakeSparqlSelect = (query) => Promise.resolve(respond(query));
|
|
@@ -10,5 +10,5 @@ export declare const generatedRoot: URL;
|
|
|
10
10
|
/** Whether `just generate` has been run for this realm in this checkout. */
|
|
11
11
|
export declare function hasGeneratedSchemas(realm: Realm, root?: URL): boolean;
|
|
12
12
|
export declare const generatedNotice = "built from this realm's own datastore shapes, which are a subset of the canonical set";
|
|
13
|
-
/** Node only: it reads the filesystem.
|
|
13
|
+
/** Node only: it reads the filesystem. `hornsShapeCatalog` calls it at build time; a worker never does. */
|
|
14
14
|
export declare function ldkitSchemasFor(realm: Realm, root?: URL, preferGenerated?: boolean): Promise<LdkitSource>;
|
|
@@ -17,7 +17,7 @@ export function hasGeneratedSchemas(realm, root = generatedRoot) {
|
|
|
17
17
|
return existsSync(new URL("index.ts", generatedDirFor(realm, root)));
|
|
18
18
|
}
|
|
19
19
|
export const generatedNotice = "built from this realm's own datastore shapes, which are a subset of the canonical set";
|
|
20
|
-
/** Node only: it reads the filesystem.
|
|
20
|
+
/** Node only: it reads the filesystem. `hornsShapeCatalog` calls it at build time; a worker never does. */
|
|
21
21
|
export async function ldkitSchemasFor(realm, root = generatedRoot, preferGenerated = false) {
|
|
22
22
|
if (preferGenerated && hasGeneratedSchemas(realm, root)) {
|
|
23
23
|
return { module: await import(__rewriteRelativeImportExtension(new URL("index.ts", generatedDirFor(realm, root)).href)), origin: "generated" };
|
|
@@ -28,7 +28,9 @@ export function studioProgressLine(line) {
|
|
|
28
28
|
return match(event["type"])
|
|
29
29
|
.with("reading", () => step("Reading the Horn and the data behind it"))
|
|
30
30
|
.with("round", () => (event["step"] === 1 && agent === undefined ? step("Thinking") : undefined))
|
|
31
|
-
.with("tool-call", () => event["repeated"] === true || name === "propose_horn_patch"
|
|
31
|
+
.with("tool-call", () => event["repeated"] === true || name === "propose_horn_patch"
|
|
32
|
+
? undefined
|
|
33
|
+
: step(toolLines[name] ?? `Using ${name}`))
|
|
32
34
|
.with("proposal-accepted", () => (name === "propose_horn_patch" ? step("Changed the Horn") : undefined))
|
|
33
35
|
.with("proposal-rejected", () => step("That change did not hold up against the data, trying again"))
|
|
34
36
|
.with("rehydrated", () => step("Starting over from the Horn as it stands"))
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** One entry per surfaced export: the declaration text the Studio may read. */
|
|
2
|
+
export type ComponentApi = Readonly<Record<string, string>>;
|
|
3
|
+
/** Node only: `import.meta.resolve` is unavailable under vitest's transform, so a test passes its own root. */
|
|
4
|
+
export declare function sdkDistRoot(): string;
|
|
5
|
+
export declare function componentApiFrom(root: string, hops?: number): ComponentApi;
|