@theoremjs/studio 0.3.4-next.458.gbf7b487 → 0.3.4-next.464.g5fc5eed

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 (28) hide show
  1. package/compile.js +1 -1
  2. package/package.json +4 -4
  3. package/page/assets/{Banner-C4MPbvft.js → Banner-fUKWqHmI.js} +2 -2
  4. package/page/assets/{ConsoleFrame-BkzieVRC.js → ConsoleFrame-B3cd6mje.js} +1 -1
  5. package/page/assets/{ContextMenu-BGP2gdee.js → ContextMenu-C1HQbsdS.js} +2 -2
  6. package/page/assets/{ToolResultCharts-xDwNDd-T.js → ToolResultCharts-CngV4ccU.js} +1 -1
  7. package/page/assets/{TraceInspector-CHpdhiVX.js → TraceInspector-BJS_1iXX.js} +1 -1
  8. package/page/assets/{TraceOverview-DCGew7UN.js → TraceOverview-6hlA2pmX.js} +1 -1
  9. package/page/assets/{browser-engine-Ca2pvYX0.js → browser-engine-BReGdZx4.js} +1 -1
  10. package/page/assets/browser-transport-Bsz-KNbB.js +47 -0
  11. package/page/assets/{export-agent-5rXmF-g1.js → export-agent-DDZ8cq19.js} +1 -1
  12. package/page/assets/{guardrail-tester-B-ZfQFVu.js → guardrail-tester-C_T6-YOZ.js} +1 -1
  13. package/page/assets/{index-Dajtrq_Z.js → index-Cw3Spve0.js} +6 -6
  14. package/page/assets/{studio-code-pane-CKYRitdD.js → studio-code-pane-xsBtdlWI.js} +1 -1
  15. package/page/assets/{studio-console-DwhqnNyw.js → studio-console-C3uStRGc.js} +1 -1
  16. package/page/assets/{studio-decision-Ckj4UNNt.js → studio-decision-MvC7eEPm.js} +1 -1
  17. package/page/assets/{studio-embedding-8qL_dVlz.js → studio-embedding-mvARkljv.js} +1 -1
  18. package/page/assets/{studio-host-console-BXwwloN4.js → studio-host-console-CzHR2V7s.js} +2 -2
  19. package/page/assets/{studio-labels-C1pvg0JC.js → studio-labels-DsQLZ9_e.js} +1 -1
  20. package/page/assets/{studio-live-26srJ74m.js → studio-live-DXV_PScy.js} +1 -1
  21. package/page/assets/{studio-map-C5zsJusF.js → studio-map-DBLOQt2Q.js} +1 -1
  22. package/page/assets/{studio-trace-replay-B38TicvO.js → studio-trace-replay-DHJTvGZO.js} +1 -1
  23. package/page/assets/{use-one-call-DjVkallj.js → use-one-call-B3x4YoMP.js} +1 -1
  24. package/page/index.html +5 -5
  25. package/ui/tree-map.css +0 -43
  26. package/ui/tree-map.d.ts +7 -5
  27. package/ui/tree-map.js +221 -42
  28. package/page/assets/browser-transport-DYgEItHi.js +0 -47
package/ui/tree-map.css CHANGED
@@ -13,46 +13,3 @@
13
13
  .studio-tree-map-lines .map-line {
14
14
  stroke: color-mix(in srgb, var(--color-border-emphasized) 60%, var(--color-background-surface));
15
15
  }
16
-
17
- /*
18
- * Feeds draw in as the loader's map draws (read-map.css), one after another: the line from the
19
- * tree, then the token at its end. Hidden, they leave in the reverse order.
20
- * invariant: Transitions, not keyframes, so drawing and leaving both ease, and a turn mid-draw
21
- * goes back from where it is.
22
- */
23
- .studio-tree-map {
24
- --feed-step: 70ms;
25
- }
26
-
27
- .studio-tree-map-lines [data-feed] {
28
- stroke-dasharray: 1;
29
- stroke-dashoffset: 0;
30
- transition: stroke-dashoffset var(--duration-medium, 240ms) var(--ease-standard, ease-out)
31
- calc(var(--feed-i) * var(--feed-step));
32
- }
33
-
34
- .studio-tree-map > [data-feed] {
35
- transition: opacity var(--duration-medium, 240ms) var(--ease-standard, ease-out)
36
- calc(var(--feed-i) * var(--feed-step) + var(--duration-medium, 240ms));
37
- }
38
-
39
- .studio-tree-map[data-feeds="hidden"] > [data-feed] {
40
- opacity: 0;
41
- transition-delay: calc((var(--feed-n) - var(--feed-i)) * var(--feed-step));
42
- }
43
-
44
- .studio-tree-map[data-feeds="hidden"] .studio-tree-map-lines [data-feed] {
45
- stroke-dashoffset: var(--feed-from);
46
- transition-delay: calc(
47
- (var(--feed-n) - var(--feed-i)) *
48
- var(--feed-step) +
49
- var(--duration-medium, 240ms)
50
- );
51
- }
52
-
53
- @media (prefers-reduced-motion: reduce) {
54
- .studio-tree-map-lines [data-feed],
55
- .studio-tree-map > [data-feed] {
56
- transition: none;
57
- }
58
- }
package/ui/tree-map.d.ts CHANGED
@@ -4,10 +4,12 @@ export interface StudioTreeMapProps {
4
4
  /** The keys of the agents to draw, in the workspace's order; every agent when unset. */
5
5
  agents?: readonly string[] | undefined;
6
6
  /**
7
- * Whether what feeds the tree is drawn beside it. Turned on, each feed draws in after the one
8
- * before, its line from the tree first, as the loader's map draws; turned off, they leave in the
9
- * reverse order. Shown when unset.
7
+ * How far the tree has grown, from 0 to 1. As it grows each section drifts to its own zone: the
8
+ * providers to a band along the top, each agent's tree apart from the next with its runner level
9
+ * with it, and the tool registry to a field of its own. What growing adds draws in on the way,
10
+ * each over its own stretch, its line and then its token: the steps of a turn under each turn
11
+ * runner, and every tool in the registry. Shrinking takes it all back the same way.
10
12
  */
11
- feeds?: 'shown' | 'hidden' | undefined;
13
+ grow?: number | undefined;
12
14
  }
13
- export declare function StudioTreeMap({ workspace, agents, feeds: shown }: StudioTreeMapProps): import("react").JSX.Element;
15
+ export declare function StudioTreeMap({ workspace, agents, grow }: StudioTreeMapProps): import("react").JSX.Element;
package/ui/tree-map.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { IconCloud, IconEngine, IconSettingsAutomation, IconTool } from '@tabler/icons-react';
2
+ import { IconAdjustments, IconArrowsMinimize, IconChecks, IconCloud, IconEngine, IconFlag, IconKey, IconRepeat, IconSettingsAutomation, IconShieldCheck, IconTimeline, IconTool, } from '@tabler/icons-react';
3
3
  import { useRef } from 'react';
4
4
  import { mapCurve } from "../map-line.js";
5
5
  import { createBlankDraft, libraryDraft, workspaceTree, } from "../mod.js";
@@ -34,7 +34,7 @@ function rowsOf(workspace, only) {
34
34
  agent,
35
35
  x: PAD + depth * INDENT,
36
36
  y: PAD + HALF + rows.length * ROW,
37
- ...(parent ? { parent: { x: parent.x, y: parent.y } } : {}),
37
+ ...(parent ? { parent: rowId(parent) } : {}),
38
38
  };
39
39
  rows.push(row);
40
40
  for (const child of node.children)
@@ -72,7 +72,26 @@ function runnerLabel(type) {
72
72
  return 'Embedding runner';
73
73
  return 'Turn runner';
74
74
  }
75
- function feedsOf(workspace, rows) {
75
+ /**
76
+ * What a turn runner does in one turn, in order.
77
+ * why: The kernel contract's turn lifecycle (docs/contracts/kernel.md, "Turn lifecycle"), step for
78
+ * step; a decision and a session have no such list there, so their runners open into nothing.
79
+ */
80
+ const TURN_STEPS = [
81
+ { id: 'resolve', label: 'Resolve', icon: IconAdjustments },
82
+ { id: 'sanitize', label: 'Sanitize', icon: IconShieldCheck },
83
+ { id: 'compaction', label: 'Compaction', icon: IconArrowsMinimize },
84
+ { id: 'canary', label: 'Canary bind', icon: IconKey },
85
+ { id: 'pre_turn', label: 'pre_turn', icon: IconFlag },
86
+ { id: 'stream', label: 'Provider stream', icon: IconCloud },
87
+ { id: 'tools', label: 'Tool loop', icon: IconRepeat },
88
+ { id: 'before_end', label: 'before_end', icon: IconFlag },
89
+ { id: 'validation', label: 'Validation', icon: IconChecks },
90
+ { id: 'egress', label: 'Egress', icon: IconShieldCheck },
91
+ { id: 'trace', label: 'Trace', icon: IconTimeline },
92
+ { id: 'post_turn', label: 'post_turn', icon: IconFlag },
93
+ ];
94
+ function feedsOf(workspace, rows, detail) {
76
95
  const feeds = [];
77
96
  const add = (id, label, icon, into, under, depth = 0) => {
78
97
  const below = (feeds.at(-1)?.y ?? Number.NEGATIVE_INFINITY) + ROW;
@@ -93,8 +112,13 @@ function feedsOf(workspace, rows) {
93
112
  if (!name)
94
113
  continue;
95
114
  const runner = runnerLabel(agent.identity.profileType);
96
- if (runner)
97
- add(`${agent.key}/runner`, runner, IconEngine, [name]);
115
+ if (runner) {
116
+ const id = `${agent.key}/runner`;
117
+ add(id, runner, IconEngine, [name]);
118
+ const steps = detail && runner === 'Turn runner' ? TURN_STEPS : [];
119
+ for (const step of steps)
120
+ add(`${id}/${step.id}`, step.label, step.icon, [], id, 1);
121
+ }
98
122
  const providers = new Map();
99
123
  for (const binding of agent.modelBindings) {
100
124
  const row = mine.find((each) => each.node.ref.facet === 'modelBinding' && each.node.ref.key === binding.key);
@@ -113,7 +137,7 @@ function feedsOf(workspace, rows) {
113
137
  else if (tools && workspace.toolSpecs.length > 0) {
114
138
  const registry = REGISTRY;
115
139
  add(registry, 'Tool registry', IconTool, [tools]);
116
- for (const tool of shownTools(workspace.toolSpecs)) {
140
+ for (const tool of listedTools(workspace.toolSpecs, detail)) {
117
141
  const icon = toolTypeIcon(tool.toolType);
118
142
  add(`${registry}/${tool.key}`, tool.toolName, icon, [], registry, 1);
119
143
  }
@@ -122,14 +146,18 @@ function feedsOf(workspace, rows) {
122
146
  }
123
147
  return feeds;
124
148
  }
125
- /** A few of the registry's tools: the first of each type, then the next ones, in the registry's order. */
126
- function shownTools(tools) {
149
+ /**
150
+ * The registry's tools: the first of each type, then the rest in the registry's order. A few of
151
+ * them, or with `detail` every one.
152
+ */
153
+ function listedTools(tools, detail) {
127
154
  const firsts = TOOL_TYPES.flatMap((type) => {
128
155
  const tool = tools.find((each) => each.toolType === type);
129
156
  return tool ? [tool] : [];
130
157
  });
131
158
  const rest = tools.filter((tool) => !firsts.includes(tool));
132
- return [...firsts, ...rest].slice(0, SHOWN_TOOLS);
159
+ const listed = [...firsts, ...rest];
160
+ return detail ? listed : listed.slice(0, SHOWN_TOOLS);
133
161
  }
134
162
  /** Right from `from` to `rail`, up or down it, and back left to `to`, its corners turned. */
135
163
  function sidePath(from, to, rail) {
@@ -142,54 +170,205 @@ function linePath(from, to) {
142
170
  const x = from.x + TRUNK;
143
171
  return `M ${x} ${from.y + HALF} V ${to.y - TURN} Q ${x} ${to.y} ${x + TURN} ${to.y} H ${to.x - LINE_GAP}`;
144
172
  }
145
- /** The order a feed draws in, and which end its line starts from: 1 the path's start, -1 its end. */
146
- const feedStyle = (index, from = 1) => ({ '--feed-i': index, '--feed-from': from });
147
- export function StudioTreeMap({ workspace, agents, feeds: shown = 'shown' }) {
173
+ /**
174
+ * From under the parent's icon to a child in any column of the rows under it: across just under
175
+ * the parent to the child's own trunk, a level in from the child, then down it and across. A child
176
+ * one level in has its trunk under the parent's icon, so it is the tree's line.
177
+ */
178
+ function underPath(from, to) {
179
+ const trunk = to.x - INDENT + TRUNK;
180
+ const across = from.y + HALF + LINE_GAP;
181
+ return (`M ${from.x + TRUNK} ${from.y + HALF} V ${across} H ${trunk} V ${to.y - TURN}` +
182
+ ` Q ${trunk} ${to.y} ${trunk + TURN} ${to.y} H ${to.x - LINE_GAP}`);
183
+ }
184
+ /** Grown, a row's height and the room between two agents' trees. */
185
+ const GROWN_ROW = 28;
186
+ const AGENT_GAP = 16;
187
+ /** Grown, the providers' band and the room under it. */
188
+ const BAND = GROWN_ROW + AGENT_GAP;
189
+ /** Grown, a turn's steps in rows under its runner, so many to a row. */
190
+ const STEPS_ACROSS = 3;
191
+ const STEP_SLOT = 160;
192
+ /** Grown, the registry's field: right of the turn's steps, its tools in columns this tall. */
193
+ const REGISTRY_IN = 186;
194
+ const TOOLS_DOWN = 16;
195
+ const TOOL_SLOT = 210;
196
+ /** Over what stretch of growing the added nodes draw, and how long each takes of it. */
197
+ const DRAW_FROM = 0.2;
198
+ const DRAW_TO = 0.95;
199
+ const DRAW_SPAN = 0.1;
200
+ const clamp = (value) => Math.min(Math.max(value, 0), 1);
201
+ const between = (from, to, at) => from + (to - from) * at;
202
+ /** Every other one from each list, while both last: the turn's steps and the tools draw by turns. */
203
+ function byTurns(first, second) {
204
+ return Array.from({ length: Math.max(first.length, second.length) }, (_, index) => [first[index], second[index]].filter((each) => each !== undefined)).flat();
205
+ }
206
+ /** The rows grown: restacked, each agent apart from the next, under the providers' band. */
207
+ function grownRows(rows) {
208
+ const places = new Map();
209
+ let agents = -1;
210
+ rows.forEach((row, index) => {
211
+ if (index === 0 || rows[index - 1]?.agent !== row.agent)
212
+ agents += 1;
213
+ places.set(rowId(row), {
214
+ x: row.x,
215
+ y: PAD + HALF + BAND + index * GROWN_ROW + agents * AGENT_GAP,
216
+ });
217
+ });
218
+ return places;
219
+ }
220
+ /** The next place under a holder, counting it. */
221
+ function nextUnder(growing, holder) {
222
+ const index = growing.under.get(holder) ?? 0;
223
+ growing.under.set(holder, index + 1);
224
+ return index;
225
+ }
226
+ /** A provider's place in the band, by name: an agent that has one too shares it. */
227
+ function bandPlace(growing, feed) {
228
+ const held = growing.band.get(feed.label);
229
+ if (held)
230
+ return held;
231
+ const place = { x: growing.bandX, y: PAD + HALF };
232
+ growing.band.set(feed.label, place);
233
+ growing.bandX += growing.width(feed.id) + AGENT_GAP;
234
+ return place;
235
+ }
236
+ /** Under the registry: its tools in columns, then the tool runner below them. */
237
+ function registryPlace(growing, feed, above) {
238
+ if (feed.depth === 0) {
239
+ const tools = Math.min(growing.under.get(REGISTRY) ?? 0, TOOLS_DOWN);
240
+ return { x: above.x, y: above.y + (tools + 1) * GROWN_ROW };
241
+ }
242
+ const index = nextUnder(growing, REGISTRY);
243
+ return {
244
+ x: above.x + INDENT + Math.floor(index / TOOLS_DOWN) * TOOL_SLOT,
245
+ y: above.y + ((index % TOOLS_DOWN) + 1) * GROWN_ROW,
246
+ };
247
+ }
248
+ /** A step under its runner, in rows so many across. */
249
+ function stepPlace(growing, holder, above) {
250
+ const index = nextUnder(growing, holder);
251
+ const y = above.y + (Math.floor(index / STEPS_ACROSS) + 1) * GROWN_ROW;
252
+ growing.stepsEnd = Math.max(growing.stepsEnd, y);
253
+ return { x: above.x + INDENT + (index % STEPS_ACROSS) * STEP_SLOT, y };
254
+ }
255
+ function feedPlace(growing, feed) {
256
+ if (feed.id.includes('/provider:'))
257
+ return bandPlace(growing, feed);
258
+ if (feed.id === REGISTRY) {
259
+ return { x: growing.feedX + REGISTRY_IN, y: growing.stepsEnd + GROWN_ROW * 1.5 };
260
+ }
261
+ const above = feed.under === undefined ? undefined : growing.places.get(feed.under);
262
+ if (feed.under === REGISTRY && above)
263
+ return registryPlace(growing, feed, above);
264
+ if (feed.under !== undefined && above)
265
+ return stepPlace(growing, feed.under, above);
266
+ const into = growing.places.get(feed.into[0] ?? '');
267
+ return { x: growing.feedX, y: into?.y ?? feed.y };
268
+ }
269
+ /** Each node's place grown: by its token id, rows and feeds alike. */
270
+ function grownPlaces(rows, feeds, feedX, width) {
271
+ const growing = {
272
+ places: grownRows(rows),
273
+ band: new Map(),
274
+ bandX: feedX,
275
+ under: new Map(),
276
+ stepsEnd: PAD + HALF + BAND,
277
+ feedX,
278
+ width,
279
+ };
280
+ for (const feed of feeds)
281
+ growing.places.set(feed.id, feedPlace(growing, feed));
282
+ return growing.places;
283
+ }
284
+ export function StudioTreeMap({ workspace, agents, grow = 0 }) {
148
285
  const root = useRef(null);
149
286
  const rows = rowsOf(workspace, agents);
150
- const feeds = feedsOf(workspace, rows);
287
+ const few = feedsOf(workspace, rows, false);
288
+ const full = feedsOf(workspace, rows, true);
151
289
  const widths = useTokenWidths(root, workspace);
290
+ const width = (id) => widths[id] ?? GUESS;
152
291
  const drafts = new Map(workspace.agents.map((agent) => [
153
292
  agent.key,
154
293
  libraryDraft(workspace, agent.key) ?? createBlankDraft(),
155
294
  ]));
156
- const right = (row) => row.x + (widths[rowId(row)] ?? GUESS);
157
- const feedX = Math.max(0, ...rows.map(right)) + GAP;
158
- const byId = new Map(rows.map((row) => [rowId(row), row]));
159
- const feedById = new Map(feeds.map((feed) => [feed.id, feed]));
160
- const height = PAD * 2 + Math.max(rows.length * ROW, ...feeds.map((feed) => feed.y + HALF - PAD));
161
- const feedLeft = (feed) => feedX + feed.depth * INDENT;
162
- const feedRight = (feed) => feedLeft(feed) + (widths[feed.id] ?? GUESS);
163
- /** Right of every feed from `above` down to `feed`. */
164
- const railFor = (feed, above) => Math.max(...feeds.filter((each) => each.y >= above.y && each.y <= feed.y).map(feedRight)) +
165
- RAIL;
166
- const rails = feeds.flatMap((feed) => {
295
+ const feedX = Math.max(0, ...rows.map((row) => row.x + width(rowId(row)))) + GAP;
296
+ // The tree as it stands: the rows, and the few feeds beside them.
297
+ const closed = new Map(rows.map((row) => [rowId(row), row]));
298
+ for (const feed of few)
299
+ closed.set(feed.id, { x: feedX + feed.depth * INDENT, y: feed.y });
300
+ const grown = grownPlaces(rows, full, feedX, width);
301
+ const feedById = new Map(full.map((feed) => [feed.id, feed]));
302
+ /**
303
+ * Where a node is, this far grown. One growing adds keeps its place in its zone and rides with
304
+ * the feed it comes under, so a zone drifts whole.
305
+ */
306
+ const at = (id) => {
307
+ const to = grown.get(id) ?? closed.get(id) ?? { x: 0, y: 0 };
308
+ const from = closed.get(id);
309
+ if (from)
310
+ return { x: between(from.x, to.x, grow), y: between(from.y, to.y, grow) };
311
+ const under = feedById.get(id)?.under;
312
+ const holder = under === undefined ? undefined : grown.get(under);
313
+ if (under === undefined || holder === undefined)
314
+ return to;
315
+ const now = at(under);
316
+ return { x: to.x + now.x - holder.x, y: to.y + now.y - holder.y };
317
+ };
318
+ // What growing adds draws in order, the turn's steps and the tools by turns.
319
+ const fewIds = new Set(few.map((feed) => feed.id));
320
+ const added = full.filter((feed) => !fewIds.has(feed.id));
321
+ const order = byTurns(added.filter((feed) => feed.under !== REGISTRY), added.filter((feed) => feed.under === REGISTRY));
322
+ /** How far an added node has drawn, 0 to 1: its line in the first half, its token in the second. */
323
+ const drawn = new Map(order.map((feed, index) => {
324
+ const start = DRAW_FROM + ((DRAW_TO - DRAW_SPAN - DRAW_FROM) * index) / Math.max(order.length - 1, 1);
325
+ return [feed.id, clamp((grow - start) / DRAW_SPAN)];
326
+ }));
327
+ const lineDrawn = (id) => {
328
+ const part = drawn.get(id);
329
+ return part === undefined
330
+ ? {}
331
+ : { pathLength: 1, style: { strokeDasharray: 1, strokeDashoffset: 1 - clamp(part * 2) } };
332
+ };
333
+ const shown = full.filter((feed) => (drawn.get(feed.id) ?? 1) > 0);
334
+ const right = (id) => at(id).x + width(id);
335
+ /** Right of every feed from `above` down to `feed`, where each stands now. */
336
+ const railFor = (feed, above) => {
337
+ const [top, bottom] = [at(above.id).y, at(feed.id).y].sort((a, b) => a - b);
338
+ const lying = shown.filter((each) => {
339
+ const { y } = at(each.id);
340
+ return y >= (top ?? 0) && y <= (bottom ?? 0) && each.id !== feed.id;
341
+ });
342
+ return Math.max(right(feed.id), ...lying.map((each) => right(each.id))) + RAIL;
343
+ };
344
+ const places = [...rows.map(rowId), ...shown.map((feed) => feed.id)];
345
+ const rails = shown.flatMap((feed) => {
167
346
  const above = feed.under === undefined ? undefined : feedById.get(feed.under);
168
347
  return above && feed.depth <= above.depth ? [railFor(feed, above)] : [];
169
348
  });
170
- const width = Math.max(GUESS, ...feeds.map(feedRight), ...rails) + PAD;
171
- const order = new Map(feeds.map((feed, index) => [feed.id, index]));
172
- return (_jsxs("div", { ref: root, className: "studio-tree-map", "data-feeds": shown, style: { width, height, '--feed-n': feeds.length }, children: [_jsxs("svg", { className: "studio-tree-map-lines", width: width, height: height, "aria-hidden": "true", children: [rows.map((row) => row.parent && (_jsx("path", { className: "map-line", d: linePath(row.parent, row) }, rowId(row)))), feeds.flatMap((feed) => feed.into.map((id) => {
173
- const row = byId.get(id);
174
- if (!row)
175
- return null;
176
- const { d } = mapCurve({ x: feedX - LINE_GAP, y: feed.y }, { x: right(row) + LINE_GAP, y: row.y });
177
- // The line runs from the feed to the row; it draws from the row.
178
- return (_jsx("path", { className: "map-line", d: d, pathLength: 1, "data-feed": "", style: feedStyle(order.get(feed.id) ?? 0, -1) }, `${feed.id}>${id}`));
179
- })), feeds.map((feed) => {
349
+ const mapWidth = Math.max(GUESS, ...places.map(right), ...rails) + PAD;
350
+ const height = Math.max(...places.map((id) => at(id).y)) + HALF + PAD;
351
+ return (_jsxs("div", { ref: root, className: "studio-tree-map", style: { width: mapWidth, height }, children: [_jsxs("svg", { className: "studio-tree-map-lines", width: mapWidth, height: height, "aria-hidden": "true", children: [rows.map((row) => row.parent && (_jsx("path", { className: "map-line", d: linePath(at(row.parent), at(rowId(row))) }, rowId(row)))), shown.flatMap((feed) => feed.into.map((id) => {
352
+ const from = at(feed.id);
353
+ const { d } = mapCurve({ x: from.x - LINE_GAP, y: from.y }, { x: right(id) + LINE_GAP, y: at(id).y });
354
+ return _jsx("path", { className: "map-line", d: d }, `${feed.id}>${id}`);
355
+ })), shown.map((feed) => {
180
356
  const above = feed.under === undefined ? undefined : feedById.get(feed.under);
181
357
  if (above === undefined)
182
358
  return null;
183
- const from = { x: feedLeft(above), y: above.y };
184
- // One of its own turns in from the trunk, as a tree row does; one level with it comes round
359
+ // One of its own comes down from it, as a tree row does; one level with it comes round
185
360
  // the right of what lies between, so it reads as joining the feed, not as one of its own.
186
361
  const d = feed.depth > above.depth
187
- ? linePath(from, { x: feedLeft(feed), y: feed.y })
188
- : sidePath({ x: feedRight(feed) + LINE_GAP, y: feed.y }, { x: feedRight(above) + LINE_GAP, y: above.y }, railFor(feed, above));
189
- // A turn in runs from the feed above; a line round the side runs from this one.
190
- return (_jsx("path", { className: "map-line", d: d, pathLength: 1, "data-feed": "", style: feedStyle(order.get(feed.id) ?? 0, feed.depth > above.depth ? 1 : -1) }, `${feed.id}^`));
362
+ ? underPath(at(above.id), at(feed.id))
363
+ : sidePath({ x: right(feed.id) + LINE_GAP, y: at(feed.id).y }, { x: right(above.id) + LINE_GAP, y: at(above.id).y }, railFor(feed, above));
364
+ return _jsx("path", { className: "map-line", d: d, ...lineDrawn(feed.id) }, `${feed.id}^`);
191
365
  })] }), rows.map((row) => {
192
366
  const draft = drafts.get(row.agent) ?? createBlankDraft();
193
- return (_jsx(MapToken, { id: rowId(row), label: row.node.label, icon: nodeIcon(draft, row.node.ref), x: row.x, y: row.y }, rowId(row)));
194
- }), feeds.map((feed) => (_jsx(MapToken, { id: feed.id, label: feed.label, icon: feed.icon, x: feedLeft(feed), y: feed.y, "data-feed": "", style: feedStyle(order.get(feed.id) ?? 0) }, feed.id)))] }));
367
+ const { x, y } = at(rowId(row));
368
+ return (_jsx(MapToken, { id: rowId(row), label: row.node.label, icon: nodeIcon(draft, row.node.ref), x: x, y: y }, rowId(row)));
369
+ }), shown.map((feed) => {
370
+ const { x, y } = at(feed.id);
371
+ const part = drawn.get(feed.id);
372
+ return (_jsx(MapToken, { id: feed.id, label: feed.label, icon: feed.icon, x: x, y: y, ...(part === undefined ? {} : { style: { opacity: clamp(part * 2 - 1) } }) }, feed.id));
373
+ })] }));
195
374
  }