@manta-eu/horns-client 0.1.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.
Files changed (101) hide show
  1. package/LICENSE +21 -0
  2. package/dist/app/coverage-run.d.ts +5 -0
  3. package/dist/app/coverage-run.js +221 -0
  4. package/dist/app/home-page.d.ts +1 -0
  5. package/dist/app/home-page.js +11 -0
  6. package/dist/app/horn-page.d.ts +1 -0
  7. package/dist/app/horn-page.js +38 -0
  8. package/dist/app/horn-studio.d.ts +30 -0
  9. package/dist/app/horn-studio.js +428 -0
  10. package/dist/app/horn-view-page.d.ts +1 -0
  11. package/dist/app/horn-view-page.js +96 -0
  12. package/dist/app/horns-page.d.ts +1 -0
  13. package/dist/app/horns-page.js +43 -0
  14. package/dist/app/new-horn-page.d.ts +1 -0
  15. package/dist/app/new-horn-page.js +34 -0
  16. package/dist/app/paths.d.ts +13 -0
  17. package/dist/app/paths.js +32 -0
  18. package/dist/app/routes.d.ts +12 -0
  19. package/dist/app/routes.js +32 -0
  20. package/dist/app/rule-page.d.ts +1 -0
  21. package/dist/app/rule-page.js +84 -0
  22. package/dist/app/rules-page.d.ts +1 -0
  23. package/dist/app/rules-page.js +74 -0
  24. package/dist/app/saved-horn.d.ts +7 -0
  25. package/dist/app/saved-horn.js +26 -0
  26. package/dist/app/session-gate.d.ts +6 -0
  27. package/dist/app/session-gate.js +19 -0
  28. package/dist/app/shape-entity-page.d.ts +1 -0
  29. package/dist/app/shape-entity-page.js +68 -0
  30. package/dist/app/shape-view-studio.d.ts +14 -0
  31. package/dist/app/shape-view-studio.js +163 -0
  32. package/dist/app/shape-zoo-page.d.ts +1 -0
  33. package/dist/app/shape-zoo-page.js +58 -0
  34. package/dist/app/shapes-page.d.ts +1 -0
  35. package/dist/app/shapes-page.js +75 -0
  36. package/dist/app/showcase-page.d.ts +2 -0
  37. package/dist/app/showcase-page.js +99 -0
  38. package/dist/app/variable-bar.d.ts +6 -0
  39. package/dist/app/variable-bar.js +42 -0
  40. package/dist/client.d.ts +10 -0
  41. package/dist/client.js +22 -0
  42. package/dist/components/app-shell.d.ts +1 -0
  43. package/dist/components/app-shell.js +18 -0
  44. package/dist/components/catalog-card-body.d.ts +8 -0
  45. package/dist/components/catalog-card-body.js +29 -0
  46. package/dist/components/catalog-card-view.d.ts +2 -0
  47. package/dist/components/catalog-card-view.js +13 -0
  48. package/dist/components/chat-column.d.ts +22 -0
  49. package/dist/components/chat-column.js +20 -0
  50. package/dist/components/page-header.d.ts +15 -0
  51. package/dist/components/page-header.js +11 -0
  52. package/dist/components/plan-runs.d.ts +28 -0
  53. package/dist/components/plan-runs.js +90 -0
  54. package/dist/components/rasterize.d.ts +34 -0
  55. package/dist/components/rasterize.js +115 -0
  56. package/dist/components/read-the-code.d.ts +14 -0
  57. package/dist/components/read-the-code.js +43 -0
  58. package/dist/components/snapshot-view.d.ts +12 -0
  59. package/dist/components/snapshot-view.js +25 -0
  60. package/dist/components/states.d.ts +23 -0
  61. package/dist/components/states.js +22 -0
  62. package/dist/components/studio-chat.d.ts +47 -0
  63. package/dist/components/studio-chat.js +63 -0
  64. package/dist/components/written-horn-view.d.ts +15 -0
  65. package/dist/components/written-horn-view.js +25 -0
  66. package/dist/index.d.ts +11 -0
  67. package/dist/index.js +16 -0
  68. package/dist/lib/horn-review.d.ts +27 -0
  69. package/dist/lib/horn-review.js +59 -0
  70. package/dist/lib/manta/__tests__/sdk-realm.d.ts +2 -0
  71. package/dist/lib/manta/__tests__/sdk-realm.js +17 -0
  72. package/dist/lib/manta/ldo.d.ts +6 -0
  73. package/dist/lib/manta/ldo.js +14 -0
  74. package/dist/lib/manta/providers.d.ts +10 -0
  75. package/dist/lib/manta/providers.js +48 -0
  76. package/dist/lib/manta/session.d.ts +47 -0
  77. package/dist/lib/manta/session.js +204 -0
  78. package/dist/lib/manta/sparql-select.d.ts +2 -0
  79. package/dist/lib/manta/sparql-select.js +19 -0
  80. package/dist/lib/plan-runs.d.ts +14 -0
  81. package/dist/lib/plan-runs.js +26 -0
  82. package/dist/lib/problem.d.ts +8 -0
  83. package/dist/lib/problem.js +16 -0
  84. package/dist/lib/shapes/catalog.d.ts +14 -0
  85. package/dist/lib/shapes/catalog.js +25 -0
  86. package/dist/lib/shapes/ldkit-source.d.ts +14 -0
  87. package/dist/lib/shapes/ldkit-source.js +26 -0
  88. package/dist/lib/studio-progress.d.ts +7 -0
  89. package/dist/lib/studio-progress.js +36 -0
  90. package/dist/lib/telemetry/sentry.d.ts +1 -0
  91. package/dist/lib/telemetry/sentry.js +17 -0
  92. package/dist/lib/theme.d.ts +2 -0
  93. package/dist/lib/theme.js +7 -0
  94. package/dist/lib/time-ago.d.ts +5 -0
  95. package/dist/lib/time-ago.js +35 -0
  96. package/dist/lib/written-surface.d.ts +9 -0
  97. package/dist/lib/written-surface.js +10 -0
  98. package/dist/render/horn-client.d.ts +1 -0
  99. package/dist/render/horn-client.js +10 -0
  100. package/dist/styles.css +2 -0
  101. package/package.json +77 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Manta AI AS
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,5 @@
1
+ type CoverageRunProps = {
2
+ datastoreId: string;
3
+ };
4
+ export declare function CoverageRun({ datastoreId }: CoverageRunProps): import("react").JSX.Element;
5
+ export {};
@@ -0,0 +1,221 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useRef, useState } from "react";
3
+ import { useQuery } from "@tanstack/react-query";
4
+ import { isPresent } from "@manta-eu/lib/present";
5
+ import { Link } from "react-router";
6
+ import { match } from "ts-pattern";
7
+ import { Check, Minus, Play, Square, TriangleAlert, X } from "lucide-react";
8
+ import { Badge, Button, cn } from "@manta-eu/react";
9
+ import { buildShapeZoo, shapeViewSpecSchema } from "@manta-eu/horns/shapes";
10
+ import { Card, CardTitle } from "../components/page-header";
11
+ import { SnapshotViewBody } from "../components/snapshot-view";
12
+ import { ErrorState, Loading } from "../components/states";
13
+ import { shapeZooPath } from "./paths";
14
+ import { isRecord } from "@manta-eu/horns";
15
+ import { readNdjson } from "@manta-eu/horns-server/shared";
16
+ import { problemFrom, problemOf } from "../lib/problem";
17
+ import { browserSparqlSelect } from "../lib/manta/sparql-select";
18
+ import { shapesRealm } from "../lib/shapes/catalog";
19
+ // The Studio designing a view, watched while it does it. A shape takes minutes and most of that is spent before
20
+ // the model says anything, so the run is shown as the four stages it actually goes through, each round the zoo
21
+ // judged, and what the zoo said when it refused one — the reason being the part worth reading.
22
+ const stages = ["sampling", "reading", "designing", "verdict"];
23
+ const stageLabels = {
24
+ sampling: "Looking at real instances",
25
+ reading: "Reading what fields point at",
26
+ designing: "Designing the card",
27
+ verdict: "Showing it to the zoo",
28
+ };
29
+ const stepKinds = ["sampling", "reading", "round", "rejected", "accepted"];
30
+ function readStrings(value) {
31
+ return Array.isArray(value) ? value.filter((one) => typeof one === "string") : [];
32
+ }
33
+ function readNumber(value) {
34
+ return typeof value === "number" ? value : undefined;
35
+ }
36
+ /** Fold one streamed step into the shape it belongs to, keeping every shape the run has touched. */
37
+ function applyStep(runs, line) {
38
+ const kind = stepKinds.find((candidate) => candidate === line["kind"]);
39
+ const schema = line["schema"];
40
+ const index = readNumber(line["index"]);
41
+ const total = readNumber(line["total"]);
42
+ if (kind === undefined || typeof schema !== "string" || index === undefined || total === undefined)
43
+ return runs;
44
+ const round = readNumber(line["round"]) ?? 1;
45
+ const existing = runs.find((run) => run.schema === schema);
46
+ const base = existing ?? { schema, index, total, stage: "sampling", rounds: [] };
47
+ const next = match(kind)
48
+ .with("sampling", () => ({ ...base, stage: "sampling" }))
49
+ .with("reading", () => ({ ...base, stage: "reading" }))
50
+ .with("round", () => ({
51
+ ...base,
52
+ stage: "designing",
53
+ rounds: base.rounds.some((one) => one.round === round) ? base.rounds : [...base.rounds, { round }],
54
+ }))
55
+ .with("rejected", () => ({
56
+ ...base,
57
+ stage: "verdict",
58
+ rounds: base.rounds.map((one) => one.round === round ? { ...one, refused: readStrings(line["problems"])[0] ?? "no reason given" } : one),
59
+ }))
60
+ .with("accepted", () => ({ ...base, stage: "verdict" }))
61
+ .exhaustive();
62
+ return isPresent(existing) ? runs.map((run) => (run.schema === schema ? next : run)) : [...runs, next];
63
+ }
64
+ /** Fold the finished-shape event in: the verdict, and the card it designed when there is one. */
65
+ function applyFinished(runs, line) {
66
+ const schema = line["schema"];
67
+ const status = ["accepted", "gave-up", "no-data", "failed"].find((candidate) => candidate === line["status"]);
68
+ if (typeof schema !== "string" || status === undefined)
69
+ return runs;
70
+ const parsed = shapeViewSpecSchema.safeParse(line["spec"]);
71
+ const verdict = {
72
+ status: match(status)
73
+ .with("accepted", () => "accepted")
74
+ .with("gave-up", () => "gave-up")
75
+ .with("no-data", () => "no-data")
76
+ .otherwise(() => "failed"),
77
+ ...(readNumber(line["rounds"]) === undefined ? {} : { rounds: readNumber(line["rounds"]) }),
78
+ ...(typeof line["error"] === "string" ? { error: line["error"] } : {}),
79
+ ...(isPresent(readStrings(line["problems"])) ? { problems: readStrings(line["problems"]) } : {}),
80
+ ...(isPresent(readStrings(line["advice"])) ? { advice: readStrings(line["advice"]) } : {}),
81
+ ...(parsed.success ? { spec: parsed.data } : {}),
82
+ };
83
+ const index = readNumber(line["index"]) ?? 1;
84
+ const total = readNumber(line["total"]) ?? 1;
85
+ const existing = runs.find((run) => run.schema === schema);
86
+ const next = { ...(existing ?? { schema, index, total, rounds: [] }), stage: "verdict", verdict };
87
+ return isPresent(existing) ? runs.map((run) => (run.schema === schema ? next : run)) : [...runs, next];
88
+ }
89
+ function stageState(run, stage) {
90
+ const reached = stages.indexOf(run.stage);
91
+ const at = stages.indexOf(stage);
92
+ if (isPresent(run.verdict)) {
93
+ if (run.verdict.status === "accepted")
94
+ return "done";
95
+ // A shape with no data never got past sampling; a failure stops wherever it was.
96
+ if (at < reached)
97
+ return "done";
98
+ return at === reached ? "stopped" : "waiting";
99
+ }
100
+ if (at < reached)
101
+ return "done";
102
+ return at === reached ? "active" : "waiting";
103
+ }
104
+ function StageRail({ run }) {
105
+ return (_jsx("ol", { "data-stages": true, className: "flex flex-col gap-1.5 sm:flex-row sm:flex-wrap sm:items-center sm:gap-x-2 sm:gap-y-2", children: stages.map((stage, index) => {
106
+ const state = stageState(run, stage);
107
+ return (_jsxs("li", { "data-stage": stage, "data-state": state, className: "flex items-center gap-2", children: [_jsx("span", { "aria-hidden": "true", className: cn("flex size-5 shrink-0 items-center justify-center rounded-full border text-[10px]",
108
+ // Only the classes the design system's own build compiled exist here: its tokens sit in plain
109
+ // `:root` blocks, so Tailwind cannot mint `bg-success/15` and the like from them (globals.css).
110
+ state === "done" && "border-success text-success", state === "active" && "animate-pulse border-info text-info", state === "stopped" && "border-error text-error-foreground", state === "waiting" && "border-default-border text-muted-foreground"), children: match(state)
111
+ .with("done", () => _jsx(Check, { size: 12 }))
112
+ .with("stopped", () => _jsx(X, { size: 12 }))
113
+ .with("waiting", () => _jsx(Minus, { size: 12 }))
114
+ .otherwise(() => (_jsx("span", { className: "size-1.5 rounded-full bg-info" }))) }), _jsx("span", { className: cn("text-xs", state === "waiting" ? "text-muted-foreground" : "text-default-foreground", state === "active" && "font-medium"), children: stageLabels[stage] }), index < stages.length - 1 ? (_jsx("span", { "aria-hidden": "true", className: "hidden h-px w-4 bg-default-border sm:block" })) : null] }, stage));
115
+ }) }));
116
+ }
117
+ function Rounds({ rounds }) {
118
+ if (!isPresent(rounds))
119
+ return null;
120
+ return (_jsx("div", { "data-rounds": true, className: "flex flex-col gap-1.5", children: rounds.map((round) => (_jsxs("div", { className: "flex flex-wrap items-baseline gap-2", children: [_jsx(Badge, { variant: isPresent(round.refused) ? "destructive" : "secondary", size: "sm", children: `Round ${round.round}` }), isPresent(round.refused) ? (_jsx("span", { className: "text-xs text-muted-foreground", children: `the zoo refused it — ${round.refused}` })) : (_jsx("span", { className: "text-xs text-muted-foreground", children: "designing\u2026" }))] }, round.round))) }));
121
+ }
122
+ /** The card the Studio just designed, drawn on real instances — the thing the run is for. */
123
+ function DesignedCard({ datastoreId, spec }) {
124
+ const realm = shapesRealm();
125
+ const zoo = useQuery({
126
+ queryKey: ["coverage-preview", datastoreId, spec.schema],
127
+ queryFn: ({ signal }) => buildShapeZoo({ spec, realm, datastoreId, select: browserSparqlSelect(datastoreId), signal }),
128
+ });
129
+ if (zoo.isPending)
130
+ return _jsx(Loading, { label: "Drawing it on real instances\u2026", rows: 3 });
131
+ if (zoo.isError)
132
+ return _jsx("p", { className: "text-xs text-muted-foreground", children: `Could not draw it: ${zoo.error.message}` });
133
+ const view = zoo.data.snapshot.views[0];
134
+ const content = view?.content;
135
+ const variant = zoo.data.variants[0];
136
+ if (!view || content?.lane !== "sandbox" || !isPresent(variant))
137
+ return null;
138
+ return (_jsxs("div", { "data-designed-card": true, className: "flex flex-col gap-2 rounded-lg border border-default-border bg-card p-4", children: [_jsx(SnapshotViewBody, { snapshot: zoo.data.snapshot, view: {
139
+ ...view,
140
+ title: `${spec.schema} as the Studio designed it`,
141
+ content: { ...content, rows: content.rows.filter((row) => row["this"] === variant.entity) },
142
+ } }), _jsx("p", { className: "text-xs text-muted-foreground", children: `One of ${zoo.data.distinct} variants, from ${zoo.data.sampled} sampled instances.` })] }));
143
+ }
144
+ function VerdictPanel({ datastoreId, run }) {
145
+ const verdict = run.verdict;
146
+ if (verdict === undefined)
147
+ return null;
148
+ return match(verdict.status)
149
+ .with("accepted", () => (_jsxs("div", { "data-verdict": "accepted", className: "flex flex-col gap-3", children: [_jsx("p", { className: "text-sm text-default-foreground", children: `The zoo accepted it in ${verdict.rounds ?? 1} round${verdict.rounds === 1 ? "" : "s"}.` }), isPresent(verdict.spec) ? (_jsxs(_Fragment, { children: [_jsx("ul", { className: "flex flex-wrap gap-1.5", children: verdict.spec.fields.map((field) => (_jsx("li", { "data-field": field.name, className: "rounded-md border border-default-border px-2 py-0.5 font-mono text-xs text-muted-foreground", children: `${field.name} · ${field.type}` }, field.name))) }), _jsx(DesignedCard, { datastoreId: datastoreId, spec: verdict.spec })] })) : null, isPresent(verdict.advice) ? (_jsxs("div", { "data-advice": true, className: "flex flex-col gap-2 rounded-lg border border-warning px-4 py-3", children: [_jsx("p", { className: "text-sm font-medium text-default-foreground", children: "What a stranger would not understand" }), _jsx("ul", { className: "flex list-disc flex-col gap-1 pl-5 text-sm text-muted-foreground", children: verdict.advice.map((one) => (_jsx("li", { children: one }, one))) }), _jsx("p", { className: "text-xs text-muted-foreground", children: "The zoo still accepted this view. This is advice for whoever reviews it, not a reason it failed." })] })) : null, _jsx(Link, { className: "text-sm text-muted-foreground underline hover:text-default-foreground", to: shapeZooPath(datastoreId, run.schema), children: "Open its zoo to keep it" })] })))
150
+ .with("gave-up", () => (_jsxs("div", { "data-verdict": "gave-up", className: "flex flex-col gap-2", children: [_jsx("p", { className: "text-sm text-default-foreground", children: `It gave up after ${verdict.rounds ?? stages.length} round${verdict.rounds === 1 ? "" : "s"}. What the zoo kept saying:` }), _jsx("ul", { className: "flex list-disc flex-col gap-1 pl-5 text-sm text-muted-foreground", children: (verdict.problems ?? []).map((problem) => (_jsx("li", { children: problem }, problem))) })] })))
151
+ .with("no-data", () => (_jsx("p", { "data-verdict": "no-data", className: "text-sm text-muted-foreground", children: "Nothing to design from \u2014 this datastore holds no instances of it." })))
152
+ .otherwise(() => (_jsxs("p", { "data-verdict": "failed", className: "flex items-start gap-2 text-sm text-error-foreground", children: [_jsx(TriangleAlert, { size: 16, "aria-hidden": "true", className: "mt-0.5 shrink-0" }), verdict.error ?? "It broke, with no reason given."] })));
153
+ }
154
+ function verdictBadge(run) {
155
+ if (run.verdict === undefined)
156
+ return _jsx(Badge, { variant: "info", children: "Working" });
157
+ return match(run.verdict.status)
158
+ .with("accepted", () => _jsx(Badge, { variant: "success", children: "Accepted" }))
159
+ .with("gave-up", () => _jsx(Badge, { variant: "warning", children: "Gave up" }))
160
+ .with("no-data", () => _jsx(Badge, { variant: "secondary", children: "No data" }))
161
+ .otherwise(() => _jsx(Badge, { variant: "destructive", children: "Failed" }));
162
+ }
163
+ export function CoverageRun({ datastoreId }) {
164
+ const [runs, setRuns] = useState([]);
165
+ const [tally, setTally] = useState();
166
+ const [running, setRunning] = useState(false);
167
+ const [failure, setFailure] = useState();
168
+ const [stopped, setStopped] = useState(false);
169
+ const stop = useRef(undefined);
170
+ async function run() {
171
+ const controller = new AbortController();
172
+ stop.current = controller;
173
+ setRunning(true);
174
+ setFailure(undefined);
175
+ setStopped(false);
176
+ setTally(undefined);
177
+ setRuns([]);
178
+ try {
179
+ const response = await fetch(`/api/d/${encodeURIComponent(datastoreId)}/shapes/cover`, {
180
+ method: "POST",
181
+ headers: { "content-type": "application/json" },
182
+ body: JSON.stringify({ limit: 1 }),
183
+ signal: controller.signal,
184
+ });
185
+ if (!response.ok) {
186
+ setFailure(await problemFrom(response, `The run could not start (${response.status}).`));
187
+ return;
188
+ }
189
+ const body = await readNdjson(response, (line) => {
190
+ if (line["type"] === "progress")
191
+ setRuns((sofar) => applyStep(sofar, line));
192
+ if (line["type"] === "shape")
193
+ setRuns((sofar) => applyFinished(sofar, line));
194
+ });
195
+ if (isRecord(body) && body["type"] === "error") {
196
+ setFailure(problemOf(body, "The run failed."));
197
+ return;
198
+ }
199
+ if (isRecord(body)) {
200
+ setTally({
201
+ accepted: readStrings(body["accepted"]),
202
+ gaveUp: readNumber(body["gaveUp"]) ?? 0,
203
+ noData: readNumber(body["noData"]) ?? 0,
204
+ failed: readNumber(body["failed"]) ?? 0,
205
+ });
206
+ }
207
+ }
208
+ catch (caught) {
209
+ if (controller.signal.aborted)
210
+ setStopped(true);
211
+ else
212
+ setFailure(caught instanceof Error ? caught.message : String(caught));
213
+ }
214
+ finally {
215
+ setRunning(false);
216
+ stop.current = undefined;
217
+ }
218
+ }
219
+ const working = runs.find((one) => one.verdict === undefined);
220
+ return (_jsx(Card, { children: _jsxs("section", { "aria-labelledby": "coverage-run", className: "flex flex-col gap-4", children: [_jsx(CardTitle, { aside: running ? _jsx(Badge, { variant: "info", children: "Running" }) : undefined, children: _jsx("span", { id: "coverage-run", children: "Watch the Studio design one" }) }), _jsx("p", { className: "text-sm text-muted-foreground", children: "It takes the shape at the top of the queue, looks at real instances, designs a card and shows it to the zoo until the zoo accepts it. Nothing is saved: this is for watching it work." }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsxs(Button, { variant: "primary", size: "sm", disabled: running, onClick: () => void run(), children: [_jsx(Play, { size: 14, "aria-hidden": "true" }), running ? "Working…" : "Run the next one"] }), running ? (_jsxs(Button, { variant: "secondary", size: "sm", onClick: () => stop.current?.abort(), children: [_jsx(Square, { size: 14, "aria-hidden": "true" }), "Stop"] })) : null] }), _jsx("p", { "aria-live": "polite", className: "sr-only", children: isPresent(working) ? `${working.schema}: ${stageLabels[working.stage]}` : "" }), isPresent(failure) ? _jsx(ErrorState, { title: "That run did not start", hint: failure }) : null, isPresent(runs) ? (_jsx("ol", { "data-coverage-log": true, "aria-label": "What the Studio is doing", className: "flex flex-col gap-3", children: runs.map((one) => (_jsxs("li", { "data-shape": one.schema, className: "flex flex-col gap-3 rounded-lg border border-default-border bg-card/60 p-4", children: [_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-2", children: [_jsxs("span", { className: "flex min-w-0 flex-col", children: [_jsx("span", { className: "truncate font-mono text-sm text-default-foreground", children: one.schema }), _jsx("span", { className: "text-xs text-muted-foreground", children: `Shape ${one.index} of ${one.total}` })] }), verdictBadge(one)] }), _jsx(StageRail, { run: one }), _jsx(Rounds, { rounds: one.rounds }), _jsx(VerdictPanel, { datastoreId: datastoreId, run: one })] }, one.schema))) })) : null, stopped ? _jsx("p", { className: "text-sm text-muted-foreground", children: "Stopped." }) : null, isPresent(tally) ? (_jsx("p", { "data-tally": true, className: "text-sm text-muted-foreground", children: `Finished: ${tally.accepted.length} designed, ${tally.gaveUp} gave up, ${tally.noData} with no data, ${tally.failed} broke. Nothing was saved.` })) : null] }) }));
221
+ }
@@ -0,0 +1 @@
1
+ export declare function HomePage(): import("react").JSX.Element;
@@ -0,0 +1,11 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { isPresent } from "@manta-eu/lib/present";
3
+ import { Link } from "react-router";
4
+ import { ChevronRight, Database } from "lucide-react";
5
+ import { EmptyState } from "../components/states";
6
+ import { hornsPath } from "./paths";
7
+ import { useMantaSession } from "../lib/manta/session";
8
+ export function HomePage() {
9
+ const { datastores } = useMantaSession();
10
+ return (_jsxs("div", { className: "flex w-full flex-col gap-6", children: [_jsxs("header", { className: "flex flex-col gap-1", children: [_jsx("h1", { className: "text-2xl font-semibold text-default-foreground", children: "Manta Horns" }), _jsx("p", { className: "text-sm text-muted-foreground", children: "Choose a datastore to see the Horns made from its data, or to make a new one." })] }), !isPresent(datastores) ? (_jsx(EmptyState, { title: "Your account has no datastores yet", hint: "A datastore is where your connected data lives. Ask the person who manages Manta at your company to give you access to one.", icon: _jsx(Database, { size: 20 }) })) : (_jsx("ul", { "data-datastores": true, className: "grid gap-3 sm:grid-cols-2 md:grid-cols-3", children: datastores.map((datastore) => (_jsx("li", { children: _jsxs(Link, { to: hornsPath(datastore.id), className: "flex items-center gap-3 rounded-lg border border-default-border bg-card px-4 py-3 transition-colors hover:bg-card/70", children: [_jsx("span", { className: "grid size-9 shrink-0 place-items-center rounded-md bg-muted text-muted-foreground", children: _jsx(Database, { size: 16, "aria-hidden": "true" }) }), _jsx("span", { className: "min-w-0 flex-1 truncate font-medium text-default-foreground", children: datastore.name }), _jsx(ChevronRight, { size: 16, "aria-hidden": "true", className: "shrink-0 text-muted-foreground" })] }) }, datastore.id))) }))] }));
11
+ }
@@ -0,0 +1 @@
1
+ export declare function HornPage(): import("react").JSX.Element;
@@ -0,0 +1,38 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { isPresent } from "@manta-eu/lib/present";
3
+ import { useParams, useSearchParams } from "react-router";
4
+ import { variablesFromSearch } from "@manta-eu/horns/render";
5
+ import { BackLink, DatastoreMissing, ErrorState, Loading } from "../components/states";
6
+ import { hornsPath } from "./paths";
7
+ import { HornStudio } from "./horn-studio";
8
+ import { useSavedHorn } from "./saved-horn";
9
+ import { VariableBar } from "./variable-bar";
10
+ import { MantaSparqlProvider } from "../lib/manta/providers";
11
+ import { useMantaSession } from "../lib/manta/session";
12
+ // A saved Horn, as the Studio's whole screen: the document comes back from the datastore and the snapshot is
13
+ // built from it, so what is on screen is what is stored rather than what a client happened to be holding.
14
+ export function HornPage() {
15
+ const { datastoreId, slug } = useParams();
16
+ const { datastores } = useMantaSession();
17
+ const datastore = datastores.find((candidate) => candidate.id === datastoreId);
18
+ if (!isPresent(datastore)) {
19
+ return (_jsx(Centered, { children: _jsx(DatastoreMissing, {}) }));
20
+ }
21
+ // The provider supplies the query client, so the reader has to sit inside it.
22
+ return (_jsx(MantaSparqlProvider, { datastoreId: datastore.id, children: _jsx(SavedHornPage, { slug: slug ?? "", datastoreId: datastore.id }) }));
23
+ }
24
+ /** Until there is a Horn to put in the Studio, the screen is just this. */
25
+ function Centered({ children }) {
26
+ return (_jsx("main", { className: "flex h-svh w-full flex-col items-center justify-center gap-4 bg-sidebar p-6", children: _jsx("div", { className: "flex w-full max-w-2xl flex-col gap-4", children: children }) }));
27
+ }
28
+ function SavedHornPage({ datastoreId, slug }) {
29
+ const [search] = useSearchParams();
30
+ const horn = useSavedHorn(datastoreId, slug);
31
+ if (horn.isError) {
32
+ return (_jsxs(Centered, { children: [_jsx(BackLink, { to: hornsPath(datastoreId), label: "All Horns" }), _jsx(ErrorState, { title: "That Horn could not be opened", hint: horn.error instanceof Error ? horn.error.message : "It may have been removed." })] }));
33
+ }
34
+ if (!isPresent(horn.data)) {
35
+ return (_jsx(Centered, { children: _jsx(Loading, { label: "Opening the Horn\u2026" }) }));
36
+ }
37
+ return (_jsx(HornStudio, { datastoreId: datastoreId, slug: slug, stored: horn.data.horn, revision: horn.data.revision, backTo: hornsPath(datastoreId), variables: variablesFromSearch(horn.data.horn, search.toString()), toolbar: _jsx(VariableBar, { horn: horn.data.horn }) }));
38
+ }
@@ -0,0 +1,30 @@
1
+ import { type ReactNode } from "react";
2
+ import { type Horn } from "@manta-eu/horns";
3
+ type HornStudioProps = {
4
+ datastoreId: string;
5
+ /** Absent for a Horn that has never been saved: its first save names it from its title. */
6
+ slug?: string;
7
+ stored: Horn;
8
+ /** Absent for a Horn that has never been saved: there is no revision to build on. */
9
+ revision?: string;
10
+ /** Where the way out goes. */
11
+ backTo: string;
12
+ welcome?: string;
13
+ reassurance?: string;
14
+ placeholder?: string;
15
+ starters?: readonly string[];
16
+ /** What the URL sets, so a share link redraws the Horn it names. */
17
+ variables?: Readonly<Record<string, unknown>>;
18
+ /** Controls for the preview's own bar, such as the variables a share link carries. */
19
+ toolbar?: ReactNode;
20
+ };
21
+ /**
22
+ * The Studio as a whole screen, the way Lovable lays out a project: the conversation down the left, the Horn in
23
+ * a frame on the right, and saving in the bar across the top.
24
+ *
25
+ * The draft lives here and nowhere else. Each turn that changes it adds a version, the chat keeps a card for
26
+ * each, and the preview shows whichever one is picked — the latest unless someone went back to look. Saving is
27
+ * a separate, deliberate act, which is what makes the conversation free to be wrong.
28
+ */
29
+ export declare function HornStudio(props: HornStudioProps): import("react").JSX.Element;
30
+ export {};