@theoremjs/studio 0.3.4-next.464.g5fc5eed → 0.3.4-next.468.ge2da06f

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 (45) hide show
  1. package/README.md +1 -1
  2. package/package.json +4 -4
  3. package/page/assets/{Banner-fUKWqHmI.js → Banner-Z4LFsB9Y.js} +2 -2
  4. package/page/assets/{ConsoleFrame-B3cd6mje.js → ConsoleFrame-BJo1I5fw.js} +1 -1
  5. package/page/assets/{ContextMenu-C1HQbsdS.js → ContextMenu-BrT5KgtG.js} +2 -2
  6. package/page/assets/{ToolResultCharts-CngV4ccU.js → ToolResultCharts-CvdG2e3W.js} +1 -1
  7. package/page/assets/{TraceInspector-BJS_1iXX.js → TraceInspector-CwsPN13M.js} +1 -1
  8. package/page/assets/{TraceOverview-6hlA2pmX.js → TraceOverview-Bdz85-Rn.js} +1 -1
  9. package/page/assets/{_virtual_studio-type-sources-Bu1ywN-k.js → _virtual_studio-type-sources-DdSjKnMh.js} +5 -5
  10. package/page/assets/{browser-engine-BReGdZx4.js → browser-engine-Biheh6VY.js} +8 -8
  11. package/page/assets/{browser-transport-Bsz-KNbB.js → browser-transport-Cnyo4CDA.js} +4 -4
  12. package/page/assets/{crypto-Boxudvjl.js → crypto-C5mfRMPv.js} +1 -1
  13. package/page/assets/{encode-files-DZ4tVtEQ.js → encode-files-CZXrfYAp.js} +1 -1
  14. package/page/assets/{export-agent-DDZ8cq19.js → export-agent-CVpG2q1m.js} +1 -1
  15. package/page/assets/{guardrail-tester-C_T6-YOZ.js → guardrail-tester-CJuC6zTI.js} +1 -1
  16. package/page/assets/{index-Cw3Spve0.js → index-CYmjuHBy.js} +9 -9
  17. package/page/assets/{live-messages-dPuowG_o.js → live-messages-CcUq850L.js} +1 -1
  18. package/page/assets/{reader-BB7aL5yX.js → reader-DkhgU7-Z.js} +4 -4
  19. package/page/assets/shaped-data-Hq2l-bCM.js +16 -0
  20. package/page/assets/{stub-DyzmGexy.js → stub-9noS56tk.js} +1 -1
  21. package/page/assets/{studio-code-pane-xsBtdlWI.js → studio-code-pane-CkjZy0ML.js} +3 -3
  22. package/page/assets/{studio-console-C3uStRGc.js → studio-console-D7Gp4J9U.js} +1 -1
  23. package/page/assets/{studio-decision-MvC7eEPm.js → studio-decision-BRgQ4PBH.js} +1 -1
  24. package/page/assets/{studio-embedding-mvARkljv.js → studio-embedding-Dl4-HUCw.js} +1 -1
  25. package/page/assets/{studio-host-console-CzHR2V7s.js → studio-host-console-C2kDYgZ0.js} +2 -2
  26. package/page/assets/{studio-labels-DsQLZ9_e.js → studio-labels-BOSQZulx.js} +1 -1
  27. package/page/assets/{studio-live-DXV_PScy.js → studio-live-BweMJXiN.js} +1 -1
  28. package/page/assets/{studio-map-DBLOQt2Q.js → studio-map-ClUV7cPI.js} +1 -1
  29. package/page/assets/{studio-trace-replay-DHJTvGZO.js → studio-trace-replay-BVJ_xUpV.js} +1 -1
  30. package/page/assets/{studio-type-checker-BaSH5B4-.js → studio-type-checker-Ch0YO2TO.js} +1 -1
  31. package/page/assets/{tool-schema-E9NPuDcN.js → tool-schema-DUAd6AXZ.js} +6 -6
  32. package/page/assets/{trace-record-BjsBbPRl.js → trace-record-Dfx7WS9n.js} +1 -1
  33. package/page/assets/{tree-2VeaKg0m.js → tree-q4zfZBm3.js} +2 -2
  34. package/page/assets/{turn-client-Dy9-U9_L.js → turn-client-BHueOGtF.js} +2 -2
  35. package/page/assets/types-CbxtWiCN.js +1 -0
  36. package/page/assets/{use-one-call-B3x4YoMP.js → use-one-call-Djq1Oucn.js} +1 -1
  37. package/page/assets/{workspace-Cno6Ge26.js → workspace-D8wB8WWL.js} +1 -1
  38. package/page/index.html +15 -15
  39. package/ui/lib/tree-map-layout.d.ts +75 -0
  40. package/ui/lib/tree-map-layout.js +289 -0
  41. package/ui/tree-map.css +2 -0
  42. package/ui/tree-map.d.ts +10 -6
  43. package/ui/tree-map.js +55 -286
  44. package/page/assets/shaped-data-0GUF7Al3.js +0 -16
  45. package/page/assets/types-CMdljBjd.js +0 -1
package/ui/tree-map.js CHANGED
@@ -1,163 +1,49 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { IconAdjustments, IconArrowsMinimize, IconChecks, IconCloud, IconEngine, IconFlag, IconKey, IconRepeat, IconSettingsAutomation, IconShieldCheck, IconTimeline, IconTool, } from '@tabler/icons-react';
3
- import { useRef } from 'react';
3
+ import { useMemo, useRef } from 'react';
4
4
  import { mapCurve } from "../map-line.js";
5
- import { createBlankDraft, libraryDraft, workspaceTree, } from "../mod.js";
5
+ import { createBlankDraft, libraryDraft } from "../mod.js";
6
+ import { drawnAt, HALF, INDENT, PAD, placeAt, rowId, treeMapLayout, } from "./lib/tree-map-layout.js";
6
7
  import { toolTypeIcon } from "./lib/type-icons.js";
7
8
  import { MapToken, useTokenWidths } from "./map-token.js";
8
9
  import { nodeIcon } from "./node-icon.js";
9
10
  import './tree-map.css';
10
- /** A row's height, and how far in each level of the tree starts. */
11
- const ROW = 32;
12
- const INDENT = 28;
13
11
  /** Where under a token its children's line runs: the middle of its icon. */
14
12
  const TRUNK = 13;
15
- /** A token's half height, and the radius of the line's turn. */
16
- const HALF = 11;
13
+ /** The radius of the line's turn. */
17
14
  const TURN = 8;
18
- const PAD = 16;
19
- /** Between the tree's widest row and the feeds, and between a token's side and its line. */
20
- const GAP = 72;
15
+ /** Between a token's side and its line. */
21
16
  const LINE_GAP = 4;
22
17
  /** How far right of the widest feed a line coming round them runs. */
23
18
  const RAIL = 20;
24
- /** How many of the registry's tools sit under it: one of each type first. */
25
- const SHOWN_TOOLS = 4;
26
- const TOOL_TYPES = ['function', 'http', 'mcp', 'agent'];
27
19
  /** A token's width until it is measured. */
28
20
  const GUESS = 120;
29
- function rowsOf(workspace, only) {
30
- const rows = [];
31
- const walk = (node, agent, depth, parent) => {
32
- const row = {
33
- node,
34
- agent,
35
- x: PAD + depth * INDENT,
36
- y: PAD + HALF + rows.length * ROW,
37
- ...(parent ? { parent: rowId(parent) } : {}),
38
- };
39
- rows.push(row);
40
- for (const child of node.children)
41
- walk(child, agent, depth + 1, row);
42
- };
43
- workspaceTree(workspace).agents.forEach((root, index) => {
44
- const key = workspace.agents[index]?.key ?? '';
45
- if (only === undefined || only.includes(key))
46
- walk(root, key, 0);
47
- });
48
- return rows;
49
- }
50
- /** A row's token id: the tree's ids repeat from one agent to the next. */
51
- function rowId(row) {
52
- return `${row.agent}/${row.node.id}`;
53
- }
54
- /**
55
- * Each agent's runner (`runTurn`, or `runDecision` for a decision), beside its name; its providers, beside its models; and the
56
- * workspace's tool registry, beside its Tools row, with the tool runner under it. A feed sits level
57
- * with the middle of the rows it joins, or under the feed before it when that is lower.
58
- */
59
- const REGISTRY = 'tool-registry';
60
- /**
61
- * What runs a profile of this type: `runDecision`, `runSession`, `runEmbedding`, else `runTurn`. A
62
- * host has none of its own: the tool runner runs its calls.
63
- */
64
- function runnerLabel(type) {
65
- if (type === 'host')
66
- return undefined;
67
- if (type === 'decision')
68
- return 'Decision runner';
69
- if (type === 'live')
70
- return 'Session runner';
71
- if (type === 'embedding')
72
- return 'Embedding runner';
73
- return 'Turn runner';
74
- }
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) {
95
- const feeds = [];
96
- const add = (id, label, icon, into, under, depth = 0) => {
97
- const below = (feeds.at(-1)?.y ?? Number.NEGATIVE_INFINITY) + ROW;
98
- const middle = into.length ? into.reduce((sum, row) => sum + row.y, 0) / into.length : below;
99
- feeds.push({
100
- id,
101
- label,
102
- icon,
103
- y: Math.max(middle, below),
104
- into: into.map(rowId),
105
- ...(under ? { under } : {}),
106
- depth,
107
- });
108
- };
109
- for (const agent of workspace.agents) {
110
- const mine = rows.filter((row) => row.agent === agent.key);
111
- const name = mine.find((row) => row.node.ref.facet === 'identity');
112
- if (!name)
113
- continue;
114
- const runner = runnerLabel(agent.identity.profileType);
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
- }
122
- const providers = new Map();
123
- for (const binding of agent.modelBindings) {
124
- const row = mine.find((each) => each.node.ref.facet === 'modelBinding' && each.node.ref.key === binding.key);
125
- if (row)
126
- providers.set(binding.provider, [...(providers.get(binding.provider) ?? []), row]);
127
- }
128
- for (const [provider, into] of providers) {
129
- add(`${agent.key}/provider:${provider}`, provider, IconCloud, into);
130
- }
131
- // The workspace's tools are one registry that each agent's tool policy picks from: drawn once,
132
- // beside the first Tools row, with a line into every other.
133
- const tools = mine.find((row) => row.node.ref.facet === 'tools');
134
- const drawn = feeds.find((feed) => feed.id === REGISTRY);
135
- if (tools && drawn)
136
- drawn.into.push(rowId(tools));
137
- else if (tools && workspace.toolSpecs.length > 0) {
138
- const registry = REGISTRY;
139
- add(registry, 'Tool registry', IconTool, [tools]);
140
- for (const tool of listedTools(workspace.toolSpecs, detail)) {
141
- const icon = toolTypeIcon(tool.toolType);
142
- add(`${registry}/${tool.key}`, tool.toolName, icon, [], registry, 1);
143
- }
144
- add('tool-runner', 'Tool runner', IconSettingsAutomation, [], registry);
145
- }
146
- }
147
- return feeds;
148
- }
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) {
154
- const firsts = TOOL_TYPES.flatMap((type) => {
155
- const tool = tools.find((each) => each.toolType === type);
156
- return tool ? [tool] : [];
157
- });
158
- const rest = tools.filter((tool) => !firsts.includes(tool));
159
- const listed = [...firsts, ...rest];
160
- return detail ? listed : listed.slice(0, SHOWN_TOOLS);
21
+ const STEP_ICONS = {
22
+ resolve: IconAdjustments,
23
+ sanitize: IconShieldCheck,
24
+ compaction: IconArrowsMinimize,
25
+ canary: IconKey,
26
+ pre_turn: IconFlag,
27
+ stream: IconCloud,
28
+ tools: IconRepeat,
29
+ before_end: IconFlag,
30
+ validation: IconChecks,
31
+ egress: IconShieldCheck,
32
+ trace: IconTimeline,
33
+ post_turn: IconFlag,
34
+ };
35
+ function feedIcon(feed) {
36
+ if (feed.kind === 'runner')
37
+ return IconEngine;
38
+ if (feed.kind === 'provider')
39
+ return IconCloud;
40
+ if (feed.kind === 'tool')
41
+ return toolTypeIcon(feed.of ?? '');
42
+ if (feed.kind === 'tool-runner')
43
+ return IconSettingsAutomation;
44
+ if (feed.kind === 'step')
45
+ return STEP_ICONS[feed.of ?? ''] ?? IconFlag;
46
+ return IconTool;
161
47
  }
162
48
  /** Right from `from` to `rail`, up or down it, and back left to `to`, its corners turned. */
163
49
  function sidePath(from, to, rail) {
@@ -181,156 +67,28 @@ function underPath(from, to) {
181
67
  return (`M ${from.x + TRUNK} ${from.y + HALF} V ${across} H ${trunk} V ${to.y - TURN}` +
182
68
  ` Q ${trunk} ${to.y} ${trunk + TURN} ${to.y} H ${to.x - LINE_GAP}`);
183
69
  }
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
70
  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 }) {
71
+ export function StudioTreeMap({ workspace, agents, grow = 0, fit }) {
285
72
  const root = useRef(null);
286
- const rows = rowsOf(workspace, agents);
287
- const few = feedsOf(workspace, rows, false);
288
- const full = feedsOf(workspace, rows, true);
73
+ // why: Every token is drawn from the start, the undrawn ones unseen, so each is measured before it shows.
289
74
  const widths = useTokenWidths(root, workspace);
75
+ const layout = useMemo(() => treeMapLayout(workspace, agents, (id) => widths[id] ?? GUESS), [workspace, agents, widths]);
290
76
  const width = (id) => widths[id] ?? GUESS;
291
77
  const drafts = new Map(workspace.agents.map((agent) => [
292
78
  agent.key,
293
79
  libraryDraft(workspace, agent.key) ?? createBlankDraft(),
294
80
  ]));
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
- }));
81
+ const { rows, feeds } = layout;
82
+ const at = (id) => placeAt(layout, id, grow);
83
+ const drawn = (id) => drawnAt(layout, id, grow);
84
+ const feedById = new Map(feeds.map((feed) => [feed.id, feed]));
327
85
  const lineDrawn = (id) => {
328
- const part = drawn.get(id);
86
+ const part = drawn(id);
329
87
  return part === undefined
330
88
  ? {}
331
89
  : { pathLength: 1, style: { strokeDasharray: 1, strokeDashoffset: 1 - clamp(part * 2) } };
332
90
  };
333
- const shown = full.filter((feed) => (drawn.get(feed.id) ?? 1) > 0);
91
+ const shown = feeds.filter((feed) => (drawn(feed.id) ?? 1) > 0);
334
92
  const right = (id) => at(id).x + width(id);
335
93
  /** Right of every feed from `above` down to `feed`, where each stands now. */
336
94
  const railFor = (feed, above) => {
@@ -348,7 +106,17 @@ export function StudioTreeMap({ workspace, agents, grow = 0 }) {
348
106
  });
349
107
  const mapWidth = Math.max(GUESS, ...places.map(right), ...rails) + PAD;
350
108
  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) => {
109
+ const grown = useMemo(() => {
110
+ const ids = [...layout.rows.map(rowId), ...layout.feeds.map((feed) => feed.id)];
111
+ const ends = ids.map((id) => ({ id, ...placeAt(layout, id, 1) }));
112
+ return {
113
+ width: Math.max(...ends.map((end) => end.x + (widths[end.id] ?? GUESS))) + RAIL + PAD,
114
+ height: Math.max(...ends.map((end) => end.y)) + HALF + PAD,
115
+ };
116
+ }, [layout, widths]);
117
+ const fits = fit ? Math.min(1, fit.width / grown.width, fit.height / grown.height) : 1;
118
+ const scale = 1 + (fits - 1) * clamp(grow);
119
+ return (_jsxs("div", { ref: root, className: "studio-tree-map", style: { width: mapWidth, height, ...(scale < 1 ? { scale: String(scale) } : {}) }, 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
120
  const from = at(feed.id);
353
121
  const { d } = mapCurve({ x: from.x - LINE_GAP, y: from.y }, { x: right(id) + LINE_GAP, y: at(id).y });
354
122
  return _jsx("path", { className: "map-line", d: d }, `${feed.id}>${id}`);
@@ -366,9 +134,10 @@ export function StudioTreeMap({ workspace, agents, grow = 0 }) {
366
134
  const draft = drafts.get(row.agent) ?? createBlankDraft();
367
135
  const { x, y } = at(rowId(row));
368
136
  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) => {
137
+ }), feeds.map((feed) => {
370
138
  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));
139
+ const part = drawn(feed.id);
140
+ const unseen = part === 0 ? { visibility: 'hidden' } : {};
141
+ return (_jsx(MapToken, { id: feed.id, label: feed.label, icon: feedIcon(feed), x: x, y: y, ...(part === undefined ? {} : { style: { ...unseen, opacity: clamp(part * 2 - 1) } }) }, feed.id));
373
142
  })] }));
374
143
  }