@signal9/era-ui 4.11.0 → 4.11.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ai/swarm/swarm-node.svelte +85 -33
- package/dist/ai/swarm/swarm-node.svelte.d.ts +17 -9
- package/dist/ai/swarm/swarm-tree.svelte +32 -16
- package/dist/ai/swarm/swarm-tree.svelte.d.ts +4 -4
- package/dist/apps/llm-shell/llm-swarm.svelte +10 -2
- package/dist/apps/notes/notes.svelte +22 -6
- package/dist/era-ui.css +1 -1
- package/dist/styles/density.css +8 -0
- package/dist/styles/themes.css +18 -3
- package/dist/ui/bar/bar.svelte +4 -4
- package/dist/ui/chip/chip.svelte +10 -1
- package/dist/ui/code-block/code-block.svelte +23 -7
- package/dist/ui/context-menu/context-menu-content.svelte +9 -1
- package/dist/ui/context-menu/context-menu-item.svelte +1 -1
- package/dist/ui/dropdown-menu/dropdown-menu-content.svelte +9 -1
- package/dist/ui/dropdown-menu/dropdown-menu-item.svelte +1 -1
- package/dist/ui/menubar/menubar-content.svelte +9 -1
- package/dist/ui/menubar/menubar-item.svelte +1 -1
- package/dist/ui/pane/pane-handle.svelte +15 -1
- package/dist/ui/select/select-content.svelte +9 -1
- package/dist/ui/select/select-item.svelte +1 -1
- package/dist/ui/video-player/video-player.svelte +13 -13
- package/package.json +1 -1
|
@@ -14,30 +14,38 @@
|
|
|
14
14
|
agent,
|
|
15
15
|
level = 1,
|
|
16
16
|
open = $bindable(true),
|
|
17
|
+
detailOpen = $bindable(false),
|
|
18
|
+
detailLabel,
|
|
17
19
|
active = false,
|
|
18
|
-
expandable,
|
|
19
20
|
detail,
|
|
20
21
|
meta,
|
|
21
22
|
children,
|
|
22
23
|
onOpen,
|
|
23
24
|
onCancel,
|
|
24
25
|
onToggle,
|
|
26
|
+
onDetailToggle,
|
|
25
27
|
class: className,
|
|
26
28
|
...restProps
|
|
27
29
|
}: Omit<Collapsible.RootProps, 'children' | 'child' | 'open'> & {
|
|
28
30
|
agent: SwarmAgent;
|
|
29
31
|
/** Depth in the tree, 1-based — announced as aria-level. `Swarm.Tree` sets it. */
|
|
30
32
|
level?: number;
|
|
31
|
-
/**
|
|
33
|
+
/**
|
|
34
|
+
* Subtree expanded — the chevron, and nothing else. A tree's disclosure is
|
|
35
|
+
* its CHILDREN; this component used to make it mean the agent's output as
|
|
36
|
+
* well, which is why a finished swarm sat fully unfolded.
|
|
37
|
+
*/
|
|
32
38
|
open?: boolean;
|
|
33
|
-
/** The
|
|
34
|
-
|
|
39
|
+
/** The agent's own output, disclosed separately. Bindable. */
|
|
40
|
+
detailOpen?: boolean;
|
|
35
41
|
/**
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
42
|
+
* Labels the detail affordance, and its presence is what creates one —
|
|
43
|
+
* "2 artifacts", "transcript". Say what is behind it, since a chip that
|
|
44
|
+
* names its contents needs no icon to explain itself.
|
|
39
45
|
*/
|
|
40
|
-
|
|
46
|
+
detailLabel?: string;
|
|
47
|
+
/** The tree's focused row — holds the seated highlight. */
|
|
48
|
+
active?: boolean;
|
|
41
49
|
/** Expanded body for this agent: its artifacts, transcript, output. */
|
|
42
50
|
detail?: Snippet<[SwarmAgent]>;
|
|
43
51
|
/** Trailing row content — a count, a chip, anything the host tracks. */
|
|
@@ -47,22 +55,41 @@
|
|
|
47
55
|
/** Overrides the Root's handler for this row. */
|
|
48
56
|
onOpen?: (agent: SwarmAgent) => void;
|
|
49
57
|
onCancel?: (agent: SwarmAgent) => void;
|
|
50
|
-
/** Fired after the
|
|
58
|
+
/** Fired after the SUBTREE expands or collapses. */
|
|
51
59
|
onToggle?: (open: boolean) => void;
|
|
60
|
+
/** Fired after the detail expands or collapses. */
|
|
61
|
+
onDetailToggle?: (open: boolean) => void;
|
|
52
62
|
} = $props();
|
|
53
63
|
|
|
54
64
|
const ctx = getSwarmContext();
|
|
55
65
|
const actions = $derived(swarmActions(agent, ctx, { onOpen, onCancel }));
|
|
56
66
|
const hasActions = $derived(actions.open != null || actions.cancel != null);
|
|
57
67
|
|
|
58
|
-
|
|
68
|
+
// Two disclosures, deliberately separate. The chevron owns the subtree (what a
|
|
69
|
+
// tree's expansion means everywhere else), and the row's detail chip owns this
|
|
70
|
+
// agent's output. Fusing them is what made every finished agent hold its
|
|
71
|
+
// artifacts open forever — the only way to hide the noise was to hide the tree.
|
|
72
|
+
const hasChildren = $derived(children != null);
|
|
73
|
+
const hasDetail = $derived(detail != null && detailLabel != null);
|
|
59
74
|
const running = $derived(agent.status === 'running');
|
|
60
75
|
const elapsed = $derived(swarmElapsed(agent, ctx.now));
|
|
61
76
|
|
|
77
|
+
// A branch's row toggles its branch; a LEAF has no branch, so its row toggles
|
|
78
|
+
// the one thing it does have. Either way the row does the obvious thing, and
|
|
79
|
+
// the chip is always there for the detail specifically.
|
|
62
80
|
function toggle() {
|
|
63
|
-
if (
|
|
64
|
-
|
|
65
|
-
|
|
81
|
+
if (hasChildren) {
|
|
82
|
+
open = !open;
|
|
83
|
+
onToggle?.(open);
|
|
84
|
+
} else if (hasDetail) {
|
|
85
|
+
toggleDetail();
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function toggleDetail() {
|
|
90
|
+
if (!hasDetail) return;
|
|
91
|
+
detailOpen = !detailOpen;
|
|
92
|
+
onDetailToggle?.(detailOpen);
|
|
66
93
|
}
|
|
67
94
|
|
|
68
95
|
/**
|
|
@@ -102,6 +129,9 @@
|
|
|
102
129
|
</script>
|
|
103
130
|
|
|
104
131
|
<!-- One agent in the delegation tree.
|
|
132
|
+
aria-expanded describes the SUBTREE, per the tree pattern — a leaf that
|
|
133
|
+
advertised it would tell a screen reader it has children it doesn't have.
|
|
134
|
+
The detail's own expanded state lives on the chip that controls it.
|
|
105
135
|
The treeitem is the Collapsible root, so `data-state` (and therefore the
|
|
106
136
|
chevron's rotation and the body's height sweep) comes from the same element
|
|
107
137
|
that carries aria-expanded — one source of truth for "is this row open",
|
|
@@ -110,7 +140,7 @@
|
|
|
110
140
|
bind:open
|
|
111
141
|
role="treeitem"
|
|
112
142
|
aria-level={level}
|
|
113
|
-
aria-expanded={
|
|
143
|
+
aria-expanded={hasChildren ? open : undefined}
|
|
114
144
|
data-agent-id={agent.id}
|
|
115
145
|
data-active={active || undefined}
|
|
116
146
|
tabindex={active ? 0 : -1}
|
|
@@ -126,14 +156,14 @@
|
|
|
126
156
|
class={cn(
|
|
127
157
|
'flex h-(--era-h-md) items-center gap-(--era-gap) rounded-(--era-rd-md) pl-(--era-inset-md) text-body era-text-trim',
|
|
128
158
|
hasActions ? 'pr-(--era-xxs-inset-md)' : 'pr-(--era-inset-md)',
|
|
129
|
-
|
|
159
|
+
(hasChildren || hasDetail) && 'cursor-pointer',
|
|
130
160
|
active
|
|
131
161
|
? 'bg-hover text-bright'
|
|
132
162
|
: 'text-fg hover:bg-(--era-highlight) hover:shadow-(--era-highlight-shadow)',
|
|
133
163
|
'group-focus-visible/node:outline group-focus-visible/node:-outline-offset-1 group-focus-visible/node:outline-primary'
|
|
134
164
|
)}
|
|
135
165
|
>
|
|
136
|
-
{#if
|
|
166
|
+
{#if hasChildren}
|
|
137
167
|
<ChevronRight
|
|
138
168
|
class="size-(--era-h-xs) shrink-0 text-muted transition-transform duration-(--era-duration) ease-(--era-ease) group-data-[state=open]/node:rotate-90"
|
|
139
169
|
aria-hidden="true"
|
|
@@ -163,6 +193,24 @@
|
|
|
163
193
|
|
|
164
194
|
{@render meta?.(agent)}
|
|
165
195
|
|
|
196
|
+
{#if hasDetail}
|
|
197
|
+
<!-- The detail affordance appears only where there is something behind it,
|
|
198
|
+
and says what: a chip that names its own contents needs no icon to
|
|
199
|
+
explain itself, and a row with nothing to show stays a plain line. -->
|
|
200
|
+
<button
|
|
201
|
+
type="button"
|
|
202
|
+
data-state={detailOpen ? 'open' : 'closed'}
|
|
203
|
+
aria-expanded={detailOpen}
|
|
204
|
+
onclick={(event: MouseEvent) => {
|
|
205
|
+
event.stopPropagation();
|
|
206
|
+
toggleDetail();
|
|
207
|
+
}}
|
|
208
|
+
class="shrink-0 era-interactive cursor-pointer rounded-(--era-rd-xxs) px-(--era-pill-xxs) font-mono text-body era-text-trim text-muted hover:bg-(--era-highlight) hover:text-fg data-[state=open]:bg-hover data-[state=open]:text-fg"
|
|
209
|
+
>
|
|
210
|
+
{detailLabel}
|
|
211
|
+
</button>
|
|
212
|
+
{/if}
|
|
213
|
+
|
|
166
214
|
{#if agent.sandbox}
|
|
167
215
|
<Sandbox sandbox={agent.sandbox} />
|
|
168
216
|
{/if}
|
|
@@ -176,27 +224,31 @@
|
|
|
176
224
|
<Actions {agent} open={actions.open} cancel={actions.cancel} />
|
|
177
225
|
</div>
|
|
178
226
|
|
|
179
|
-
{#if
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
227
|
+
{#if hasDetail}
|
|
228
|
+
<!-- The agent's own output, on its own disclosure. Indented to where the
|
|
229
|
+
row's text starts (chevron + gap), so it reads as this agent's output
|
|
230
|
+
rather than a sibling row. -->
|
|
231
|
+
<Collapsible.Root bind:open={detailOpen}>
|
|
232
|
+
<Collapsible.Content class="era-collapse">
|
|
184
233
|
<div
|
|
185
234
|
class="min-w-0 pr-(--era-inset-md) pb-(--era-gap) pl-[calc(var(--era-inset-md)+var(--era-h-xs)+var(--era-gap))]"
|
|
186
235
|
>
|
|
187
|
-
{@render detail(agent)}
|
|
236
|
+
{@render detail?.(agent)}
|
|
188
237
|
</div>
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
238
|
+
</Collapsible.Content>
|
|
239
|
+
</Collapsible.Root>
|
|
240
|
+
{/if}
|
|
241
|
+
|
|
242
|
+
{#if hasChildren}
|
|
243
|
+
<Collapsible.Content class="era-collapse">
|
|
244
|
+
<!-- The rail lands on the chevron's centre line, so the connector runs
|
|
245
|
+
straight down from the parent's own arrow into its children. -->
|
|
246
|
+
<div
|
|
247
|
+
role="group"
|
|
248
|
+
class="ml-[calc(var(--era-inset-md)+var(--era-h-xs)/2)] border-l border-divider-faded"
|
|
249
|
+
>
|
|
250
|
+
{@render children?.()}
|
|
251
|
+
</div>
|
|
200
252
|
</Collapsible.Content>
|
|
201
253
|
{/if}
|
|
202
254
|
</Collapsible.Root>
|
|
@@ -5,16 +5,22 @@ type $$ComponentProps = Omit<Collapsible.RootProps, 'children' | 'child' | 'open
|
|
|
5
5
|
agent: SwarmAgent;
|
|
6
6
|
/** Depth in the tree, 1-based — announced as aria-level. `Swarm.Tree` sets it. */
|
|
7
7
|
level?: number;
|
|
8
|
-
/**
|
|
8
|
+
/**
|
|
9
|
+
* Subtree expanded — the chevron, and nothing else. A tree's disclosure is
|
|
10
|
+
* its CHILDREN; this component used to make it mean the agent's output as
|
|
11
|
+
* well, which is why a finished swarm sat fully unfolded.
|
|
12
|
+
*/
|
|
9
13
|
open?: boolean;
|
|
10
|
-
/** The
|
|
11
|
-
|
|
14
|
+
/** The agent's own output, disclosed separately. Bindable. */
|
|
15
|
+
detailOpen?: boolean;
|
|
12
16
|
/**
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
17
|
+
* Labels the detail affordance, and its presence is what creates one —
|
|
18
|
+
* "2 artifacts", "transcript". Say what is behind it, since a chip that
|
|
19
|
+
* names its contents needs no icon to explain itself.
|
|
16
20
|
*/
|
|
17
|
-
|
|
21
|
+
detailLabel?: string;
|
|
22
|
+
/** The tree's focused row — holds the seated highlight. */
|
|
23
|
+
active?: boolean;
|
|
18
24
|
/** Expanded body for this agent: its artifacts, transcript, output. */
|
|
19
25
|
detail?: Snippet<[SwarmAgent]>;
|
|
20
26
|
/** Trailing row content — a count, a chip, anything the host tracks. */
|
|
@@ -24,9 +30,11 @@ type $$ComponentProps = Omit<Collapsible.RootProps, 'children' | 'child' | 'open
|
|
|
24
30
|
/** Overrides the Root's handler for this row. */
|
|
25
31
|
onOpen?: (agent: SwarmAgent) => void;
|
|
26
32
|
onCancel?: (agent: SwarmAgent) => void;
|
|
27
|
-
/** Fired after the
|
|
33
|
+
/** Fired after the SUBTREE expands or collapses. */
|
|
28
34
|
onToggle?: (open: boolean) => void;
|
|
35
|
+
/** Fired after the detail expands or collapses. */
|
|
36
|
+
onDetailToggle?: (open: boolean) => void;
|
|
29
37
|
};
|
|
30
|
-
declare const SwarmNode: import("svelte").Component<$$ComponentProps, {}, "open">;
|
|
38
|
+
declare const SwarmNode: import("svelte").Component<$$ComponentProps, {}, "open" | "detailOpen">;
|
|
31
39
|
type SwarmNode = ReturnType<typeof SwarmNode>;
|
|
32
40
|
export default SwarmNode;
|
|
@@ -3,12 +3,12 @@
|
|
|
3
3
|
import type { HTMLAttributes } from 'svelte/elements';
|
|
4
4
|
import { cn } from '../../utils/index.js';
|
|
5
5
|
import Node from './swarm-node.svelte';
|
|
6
|
-
import { buildSwarmTree, type SwarmAgent, type SwarmNode } from './swarm.js';
|
|
6
|
+
import { buildSwarmTree, isSwarmAgentActive, type SwarmAgent, type SwarmNode } from './swarm.js';
|
|
7
7
|
|
|
8
8
|
let {
|
|
9
9
|
ref = $bindable(null),
|
|
10
10
|
agents = [],
|
|
11
|
-
|
|
11
|
+
detailLabel,
|
|
12
12
|
detail,
|
|
13
13
|
meta,
|
|
14
14
|
empty,
|
|
@@ -20,11 +20,11 @@
|
|
|
20
20
|
/** The swarm's agents, flat. The tree is built from each one's `parentId`. */
|
|
21
21
|
agents?: SwarmAgent[];
|
|
22
22
|
/**
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
23
|
+
* Names each row's detail affordance — "2 artifacts", "transcript" — and its
|
|
24
|
+
* absence is what says a row has nothing to show. Returning undefined for an
|
|
25
|
+
* agent leaves that row a plain line.
|
|
26
26
|
*/
|
|
27
|
-
|
|
27
|
+
detailLabel?: (agent: SwarmAgent) => string | undefined;
|
|
28
28
|
/** Expanded body for a row — artifacts, a transcript, tool output. */
|
|
29
29
|
detail?: Snippet<[SwarmAgent]>;
|
|
30
30
|
/** Trailing row content, rendered before the elapsed readout. */
|
|
@@ -37,9 +37,21 @@
|
|
|
37
37
|
|
|
38
38
|
const tree = $derived(buildSwarmTree(agents));
|
|
39
39
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
40
|
+
/*
|
|
41
|
+
* ATTENTION FOLLOWS THE WORK.
|
|
42
|
+
*
|
|
43
|
+
* A detail opens by itself while its agent is working and folds when the agent
|
|
44
|
+
* lands, because by then the row already carries the one-line result and the
|
|
45
|
+
* open body is just noise — a finished swarm used to sit fully unfolded, every
|
|
46
|
+
* artifact card stacked under every row at once.
|
|
47
|
+
*
|
|
48
|
+
* A user's own toggle outranks that forever: the override is only consulted for
|
|
49
|
+
* rows they have actually touched, so pinning a landed agent open survives the
|
|
50
|
+
* rest of the run, and everything untouched keeps tracking the work.
|
|
51
|
+
*/
|
|
52
|
+
let detailOverride = $state<Record<string, boolean>>({});
|
|
53
|
+
const detailOpenFor = (agent: SwarmAgent) =>
|
|
54
|
+
detailOverride[agent.id] ?? isSwarmAgentActive(agent);
|
|
43
55
|
|
|
44
56
|
// Rows default to OPEN and remember only what the user closed. A swarm streams
|
|
45
57
|
// in — an agent that appears three seconds from now has no entry here, and
|
|
@@ -76,10 +88,12 @@
|
|
|
76
88
|
const out: Row[] = [];
|
|
77
89
|
const walk = (nodes: SwarmNode[], parentId: string | null) => {
|
|
78
90
|
for (const node of nodes) {
|
|
79
|
-
|
|
91
|
+
// Subtree only — the arrow keys navigate structure, and a row's detail
|
|
92
|
+
// is not structure. (It is reachable from the row's own chip.)
|
|
93
|
+
const hasChildren = node.children.length > 0;
|
|
80
94
|
const open = isOpen(node.agent.id);
|
|
81
|
-
out.push({ id: node.agent.id, agent: node.agent, parentId, expandable:
|
|
82
|
-
if (
|
|
95
|
+
out.push({ id: node.agent.id, agent: node.agent, parentId, expandable: hasChildren, open });
|
|
96
|
+
if (hasChildren && open) walk(node.children, node.agent.id);
|
|
83
97
|
}
|
|
84
98
|
};
|
|
85
99
|
walk(tree, null);
|
|
@@ -186,14 +200,16 @@
|
|
|
186
200
|
knows the visible order), and a self-recursing node would have to hand every
|
|
187
201
|
level a copy of that state. -->
|
|
188
202
|
{#snippet nodeView(node: SwarmNode, level: number)}
|
|
189
|
-
<!-- A leaf gets NO children snippet, rather than one that renders nothing:
|
|
190
|
-
|
|
191
|
-
|
|
203
|
+
<!-- A leaf gets NO children snippet, rather than one that renders nothing: the
|
|
204
|
+
node reads the snippet's presence to decide whether it has a subtree, so an
|
|
205
|
+
always-passed empty group would put a dead chevron on every leaf. -->
|
|
192
206
|
{@const props = {
|
|
193
207
|
agent: node.agent,
|
|
194
208
|
level,
|
|
195
|
-
expandable: nodeExpandable(node),
|
|
196
209
|
detail,
|
|
210
|
+
detailLabel: detailLabel?.(node.agent),
|
|
211
|
+
detailOpen: detailOpenFor(node.agent),
|
|
212
|
+
onDetailToggle: (open: boolean) => (detailOverride[node.agent.id] = open),
|
|
197
213
|
meta,
|
|
198
214
|
open: isOpen(node.agent.id),
|
|
199
215
|
// `active` is the whole roving-focus story: the node derives its own
|
|
@@ -6,11 +6,11 @@ type $$ComponentProps = Omit<HTMLAttributes<HTMLDivElement>, 'children'> & {
|
|
|
6
6
|
/** The swarm's agents, flat. The tree is built from each one's `parentId`. */
|
|
7
7
|
agents?: SwarmAgent[];
|
|
8
8
|
/**
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
9
|
+
* Names each row's detail affordance — "2 artifacts", "transcript" — and its
|
|
10
|
+
* absence is what says a row has nothing to show. Returning undefined for an
|
|
11
|
+
* agent leaves that row a plain line.
|
|
12
12
|
*/
|
|
13
|
-
|
|
13
|
+
detailLabel?: (agent: SwarmAgent) => string | undefined;
|
|
14
14
|
/** Expanded body for a row — artifacts, a transcript, tool output. */
|
|
15
15
|
detail?: Snippet<[SwarmAgent]>;
|
|
16
16
|
/** Trailing row content, rendered before the elapsed readout. */
|
|
@@ -50,9 +50,17 @@
|
|
|
50
50
|
{#if view === 'grid'}
|
|
51
51
|
<AI.Swarm.Grid agents={swarm.agents} />
|
|
52
52
|
{:else}
|
|
53
|
-
|
|
53
|
+
<!-- The chip names what it opens, so the section heading inside would say it
|
|
54
|
+
twice — the detail is just the cards. -->
|
|
55
|
+
<AI.Swarm.Tree
|
|
56
|
+
agents={swarm.agents}
|
|
57
|
+
detailLabel={(agent) => {
|
|
58
|
+
const n = artifactsFor(agent).length;
|
|
59
|
+
return n === 0 ? undefined : `${n} artifact${n === 1 ? '' : 's'}`;
|
|
60
|
+
}}
|
|
61
|
+
>
|
|
54
62
|
{#snippet detail(agent)}
|
|
55
|
-
<AI.Swarm.Artifacts artifacts={artifactsFor(agent)} />
|
|
63
|
+
<AI.Swarm.Artifacts artifacts={artifactsFor(agent)} label={undefined} />
|
|
56
64
|
{/snippet}
|
|
57
65
|
</AI.Swarm.Tree>
|
|
58
66
|
{/if}
|
|
@@ -158,7 +158,16 @@
|
|
|
158
158
|
term's notes app uses — its hardcoded 3px is what Bar now derives at
|
|
159
159
|
dense. The field keeps its fill: xxs is the smallest tier a real field
|
|
160
160
|
fits on. -->
|
|
161
|
-
|
|
161
|
+
<!-- The divider is drawn OUTSIDE the box, exactly as Pane.Handle draws its
|
|
162
|
+
own: a border consumes a pixel of the bar's content box, so its children
|
|
163
|
+
centred in 23px and sat 2.5px from the top against a 3px wall. Outset
|
|
164
|
+
costs no layout, so the bar keeps its full tier height and every child
|
|
165
|
+
sits the wall's distance from all four edges. -->
|
|
166
|
+
<Bar
|
|
167
|
+
size="md"
|
|
168
|
+
content="xxs"
|
|
169
|
+
class="shrink-0 rounded-none [box-shadow:0_1px_0_var(--color-divider-faded)]"
|
|
170
|
+
>
|
|
162
171
|
<Input
|
|
163
172
|
icon={Search}
|
|
164
173
|
size="xxs"
|
|
@@ -282,19 +291,21 @@
|
|
|
282
291
|
<ContextMenu.Content>
|
|
283
292
|
<ContextMenu.Item onSelect={() => store.togglePin(note.id)}>
|
|
284
293
|
{#if note.pinned}
|
|
285
|
-
<PinOff class="
|
|
294
|
+
<PinOff class="size-xs text-muted" />
|
|
286
295
|
Unpin note
|
|
287
296
|
{:else}
|
|
288
|
-
<Pin class="
|
|
297
|
+
<Pin class="size-xs text-muted" />
|
|
289
298
|
Pin note
|
|
290
299
|
{/if}
|
|
291
300
|
</ContextMenu.Item>
|
|
292
|
-
|
|
301
|
+
<!-- No separator: a rule groups things, and two actions on one row
|
|
302
|
+
are one group — it was drawing a boundary through the middle of
|
|
303
|
+
a pair. The panel's gutter gives them their own breathing room. -->
|
|
293
304
|
<!-- Routed through the same confirm dialog the toolbar button
|
|
294
305
|
uses, not a direct delete: a destructive action should not
|
|
295
306
|
get easier to reach by arriving from a different gesture. -->
|
|
296
307
|
<ContextMenu.Item onSelect={() => (pendingDelete = note.id)}>
|
|
297
|
-
<Trash2 class="
|
|
308
|
+
<Trash2 class="size-xs text-destructive" />
|
|
298
309
|
Delete note
|
|
299
310
|
</ContextMenu.Item>
|
|
300
311
|
</ContextMenu.Content>
|
|
@@ -314,7 +325,12 @@
|
|
|
314
325
|
{#if selected}
|
|
315
326
|
<!-- Matches the sidebar's filter bar exactly — the two headers sit side by
|
|
316
327
|
side across the top of the app, so they must be the same tier. -->
|
|
317
|
-
|
|
328
|
+
<!-- Same outset divider as the sidebar's filter bar above. -->
|
|
329
|
+
<Bar
|
|
330
|
+
size="md"
|
|
331
|
+
content="xxs"
|
|
332
|
+
class="shrink-0 rounded-none [box-shadow:0_1px_0_var(--color-divider-faded)]"
|
|
333
|
+
>
|
|
318
334
|
<Popover.Root bind:open={iconPickerOpen}>
|
|
319
335
|
<Popover.Trigger icon size="xxs" aria-label="Change icon" title="Change icon">
|
|
320
336
|
{#if selected.icon}
|