@signal9/era-ui 4.6.0 → 4.7.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 (54) hide show
  1. package/dist/ai/error-panel/error-panel.svelte +1 -30
  2. package/dist/ai/index.d.ts +1 -0
  3. package/dist/ai/index.js +1 -0
  4. package/dist/ai/runtime-feed/runtime-feed-item.svelte +4 -1
  5. package/dist/ai/swarm/context.d.ts +26 -0
  6. package/dist/ai/swarm/context.js +14 -0
  7. package/dist/ai/swarm/index.d.ts +16 -0
  8. package/dist/ai/swarm/index.js +15 -0
  9. package/dist/ai/swarm/swarm-agent.svelte +132 -0
  10. package/dist/ai/swarm/swarm-agent.svelte.d.ts +16 -0
  11. package/dist/ai/swarm/swarm-artifact.svelte +45 -0
  12. package/dist/ai/swarm/swarm-artifact.svelte.d.ts +8 -0
  13. package/dist/ai/swarm/swarm-artifacts.svelte +42 -0
  14. package/dist/ai/swarm/swarm-artifacts.svelte.d.ts +15 -0
  15. package/dist/ai/swarm/swarm-barrier.svelte +63 -0
  16. package/dist/ai/swarm/swarm-barrier.svelte.d.ts +10 -0
  17. package/dist/ai/swarm/swarm-counts.svelte +52 -0
  18. package/dist/ai/swarm/swarm-counts.svelte.d.ts +13 -0
  19. package/dist/ai/swarm/swarm-dot.svelte +52 -0
  20. package/dist/ai/swarm/swarm-dot.svelte.d.ts +23 -0
  21. package/dist/ai/swarm/swarm-footer.svelte +63 -0
  22. package/dist/ai/swarm/swarm-footer.svelte.d.ts +16 -0
  23. package/dist/ai/swarm/swarm-grid.svelte +51 -0
  24. package/dist/ai/swarm/swarm-grid.svelte.d.ts +14 -0
  25. package/dist/ai/swarm/swarm-header.svelte +64 -0
  26. package/dist/ai/swarm/swarm-header.svelte.d.ts +14 -0
  27. package/dist/ai/swarm/swarm-node.svelte +258 -0
  28. package/dist/ai/swarm/swarm-node.svelte.d.ts +32 -0
  29. package/dist/ai/swarm/swarm-role.svelte +71 -0
  30. package/dist/ai/swarm/swarm-role.svelte.d.ts +17 -0
  31. package/dist/ai/swarm/swarm-root.svelte +55 -0
  32. package/dist/ai/swarm/swarm-root.svelte.d.ts +16 -0
  33. package/dist/ai/swarm/swarm-tree.svelte +201 -0
  34. package/dist/ai/swarm/swarm-tree.svelte.d.ts +25 -0
  35. package/dist/ai/swarm/swarm.d.ts +218 -0
  36. package/dist/ai/swarm/swarm.js +187 -0
  37. package/dist/ai/swarm/swarm.svelte.d.ts +59 -0
  38. package/dist/ai/swarm/swarm.svelte.js +125 -0
  39. package/dist/apps/llm-shell/index.d.ts +1 -1
  40. package/dist/apps/llm-shell/llm-shell.svelte +65 -1
  41. package/dist/apps/llm-shell/llm-shell.svelte.d.ts +7 -0
  42. package/dist/apps/llm-shell/types.d.ts +22 -0
  43. package/dist/dev/audit/audits/clipped-glyphs.js +7 -0
  44. package/dist/era-ui.css +1 -1
  45. package/dist/generated-docs/llm-shell.md +1 -1
  46. package/dist/generated-docs/llms-full.txt +53 -11
  47. package/dist/generated-docs/llms.txt +1 -0
  48. package/dist/generated-docs/manifest.json +16 -5
  49. package/dist/generated-docs/utilities.json +12 -2
  50. package/dist/generated-docs/utilities.md +51 -10
  51. package/dist/styles/index.css +63 -9
  52. package/dist/styles/themes.css +27 -15
  53. package/dist/ui/step/step-content.svelte +5 -34
  54. package/package.json +1 -1
@@ -92,39 +92,10 @@
92
92
  {/if}
93
93
 
94
94
  {#if showDetail}
95
- <Collapsible.Content data-era-error-detail class="overflow-hidden">
95
+ <Collapsible.Content class="era-collapse">
96
96
  <div class="pt-(--era-gap)">
97
97
  <CodeBlock code={rawDetail} />
98
98
  </div>
99
99
  </Collapsible.Content>
100
100
  {/if}
101
101
  </Collapsible.Root>
102
-
103
- <style>
104
- :global([data-era-error-detail][data-state='open']) {
105
- animation: era-error-expand var(--era-duration) var(--era-ease);
106
- }
107
- :global([data-era-error-detail][data-state='closed']) {
108
- animation: era-error-collapse var(--era-duration) var(--era-ease);
109
- }
110
- @keyframes -global-era-error-expand {
111
- from {
112
- height: 0;
113
- opacity: 0;
114
- }
115
- to {
116
- height: var(--bits-collapsible-content-height);
117
- opacity: 1;
118
- }
119
- }
120
- @keyframes -global-era-error-collapse {
121
- from {
122
- height: var(--bits-collapsible-content-height);
123
- opacity: 1;
124
- }
125
- to {
126
- height: 0;
127
- opacity: 0;
128
- }
129
- }
130
- </style>
@@ -17,6 +17,7 @@ export * as StructuredValue from './structured-value/index.js';
17
17
  export * as RuntimeFeed from './runtime-feed/index.js';
18
18
  export * as ErrorPanel from './error-panel/index.js';
19
19
  export * as RunStatus from './run-status/index.js';
20
+ export * as Swarm from './swarm/index.js';
20
21
  export { Response, repairStreamingMarkdown } from './response/index.js';
21
22
  export { default as ContextGauge } from './context/context.svelte';
22
23
  export { default as ModelSelector } from './model-selector/model-selector.svelte';
package/dist/ai/index.js CHANGED
@@ -22,6 +22,7 @@ export * as StructuredValue from './structured-value/index.js';
22
22
  export * as RuntimeFeed from './runtime-feed/index.js';
23
23
  export * as ErrorPanel from './error-panel/index.js';
24
24
  export * as RunStatus from './run-status/index.js';
25
+ export * as Swarm from './swarm/index.js';
25
26
  export { Response, repairStreamingMarkdown } from './response/index.js';
26
27
  export { default as ContextGauge } from './context/context.svelte';
27
28
  export { default as ModelSelector } from './model-selector/model-selector.svelte';
@@ -54,7 +54,10 @@
54
54
  class={cn('flex items-center gap-(--era-gap) text-body', TONE[tone], className)}
55
55
  {...restProps}
56
56
  >
57
- <span class="size-(--era-h-xxs) shrink-0 rounded-full bg-current" aria-hidden="true"></span>
57
+ <!-- size-2, the library's dot size (conversations row, run status, swarm) —
58
+ NOT a tier height: h-xxs is the pill tier (18-26px), which rendered this
59
+ as a bullet taller than the line it marks. -->
60
+ <span class="size-2 shrink-0 rounded-full bg-current" aria-hidden="true"></span>
58
61
  <span class="min-w-0 flex-1 truncate">
59
62
  {#if children}{@render children()}{:else}{text}{/if}
60
63
  </span>
@@ -0,0 +1,26 @@
1
+ import type { SwarmAgent } from './swarm.js';
2
+ /**
3
+ * What `Swarm.Root` shares with every part below it.
4
+ *
5
+ * The two callbacks are here rather than on the parts because a swarm is a
6
+ * TREE: a node six levels down would otherwise have to be handed the same
7
+ * `onOpen` through six intermediaries, and the recursion is generated by the
8
+ * Tree, not written by the consumer, so there is nowhere to thread it. Any part
9
+ * may still take its own handler and win over the context.
10
+ */
11
+ export interface SwarmContext {
12
+ /**
13
+ * Epoch ms every elapsed readout measures against.
14
+ *
15
+ * The library never reads the clock itself — same input, same render (which
16
+ * is what makes it testable and SSR-safe). A host that ticks this in `$state`
17
+ * gets live counters on running agents for free.
18
+ */
19
+ readonly now: number;
20
+ /** Open an agent's own transcript. Omitted = no open affordance. */
21
+ readonly onOpen?: (agent: SwarmAgent) => void;
22
+ /** Cancel an agent (the host decides whether that means its subtree too). */
23
+ readonly onCancel?: (agent: SwarmAgent) => void;
24
+ }
25
+ export declare function setSwarmContext(ctx: SwarmContext): void;
26
+ export declare function getSwarmContext(): SwarmContext;
@@ -0,0 +1,14 @@
1
+ import { getContext, setContext } from 'svelte';
2
+ const KEY = Symbol('era-swarm');
3
+ /** Standalone parts (a `Swarm.Agent` card outside a Root) still need a clock. */
4
+ const FALLBACK = {
5
+ get now() {
6
+ return Date.now();
7
+ }
8
+ };
9
+ export function setSwarmContext(ctx) {
10
+ setContext(KEY, ctx);
11
+ }
12
+ export function getSwarmContext() {
13
+ return getContext(KEY) ?? FALLBACK;
14
+ }
@@ -0,0 +1,16 @@
1
+ export { default as Root } from './swarm-root.svelte';
2
+ export { default as Header } from './swarm-header.svelte';
3
+ export { default as Counts } from './swarm-counts.svelte';
4
+ export { default as Tree } from './swarm-tree.svelte';
5
+ export { default as Node } from './swarm-node.svelte';
6
+ export { default as Grid } from './swarm-grid.svelte';
7
+ export { default as Agent } from './swarm-agent.svelte';
8
+ export { default as Artifacts } from './swarm-artifacts.svelte';
9
+ export { default as Artifact } from './swarm-artifact.svelte';
10
+ export { default as Barrier } from './swarm-barrier.svelte';
11
+ export { default as Footer } from './swarm-footer.svelte';
12
+ export { default as Dot, SWARM_DOT } from './swarm-dot.svelte';
13
+ export { default as Role, SWARM_ROLE } from './swarm-role.svelte';
14
+ export { SwarmController } from './swarm.svelte.js';
15
+ export { buildSwarmTree, flattenSwarmTree, summarizeSwarm, summarizeBarrier, swarmElapsed, formatSwarmAge, formatSwarmDuration, type SwarmAgent, type SwarmAgentStatus, type SwarmArtifact, type SwarmBarrier, type SwarmBarrierStatus, type SwarmConnection, type SwarmCounts, type SwarmEvent, type SwarmNode, type SwarmPlan, type SwarmPlanStatus, type SwarmRole, type SwarmSandbox, type SwarmSnapshot } from './swarm.js';
16
+ export type { SwarmContext } from './context.js';
@@ -0,0 +1,15 @@
1
+ export { default as Root } from './swarm-root.svelte';
2
+ export { default as Header } from './swarm-header.svelte';
3
+ export { default as Counts } from './swarm-counts.svelte';
4
+ export { default as Tree } from './swarm-tree.svelte';
5
+ export { default as Node } from './swarm-node.svelte';
6
+ export { default as Grid } from './swarm-grid.svelte';
7
+ export { default as Agent } from './swarm-agent.svelte';
8
+ export { default as Artifacts } from './swarm-artifacts.svelte';
9
+ export { default as Artifact } from './swarm-artifact.svelte';
10
+ export { default as Barrier } from './swarm-barrier.svelte';
11
+ export { default as Footer } from './swarm-footer.svelte';
12
+ export { default as Dot, SWARM_DOT } from './swarm-dot.svelte';
13
+ export { default as Role, SWARM_ROLE } from './swarm-role.svelte';
14
+ export { SwarmController } from './swarm.svelte.js';
15
+ export { buildSwarmTree, flattenSwarmTree, summarizeSwarm, summarizeBarrier, swarmElapsed, formatSwarmAge, formatSwarmDuration } from './swarm.js';
@@ -0,0 +1,132 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+ import type { HTMLAttributes } from 'svelte/elements';
4
+ import Box from '@lucide/svelte/icons/box';
5
+ import X from '@lucide/svelte/icons/x';
6
+ import PanelRightOpen from '@lucide/svelte/icons/panel-right-open';
7
+ import { Button } from '../../ui/button/index.js';
8
+ import { Card } from '../../ui/card/index.js';
9
+ import { cn } from '../../utils/index.js';
10
+ import { getSwarmContext } from './context.js';
11
+ import Dot from './swarm-dot.svelte';
12
+ import Role from './swarm-role.svelte';
13
+ import { formatSwarmDuration, swarmElapsed, type SwarmAgent } from './swarm.js';
14
+
15
+ let {
16
+ agent,
17
+ depth,
18
+ children,
19
+ onOpen,
20
+ onCancel,
21
+ class: className,
22
+ ...restProps
23
+ }: Omit<HTMLAttributes<HTMLElement>, 'children'> & {
24
+ agent: SwarmAgent;
25
+ /** Distance from the root, shown as a `d2` tag. `buildSwarmTree` computes it. */
26
+ depth?: number;
27
+ /** Extra card body under the result — a chip row, a mini feed. */
28
+ children?: Snippet<[SwarmAgent]>;
29
+ /** Override the Root's handlers for this card. */
30
+ onOpen?: (agent: SwarmAgent) => void;
31
+ onCancel?: (agent: SwarmAgent) => void;
32
+ } = $props();
33
+
34
+ const ctx = getSwarmContext();
35
+ const openAgent = $derived(onOpen ?? ctx.onOpen);
36
+ const cancelAgent = $derived(onCancel ?? ctx.onCancel);
37
+ const cancellable = $derived(
38
+ cancelAgent != null &&
39
+ (agent.status === 'running' || agent.status === 'queued' || agent.status === 'blocked')
40
+ );
41
+ // See `Swarm.Node`: the open affordance is gated on the agent actually having
42
+ // a transcript to open.
43
+ const openable = $derived(openAgent != null && agent.conversationId != null);
44
+ const elapsed = $derived(swarmElapsed(agent, ctx.now));
45
+ </script>
46
+
47
+ <!-- One agent as a card, for the console grid — the same agent the tree renders
48
+ as a row, sized for scanning forty of them at once.
49
+
50
+ A well, not an elevated card: the grid sits INSIDE the swarm panel, and on
51
+ the flat surface `elevated` resolves to the same fill the panel already has
52
+ — forty invisible boxes. The recessed tier is the one that reads as content
53
+ put into a container on every surface.
54
+
55
+ The status dot and the sandbox chip stay SEPARATE signals: an agent can be
56
+ running with a terminated VM (reaped, or never created), and collapsing the
57
+ two into one indicator hides precisely the state you opened a console to
58
+ find. A failure rings the whole card, because in a grid of forty the one
59
+ that broke has to be findable without reading any of them. -->
60
+ <Card
61
+ tone="well"
62
+ data-status={agent.status}
63
+ class={cn(
64
+ 'flex min-w-0 flex-col gap-(--era-gap) p-(--era-pad-sm)',
65
+ agent.status === 'failed' && 'outline -outline-offset-1 outline-destructive/60',
66
+ className
67
+ )}
68
+ {...restProps}
69
+ >
70
+ <div class="flex min-w-0 items-center gap-(--era-gap) text-body era-text-trim">
71
+ <Dot status={agent.status} />
72
+ <Role role={agent.role ?? 'worker'} label={agent.label} class="min-w-0" />
73
+ {#if agent.key}
74
+ <span class="shrink-0 font-mono text-body text-muted">#{agent.key}</span>
75
+ {/if}
76
+ <span class="flex-1"></span>
77
+ {#if depth != null}
78
+ <span class="shrink-0 font-mono text-[0.625rem] text-muted tabular-nums">d{depth}</span>
79
+ {/if}
80
+ {#if cancellable}
81
+ <Button
82
+ icon
83
+ size="xxs"
84
+ tone="destructive"
85
+ aria-label="Cancel {agent.task}"
86
+ onclick={() => cancelAgent?.(agent)}
87
+ >
88
+ <X class="size-(--era-h-xs)" />
89
+ </Button>
90
+ {/if}
91
+ {#if openable}
92
+ <Button icon size="xxs" aria-label="Open {agent.task}" onclick={() => openAgent?.(agent)}>
93
+ <PanelRightOpen class="size-(--era-h-xs)" />
94
+ </Button>
95
+ {/if}
96
+ </div>
97
+
98
+ <!-- Two lines of task, one of result: enough to tell two workers apart,
99
+ bounded so one verbose subtask can't make its card twice the height of
100
+ its neighbours and shred the grid. -->
101
+ <span class="line-clamp-2 text-body text-fg" title={agent.task}>{agent.task}</span>
102
+ {#if agent.result}
103
+ <span class="line-clamp-1 text-body text-muted" title={agent.result}>{agent.result}</span>
104
+ {/if}
105
+
106
+ {@render children?.(agent)}
107
+
108
+ {#if agent.sandbox || elapsed != null}
109
+ <div class="flex min-w-0 items-center gap-(--era-gap) text-body era-text-trim text-muted">
110
+ {#if agent.sandbox}
111
+ <span
112
+ class="flex min-w-0 items-center gap-(--era-gap) font-mono"
113
+ title="sandbox {agent.sandbox.id ?? ''}"
114
+ >
115
+ <Box
116
+ class={cn(
117
+ 'size-(--era-h-xs) shrink-0',
118
+ agent.sandbox.status === 'running' && 'text-success',
119
+ agent.sandbox.status === 'paused' && 'text-warning'
120
+ )}
121
+ aria-hidden="true"
122
+ />
123
+ <span class="truncate">{agent.sandbox.status}</span>
124
+ </span>
125
+ {/if}
126
+ <span class="flex-1"></span>
127
+ {#if elapsed != null}
128
+ <span class="shrink-0 font-mono tabular-nums">{formatSwarmDuration(elapsed)}</span>
129
+ {/if}
130
+ </div>
131
+ {/if}
132
+ </Card>
@@ -0,0 +1,16 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { HTMLAttributes } from 'svelte/elements';
3
+ import { type SwarmAgent } from './swarm.js';
4
+ type $$ComponentProps = Omit<HTMLAttributes<HTMLElement>, 'children'> & {
5
+ agent: SwarmAgent;
6
+ /** Distance from the root, shown as a `d2` tag. `buildSwarmTree` computes it. */
7
+ depth?: number;
8
+ /** Extra card body under the result — a chip row, a mini feed. */
9
+ children?: Snippet<[SwarmAgent]>;
10
+ /** Override the Root's handlers for this card. */
11
+ onOpen?: (agent: SwarmAgent) => void;
12
+ onCancel?: (agent: SwarmAgent) => void;
13
+ };
14
+ declare const SwarmAgent: import("svelte").Component<$$ComponentProps, {}, "">;
15
+ type SwarmAgent = ReturnType<typeof SwarmAgent>;
16
+ export default SwarmAgent;
@@ -0,0 +1,45 @@
1
+ <script lang="ts">
2
+ import type { HTMLAttributes } from 'svelte/elements';
3
+ import { Card } from '../../ui/card/index.js';
4
+ import { cn } from '../../utils/index.js';
5
+ import { getSwarmContext } from './context.js';
6
+ import { formatSwarmAge, type SwarmArtifact } from './swarm.js';
7
+
8
+ let {
9
+ artifact,
10
+ class: className,
11
+ ...restProps
12
+ }: HTMLAttributes<HTMLElement> & {
13
+ artifact: SwarmArtifact;
14
+ } = $props();
15
+
16
+ const ctx = getSwarmContext();
17
+ const age = $derived(
18
+ artifact.createdAt != null ? formatSwarmAge(artifact.createdAt, ctx.now) : null
19
+ );
20
+ </script>
21
+
22
+ <!-- What a branch actually produced: the merged finding, the report, the diff.
23
+ A well (recessed) rather than a card: an artifact is payload the swarm put
24
+ INTO the panel, the same tier a tool's output pane uses. -->
25
+ <Card
26
+ tone="well"
27
+ data-kind={artifact.kind}
28
+ class={cn('flex min-w-0 flex-col gap-(--era-gap) p-(--era-pad-sm)', className)}
29
+ {...restProps}
30
+ >
31
+ <div class="flex min-w-0 items-center gap-(--era-gap) text-body era-text-trim">
32
+ <span class="shrink-0 font-mono text-[0.625rem] tracking-[0.12em] text-muted uppercase">
33
+ {artifact.kind}
34
+ </span>
35
+ {#if artifact.title}
36
+ <span class="min-w-0 flex-1 truncate text-bright">{artifact.title}</span>
37
+ {:else}
38
+ <span class="flex-1"></span>
39
+ {/if}
40
+ {#if age}
41
+ <span class="shrink-0 font-mono text-body text-muted tabular-nums">{age}</span>
42
+ {/if}
43
+ </div>
44
+ <p class="text-body leading-relaxed text-muted">{artifact.summary}</p>
45
+ </Card>
@@ -0,0 +1,8 @@
1
+ import type { HTMLAttributes } from 'svelte/elements';
2
+ import { type SwarmArtifact } from './swarm.js';
3
+ type $$ComponentProps = HTMLAttributes<HTMLElement> & {
4
+ artifact: SwarmArtifact;
5
+ };
6
+ declare const SwarmArtifact: import("svelte").Component<$$ComponentProps, {}, "">;
7
+ type SwarmArtifact = ReturnType<typeof SwarmArtifact>;
8
+ export default SwarmArtifact;
@@ -0,0 +1,42 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+ import type { HTMLAttributes } from 'svelte/elements';
4
+ import * as Step from '../../ui/step/index.js';
5
+ import { cn } from '../../utils/index.js';
6
+ import Artifact from './swarm-artifact.svelte';
7
+ import type { SwarmArtifact } from './swarm.js';
8
+
9
+ let {
10
+ artifacts = [],
11
+ label = 'artifacts',
12
+ limit,
13
+ children,
14
+ class: className,
15
+ ...restProps
16
+ }: Omit<HTMLAttributes<HTMLDivElement>, 'children'> & {
17
+ artifacts?: SwarmArtifact[];
18
+ /** Section heading. Set to `undefined` for a bare list. */
19
+ label?: string;
20
+ /** Show at most this many, newest first by position. Omit for all of them. */
21
+ limit?: number;
22
+ /** Replaces the default `Swarm.Artifact` rendering for each item. */
23
+ children?: Snippet<[SwarmArtifact]>;
24
+ } = $props();
25
+
26
+ const shown = $derived(limit == null ? artifacts : artifacts.slice(0, limit));
27
+ const hidden = $derived(artifacts.length - shown.length);
28
+ </script>
29
+
30
+ <!-- Renders nothing at all when there is nothing to show: an empty "ARTIFACTS"
31
+ heading over blank space is worse than silence, and this sits inside rows
32
+ that are mostly empty until a branch lands. -->
33
+ {#if artifacts.length > 0}
34
+ <Step.Section {label} class={cn('min-w-0', className)} {...restProps}>
35
+ {#each shown as artifact (artifact.id)}
36
+ {#if children}{@render children(artifact)}{:else}<Artifact {artifact} />{/if}
37
+ {/each}
38
+ {#if hidden > 0}
39
+ <span class="text-body text-muted">+{hidden} more</span>
40
+ {/if}
41
+ </Step.Section>
42
+ {/if}
@@ -0,0 +1,15 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { HTMLAttributes } from 'svelte/elements';
3
+ import type { SwarmArtifact } from './swarm.js';
4
+ type $$ComponentProps = Omit<HTMLAttributes<HTMLDivElement>, 'children'> & {
5
+ artifacts?: SwarmArtifact[];
6
+ /** Section heading. Set to `undefined` for a bare list. */
7
+ label?: string;
8
+ /** Show at most this many, newest first by position. Omit for all of them. */
9
+ limit?: number;
10
+ /** Replaces the default `Swarm.Artifact` rendering for each item. */
11
+ children?: Snippet<[SwarmArtifact]>;
12
+ };
13
+ declare const SwarmArtifacts: import("svelte").Component<$$ComponentProps, {}, "">;
14
+ type SwarmArtifacts = ReturnType<typeof SwarmArtifacts>;
15
+ export default SwarmArtifacts;
@@ -0,0 +1,63 @@
1
+ <script lang="ts" module>
2
+ import type { SwarmBarrierStatus } from './swarm.js';
3
+
4
+ const BARRIER_TONE: Record<SwarmBarrierStatus, 'default' | 'success' | 'destructive'> = {
5
+ open: 'default',
6
+ done: 'success',
7
+ failed: 'destructive',
8
+ cancelled: 'default'
9
+ };
10
+ </script>
11
+
12
+ <script lang="ts">
13
+ import type { HTMLAttributes } from 'svelte/elements';
14
+ import GitMerge from '@lucide/svelte/icons/git-merge';
15
+ import { Badge } from '../../ui/badge/index.js';
16
+ import { Progress } from '../../ui/progress/index.js';
17
+ import { cn } from '../../utils/index.js';
18
+ import { summarizeBarrier, type SwarmAgent, type SwarmBarrier } from './swarm.js';
19
+
20
+ let {
21
+ barrier,
22
+ agents = [],
23
+ class: className,
24
+ ...restProps
25
+ }: HTMLAttributes<HTMLDivElement> & {
26
+ barrier: SwarmBarrier;
27
+ /** The swarm's agents — members are the ones pointing at this barrier. */
28
+ agents?: SwarmAgent[];
29
+ } = $props();
30
+
31
+ const state = $derived(summarizeBarrier(barrier, agents));
32
+ </script>
33
+
34
+ <!-- A fan-in gate: the point where parallel branches have to rejoin before the
35
+ run can move on. It is rendered as PROGRESS rather than as a status word
36
+ because the only question anyone asks of a barrier is "how much longer",
37
+ and 3/5 answers it where "open" does not. -->
38
+ <div
39
+ data-status={state.status}
40
+ class={cn('flex min-w-0 flex-col gap-(--era-gap)', className)}
41
+ {...restProps}
42
+ >
43
+ <div class="flex min-w-0 items-center gap-(--era-gap) text-body era-text-trim">
44
+ <GitMerge class="size-(--era-h-xs) shrink-0 text-muted" aria-hidden="true" />
45
+ <span class="min-w-0 flex-1 truncate text-fg">{barrier.title}</span>
46
+ {#if barrier.key}
47
+ <span class="shrink-0 font-mono text-body text-muted">#{barrier.key}</span>
48
+ {/if}
49
+ {#if state.failed > 0}
50
+ <Badge tone="destructive">{state.failed} failed</Badge>
51
+ {/if}
52
+ <Badge tone={BARRIER_TONE[state.status]}>
53
+ {state.done}/{state.total}
54
+ </Badge>
55
+ </div>
56
+ {#if state.total > 0}
57
+ <Progress
58
+ value={state.done}
59
+ max={state.total}
60
+ aria-label="{barrier.title} — {state.done} of {state.total} agents done"
61
+ />
62
+ {/if}
63
+ </div>
@@ -0,0 +1,10 @@
1
+ import type { HTMLAttributes } from 'svelte/elements';
2
+ import { type SwarmAgent, type SwarmBarrier } from './swarm.js';
3
+ type $$ComponentProps = HTMLAttributes<HTMLDivElement> & {
4
+ barrier: SwarmBarrier;
5
+ /** The swarm's agents — members are the ones pointing at this barrier. */
6
+ agents?: SwarmAgent[];
7
+ };
8
+ declare const SwarmBarrier: import("svelte").Component<$$ComponentProps, {}, "">;
9
+ type SwarmBarrier = ReturnType<typeof SwarmBarrier>;
10
+ export default SwarmBarrier;
@@ -0,0 +1,52 @@
1
+ <script lang="ts">
2
+ import type { HTMLAttributes } from 'svelte/elements';
3
+ import Box from '@lucide/svelte/icons/box';
4
+ import { Badge } from '../../ui/badge/index.js';
5
+ import { cn } from '../../utils/index.js';
6
+ import { summarizeSwarm, type SwarmAgent, type SwarmCounts } from './swarm.js';
7
+
8
+ let {
9
+ agents = [],
10
+ counts,
11
+ sandboxes = true,
12
+ class: className,
13
+ ...restProps
14
+ }: HTMLAttributes<HTMLDivElement> & {
15
+ /** The swarm's agents — counted here so the caller never tallies by hand. */
16
+ agents?: SwarmAgent[];
17
+ /** Pre-computed counts (a controller's `counts`), used instead of `agents`. */
18
+ counts?: SwarmCounts;
19
+ /** Show the live-VM tally. Off for swarms that do not run sandboxes. */
20
+ sandboxes?: boolean;
21
+ } = $props();
22
+
23
+ const tally = $derived(counts ?? summarizeSwarm(agents));
24
+ // blocked and queued are two different waits (see SwarmAgentStatus) but they
25
+ // are one number to someone reading the header: work that has not started.
26
+ const waiting = $derived(tally.blocked + tally.queued);
27
+ // Nothing to report is itself worth reporting — a just-created swarm with no
28
+ // agents should read "0 agents", not render an empty strip.
29
+ const quiet = $derived(
30
+ tally.running === 0 && waiting === 0 && tally.done === 0 && tally.failed === 0
31
+ );
32
+ </script>
33
+
34
+ <!-- Live tally. Each badge appears only while its number is non-zero, so the
35
+ strip shrinks as a run lands instead of trailing a row of zeroes — and the
36
+ one that matters (failures) is never hidden behind the ones that don't. -->
37
+ <div class={cn('flex shrink-0 items-center gap-(--era-gap)', className)} {...restProps}>
38
+ {#if quiet}
39
+ <Badge>{tally.total} agents</Badge>
40
+ {:else}
41
+ {#if tally.running > 0}<Badge tone="accent">{tally.running} running</Badge>{/if}
42
+ {#if waiting > 0}<Badge>{waiting} waiting</Badge>{/if}
43
+ {#if tally.done > 0}<Badge tone="success">{tally.done} done</Badge>{/if}
44
+ {#if tally.failed > 0}<Badge tone="destructive">{tally.failed} failed</Badge>{/if}
45
+ {/if}
46
+ {#if sandboxes && tally.sandboxes > 0}
47
+ <Badge>
48
+ <Box class="size-(--era-h-xs)" aria-hidden="true" />
49
+ {tally.sandboxes}
50
+ </Badge>
51
+ {/if}
52
+ </div>
@@ -0,0 +1,13 @@
1
+ import type { HTMLAttributes } from 'svelte/elements';
2
+ import { type SwarmAgent, type SwarmCounts } from './swarm.js';
3
+ type $$ComponentProps = HTMLAttributes<HTMLDivElement> & {
4
+ /** The swarm's agents — counted here so the caller never tallies by hand. */
5
+ agents?: SwarmAgent[];
6
+ /** Pre-computed counts (a controller's `counts`), used instead of `agents`. */
7
+ counts?: SwarmCounts;
8
+ /** Show the live-VM tally. Off for swarms that do not run sandboxes. */
9
+ sandboxes?: boolean;
10
+ };
11
+ declare const SwarmCounts: import("svelte").Component<$$ComponentProps, {}, "">;
12
+ type SwarmCounts = ReturnType<typeof SwarmCounts>;
13
+ export default SwarmCounts;
@@ -0,0 +1,52 @@
1
+ <script lang="ts" module>
2
+ import type { SwarmAgentStatus } from './swarm.js';
3
+
4
+ /**
5
+ * Status → tone. Colour is reserved for LIFECYCLE across the whole family
6
+ * (a role is a category, and categories get glyphs, not hues), so this map is
7
+ * the single place any part says what a state looks like.
8
+ *
9
+ * `running` pulses, which is the one animation here — it rides the pulse
10
+ * keyframes, so `prefers-reduced-motion` and data-motion="instant" settle it
11
+ * to a solid dot rather than leaving a strobe in a 40-agent grid.
12
+ */
13
+ export const SWARM_DOT: Record<SwarmAgentStatus, { class: string; label: string }> = {
14
+ blocked: { class: 'bg-warning', label: 'Blocked' },
15
+ queued: { class: 'bg-muted', label: 'Queued' },
16
+ running: { class: 'bg-primary animate-pulse', label: 'Running' },
17
+ done: { class: 'bg-success', label: 'Done' },
18
+ failed: { class: 'bg-destructive', label: 'Failed' },
19
+ cancelled: { class: 'bg-muted opacity-50', label: 'Cancelled' }
20
+ };
21
+ </script>
22
+
23
+ <script lang="ts">
24
+ import type { HTMLAttributes } from 'svelte/elements';
25
+ import { cn } from '../../utils/index.js';
26
+
27
+ let {
28
+ status,
29
+ label,
30
+ class: className,
31
+ ...restProps
32
+ }: HTMLAttributes<HTMLSpanElement> & {
33
+ status: SwarmAgentStatus;
34
+ /** Overrides the announced/hover word (defaults to the status). */
35
+ label?: string;
36
+ } = $props();
37
+
38
+ const tone = $derived(SWARM_DOT[status]);
39
+ </script>
40
+
41
+ <!-- role="img" rather than aria-hidden: in a swarm the dot is not decoration
42
+ next to a spelled-out state, it IS the state, and a card that only paints
43
+ it says nothing to a screen reader. The name also flows into the enclosing
44
+ treeitem's name-from-contents, so a tree row announces its status too. -->
45
+ <span
46
+ role="img"
47
+ aria-label={label ?? tone.label}
48
+ title={label ?? tone.label}
49
+ data-status={status}
50
+ class={cn('size-2 shrink-0 rounded-full', tone.class, className)}
51
+ {...restProps}
52
+ ></span>
@@ -0,0 +1,23 @@
1
+ import type { SwarmAgentStatus } from './swarm.js';
2
+ /**
3
+ * Status → tone. Colour is reserved for LIFECYCLE across the whole family
4
+ * (a role is a category, and categories get glyphs, not hues), so this map is
5
+ * the single place any part says what a state looks like.
6
+ *
7
+ * `running` pulses, which is the one animation here — it rides the pulse
8
+ * keyframes, so `prefers-reduced-motion` and data-motion="instant" settle it
9
+ * to a solid dot rather than leaving a strobe in a 40-agent grid.
10
+ */
11
+ export declare const SWARM_DOT: Record<SwarmAgentStatus, {
12
+ class: string;
13
+ label: string;
14
+ }>;
15
+ import type { HTMLAttributes } from 'svelte/elements';
16
+ type $$ComponentProps = HTMLAttributes<HTMLSpanElement> & {
17
+ status: SwarmAgentStatus;
18
+ /** Overrides the announced/hover word (defaults to the status). */
19
+ label?: string;
20
+ };
21
+ declare const SwarmDot: import("svelte").Component<$$ComponentProps, {}, "">;
22
+ type SwarmDot = ReturnType<typeof SwarmDot>;
23
+ export default SwarmDot;