@sero-ai/ui 0.3.2 → 0.4.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.
- package/.turbo/turbo-build.log +628 -0
- package/.turbo/turbo-typecheck.log +1 -1
- package/CHANGELOG.md +38 -0
- package/dist/components/context-editor/CapabilitySection.cjs +57 -0
- package/dist/components/context-editor/CapabilitySection.d.cts +20 -0
- package/dist/components/context-editor/CapabilitySection.d.ts +20 -0
- package/dist/components/context-editor/CapabilitySection.js +57 -0
- package/dist/components/context-editor/ContextEditor.cjs +108 -0
- package/dist/components/context-editor/ContextEditor.d.cts +26 -0
- package/dist/components/context-editor/ContextEditor.d.ts +26 -0
- package/dist/components/context-editor/ContextEditor.js +108 -0
- package/dist/components/context-editor/PresetBar.cjs +71 -0
- package/dist/components/context-editor/PresetBar.d.cts +19 -0
- package/dist/components/context-editor/PresetBar.d.ts +19 -0
- package/dist/components/context-editor/PresetBar.js +71 -0
- package/dist/components/context-editor/SkillsSection.cjs +31 -0
- package/dist/components/context-editor/SkillsSection.d.cts +14 -0
- package/dist/components/context-editor/SkillsSection.d.ts +14 -0
- package/dist/components/context-editor/SkillsSection.js +31 -0
- package/dist/components/context-editor/SystemPromptSection.cjs +58 -0
- package/dist/components/context-editor/SystemPromptSection.d.cts +25 -0
- package/dist/components/context-editor/SystemPromptSection.d.ts +25 -0
- package/dist/components/context-editor/SystemPromptSection.js +58 -0
- package/dist/components/context-editor/ToolsSection.cjs +31 -0
- package/dist/components/context-editor/ToolsSection.d.cts +14 -0
- package/dist/components/context-editor/ToolsSection.d.ts +14 -0
- package/dist/components/context-editor/ToolsSection.js +31 -0
- package/dist/components/context-editor/index.cjs +8 -0
- package/dist/components/context-editor/index.d.cts +5 -0
- package/dist/components/context-editor/index.d.ts +5 -0
- package/dist/components/context-editor/index.js +8 -0
- package/dist/components/context-editor/parts.cjs +184 -0
- package/dist/components/context-editor/parts.d.cts +32 -0
- package/dist/components/context-editor/parts.d.ts +32 -0
- package/dist/components/context-editor/parts.js +184 -0
- package/dist/components/context-editor/use-context-editor-model.cjs +194 -0
- package/dist/components/context-editor/use-context-editor-model.d.cts +57 -0
- package/dist/components/context-editor/use-context-editor-model.d.ts +57 -0
- package/dist/components/context-editor/use-context-editor-model.js +194 -0
- package/dist/components/dashboard/activity-list.cjs +59 -0
- package/dist/components/dashboard/activity-list.d.cts +27 -0
- package/dist/components/dashboard/activity-list.d.ts +27 -0
- package/dist/components/dashboard/activity-list.js +59 -0
- package/dist/components/dashboard/catalog.cjs +322 -0
- package/dist/components/dashboard/catalog.d.cts +14 -0
- package/dist/components/dashboard/catalog.d.ts +14 -0
- package/dist/components/dashboard/catalog.js +322 -0
- package/dist/components/dashboard/data-boundary.cjs +29 -0
- package/dist/components/dashboard/data-boundary.d.cts +21 -0
- package/dist/components/dashboard/data-boundary.d.ts +21 -0
- package/dist/components/dashboard/data-boundary.js +29 -0
- package/dist/components/dashboard/empty-state.cjs +49 -0
- package/dist/components/dashboard/empty-state.d.cts +20 -0
- package/dist/components/dashboard/empty-state.d.ts +20 -0
- package/dist/components/dashboard/empty-state.js +49 -0
- package/dist/components/dashboard/icon-button.cjs +40 -0
- package/dist/components/dashboard/icon-button.d.cts +24 -0
- package/dist/components/dashboard/icon-button.d.ts +24 -0
- package/dist/components/dashboard/icon-button.js +40 -0
- package/dist/components/dashboard/index.cjs +15 -0
- package/dist/components/dashboard/index.d.cts +24 -0
- package/dist/components/dashboard/index.d.ts +24 -0
- package/dist/components/dashboard/index.js +15 -0
- package/dist/components/dashboard/item-list.cjs +71 -0
- package/dist/components/dashboard/item-list.d.cts +28 -0
- package/dist/components/dashboard/item-list.d.ts +28 -0
- package/dist/components/dashboard/item-list.js +71 -0
- package/dist/components/dashboard/key-value.cjs +39 -0
- package/dist/components/dashboard/key-value.d.cts +17 -0
- package/dist/components/dashboard/key-value.d.ts +17 -0
- package/dist/components/dashboard/key-value.js +39 -0
- package/dist/components/dashboard/layout.cjs +193 -0
- package/dist/components/dashboard/layout.d.cts +91 -0
- package/dist/components/dashboard/layout.d.ts +91 -0
- package/dist/components/dashboard/layout.js +193 -0
- package/dist/components/dashboard/metric.cjs +98 -0
- package/dist/components/dashboard/metric.d.cts +44 -0
- package/dist/components/dashboard/metric.d.ts +44 -0
- package/dist/components/dashboard/metric.js +98 -0
- package/dist/components/dashboard/progress-ring.cjs +77 -0
- package/dist/components/dashboard/progress-ring.d.cts +25 -0
- package/dist/components/dashboard/progress-ring.d.ts +25 -0
- package/dist/components/dashboard/progress-ring.js +77 -0
- package/dist/components/dashboard/skeletons.cjs +54 -0
- package/dist/components/dashboard/skeletons.d.cts +15 -0
- package/dist/components/dashboard/skeletons.d.ts +15 -0
- package/dist/components/dashboard/skeletons.js +54 -0
- package/dist/components/dashboard/spacing.cjs +10 -0
- package/dist/components/dashboard/spacing.d.cts +4 -0
- package/dist/components/dashboard/spacing.d.ts +4 -0
- package/dist/components/dashboard/spacing.js +10 -0
- package/dist/components/dashboard/stale-indicator.cjs +42 -0
- package/dist/components/dashboard/stale-indicator.d.cts +15 -0
- package/dist/components/dashboard/stale-indicator.d.ts +15 -0
- package/dist/components/dashboard/stale-indicator.js +42 -0
- package/dist/components/dashboard/status.cjs +62 -0
- package/dist/components/dashboard/status.d.cts +15 -0
- package/dist/components/dashboard/status.d.ts +15 -0
- package/dist/components/dashboard/status.js +62 -0
- package/dist/components/dashboard/tone.cjs +26 -0
- package/dist/components/dashboard/tone.d.cts +9 -0
- package/dist/components/dashboard/tone.d.ts +9 -0
- package/dist/components/dashboard/tone.js +26 -0
- package/dist/components/dashboard/typography.cjs +99 -0
- package/dist/components/dashboard/typography.d.cts +57 -0
- package/dist/components/dashboard/typography.d.ts +57 -0
- package/dist/components/dashboard/typography.js +99 -0
- package/dist/components/reference/ActivityExample.cjs +47 -0
- package/dist/components/reference/ActivityExample.d.cts +5 -0
- package/dist/components/reference/ActivityExample.d.ts +5 -0
- package/dist/components/reference/ActivityExample.js +47 -0
- package/dist/components/reference/ResourceExample.cjs +54 -0
- package/dist/components/reference/ResourceExample.d.cts +5 -0
- package/dist/components/reference/ResourceExample.d.ts +5 -0
- package/dist/components/reference/ResourceExample.js +54 -0
- package/dist/components/reference/SchedulerExample.cjs +68 -0
- package/dist/components/reference/SchedulerExample.d.cts +5 -0
- package/dist/components/reference/SchedulerExample.d.ts +5 -0
- package/dist/components/reference/SchedulerExample.js +68 -0
- package/dist/components/reference/StarterExample.cjs +34 -0
- package/dist/components/reference/StarterExample.d.cts +5 -0
- package/dist/components/reference/StarterExample.d.ts +5 -0
- package/dist/components/reference/StarterExample.js +34 -0
- package/dist/components/reference/index.cjs +10 -0
- package/dist/components/reference/index.d.cts +5 -0
- package/dist/components/reference/index.d.ts +5 -0
- package/dist/components/reference/index.js +10 -0
- package/dist/components/ui/badge.cjs +5 -1
- package/dist/components/ui/badge.d.cts +1 -1
- package/dist/components/ui/badge.d.ts +1 -1
- package/dist/components/ui/badge.js +5 -1
- package/dist/dashboard-catalog.cjs +8 -0
- package/dist/dashboard-catalog.d.cts +1 -0
- package/dist/dashboard-catalog.d.ts +1 -0
- package/dist/dashboard-catalog.js +8 -0
- package/dist/index.cjs +1 -0
- package/dist/index.d.cts +15 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +1 -0
- package/dist/reference.cjs +12 -0
- package/dist/reference.d.cts +5 -0
- package/dist/reference.d.ts +5 -0
- package/dist/reference.js +12 -0
- package/dist/styles/glass-board.css +43 -0
- package/dist/styles/globals.css +59 -0
- package/package.json +70 -41
- package/src/components/context-editor/CapabilitySection.tsx +85 -0
- package/src/components/context-editor/ContextEditor.tsx +138 -0
- package/src/components/context-editor/PresetBar.tsx +94 -0
- package/src/components/context-editor/SkillsSection.tsx +37 -0
- package/src/components/context-editor/SystemPromptSection.tsx +88 -0
- package/src/components/context-editor/ToolsSection.tsx +37 -0
- package/src/components/context-editor/index.ts +7 -0
- package/src/components/context-editor/parts.tsx +217 -0
- package/src/components/context-editor/use-context-editor-model.ts +296 -0
- package/src/components/dashboard/__tests__/catalog.test.ts +57 -0
- package/src/components/dashboard/__tests__/glass.test.tsx +42 -0
- package/src/components/dashboard/__tests__/state.test.tsx +110 -0
- package/src/components/dashboard/__tests__/variants.test.tsx +95 -0
- package/src/components/dashboard/activity-list.tsx +99 -0
- package/src/components/dashboard/catalog.ts +351 -0
- package/src/components/dashboard/data-boundary.tsx +54 -0
- package/src/components/dashboard/empty-state.tsx +70 -0
- package/src/components/dashboard/icon-button.tsx +58 -0
- package/src/components/dashboard/index.ts +21 -0
- package/src/components/dashboard/item-list.tsx +106 -0
- package/src/components/dashboard/key-value.tsx +54 -0
- package/src/components/dashboard/layout.tsx +286 -0
- package/src/components/dashboard/metric.tsx +139 -0
- package/src/components/dashboard/progress-ring.tsx +100 -0
- package/src/components/dashboard/skeletons.tsx +73 -0
- package/src/components/dashboard/spacing.ts +14 -0
- package/src/components/dashboard/stale-indicator.tsx +55 -0
- package/src/components/dashboard/status.tsx +75 -0
- package/src/components/dashboard/tone.ts +35 -0
- package/src/components/dashboard/typography.tsx +151 -0
- package/src/components/reference/ActivityExample.tsx +68 -0
- package/src/components/reference/ResourceExample.tsx +78 -0
- package/src/components/reference/SchedulerExample.tsx +88 -0
- package/src/components/reference/StarterExample.tsx +48 -0
- package/src/components/reference/index.ts +10 -0
- package/src/components/ui/badge.tsx +6 -0
- package/src/dashboard-catalog.ts +13 -0
- package/src/index.ts +5 -0
- package/src/reference.ts +15 -0
- package/src/styles/glass-board.css +43 -0
- package/src/styles/globals.css +59 -0
- package/tsup.config.ts +6 -2
- package/vitest.config.ts +8 -0
- package/LICENSE +0 -201
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Presentational sub-components for the shared ContextEditor dialog.
|
|
3
|
+
* Controlled, host-agnostic — no window.sero / session coupling.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { useState } from 'react';
|
|
7
|
+
import { AnimatePresence, motion } from 'motion/react';
|
|
8
|
+
import { ChevronRight } from 'lucide-react';
|
|
9
|
+
import { cn } from '../../lib/utils';
|
|
10
|
+
import { Switch } from '../ui/switch';
|
|
11
|
+
|
|
12
|
+
// ── Color tint definitions ──────────────────────────────────────
|
|
13
|
+
|
|
14
|
+
type SectionTint = 'blue' | 'amber' | 'violet' | 'neutral';
|
|
15
|
+
type BadgeVariant = 'default' | 'modified' | 'disabled' | 'partial';
|
|
16
|
+
|
|
17
|
+
const iconTintClass: Record<SectionTint, string> = {
|
|
18
|
+
blue: 'text-status-info',
|
|
19
|
+
amber: 'text-status-warning',
|
|
20
|
+
violet: 'text-[var(--collab-primary)]',
|
|
21
|
+
neutral: 'text-[var(--text-muted)]',
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const badgeClass: Record<BadgeVariant, string> = {
|
|
25
|
+
default: 'bg-[var(--bg-base)] text-[var(--text-muted)]',
|
|
26
|
+
modified: 'bg-status-warning-subtle text-status-warning',
|
|
27
|
+
disabled: 'bg-status-error-subtle text-status-error',
|
|
28
|
+
partial: 'bg-status-info-subtle text-status-info',
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const sectionBorderClass: Record<BadgeVariant, string> = {
|
|
32
|
+
default: 'border-border/50',
|
|
33
|
+
modified: 'border-status-warning-border',
|
|
34
|
+
disabled: 'border-status-error-border',
|
|
35
|
+
partial: 'border-status-info-border',
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const sectionBgClass: Record<BadgeVariant, string> = {
|
|
39
|
+
default: 'bg-[var(--bg-elevated)]/50',
|
|
40
|
+
modified: 'bg-status-warning-faint',
|
|
41
|
+
disabled: 'bg-status-error-faint',
|
|
42
|
+
partial: 'bg-status-info-faint',
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
// ── Collapsible Section ─────────────────────────────────────────
|
|
46
|
+
|
|
47
|
+
export function ContextSection({
|
|
48
|
+
icon: Icon,
|
|
49
|
+
title,
|
|
50
|
+
count,
|
|
51
|
+
badge,
|
|
52
|
+
badgeVariant = 'default',
|
|
53
|
+
tint = 'neutral',
|
|
54
|
+
defaultOpen = false,
|
|
55
|
+
children,
|
|
56
|
+
}: {
|
|
57
|
+
icon: React.ComponentType<{ className?: string }>;
|
|
58
|
+
title: string;
|
|
59
|
+
count?: number;
|
|
60
|
+
badge?: string;
|
|
61
|
+
badgeVariant?: BadgeVariant;
|
|
62
|
+
tint?: SectionTint;
|
|
63
|
+
defaultOpen?: boolean;
|
|
64
|
+
children: React.ReactNode;
|
|
65
|
+
}) {
|
|
66
|
+
const [expanded, setExpanded] = useState(defaultOpen);
|
|
67
|
+
|
|
68
|
+
return (
|
|
69
|
+
<div
|
|
70
|
+
className={cn(
|
|
71
|
+
'overflow-hidden rounded-lg border transition-colors duration-200',
|
|
72
|
+
sectionBorderClass[badgeVariant],
|
|
73
|
+
sectionBgClass[badgeVariant],
|
|
74
|
+
)}
|
|
75
|
+
>
|
|
76
|
+
<button type="button"
|
|
77
|
+
onClick={() => setExpanded((prev) => !prev)}
|
|
78
|
+
className={cn(
|
|
79
|
+
'flex w-full items-center gap-2.5 px-3 py-2 text-left transition-colors duration-150',
|
|
80
|
+
'hover:bg-[var(--bg-elevated)]/80',
|
|
81
|
+
)}
|
|
82
|
+
>
|
|
83
|
+
<motion.div
|
|
84
|
+
animate={{ rotate: expanded ? 90 : 0 }}
|
|
85
|
+
transition={{ type: 'spring', stiffness: 300, damping: 25 }}
|
|
86
|
+
>
|
|
87
|
+
<ChevronRight className="size-3.5 text-[var(--text-muted)]" />
|
|
88
|
+
</motion.div>
|
|
89
|
+
|
|
90
|
+
<Icon className={cn('size-3.5', iconTintClass[tint])} />
|
|
91
|
+
|
|
92
|
+
<span className="text-xs font-medium text-[var(--text-secondary)]">
|
|
93
|
+
{title}
|
|
94
|
+
</span>
|
|
95
|
+
|
|
96
|
+
{count !== undefined && (
|
|
97
|
+
<span className="text-[11px] text-[var(--text-muted)]">
|
|
98
|
+
({count})
|
|
99
|
+
</span>
|
|
100
|
+
)}
|
|
101
|
+
|
|
102
|
+
{badge && (
|
|
103
|
+
<span
|
|
104
|
+
className={cn(
|
|
105
|
+
'ml-auto rounded-full px-2 py-0.5 text-[10px] font-medium',
|
|
106
|
+
badgeClass[badgeVariant],
|
|
107
|
+
)}
|
|
108
|
+
>
|
|
109
|
+
{badge}
|
|
110
|
+
</span>
|
|
111
|
+
)}
|
|
112
|
+
</button>
|
|
113
|
+
|
|
114
|
+
<AnimatePresence>
|
|
115
|
+
{expanded && (
|
|
116
|
+
<motion.div
|
|
117
|
+
initial={{ height: 0, opacity: 0 }}
|
|
118
|
+
animate={{ height: 'auto', opacity: 1 }}
|
|
119
|
+
exit={{ height: 0, opacity: 0 }}
|
|
120
|
+
transition={{ duration: 0.2, ease: [0.25, 0.46, 0.45, 0.94] }}
|
|
121
|
+
className="overflow-hidden"
|
|
122
|
+
>
|
|
123
|
+
<div className="min-w-0 border-t border-border/30 p-3">
|
|
124
|
+
{children}
|
|
125
|
+
</div>
|
|
126
|
+
</motion.div>
|
|
127
|
+
)}
|
|
128
|
+
</AnimatePresence>
|
|
129
|
+
</div>
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// ── Generic Toggle Row (used for both tools and skills) ─────────
|
|
134
|
+
|
|
135
|
+
export function ToggleRow({
|
|
136
|
+
name,
|
|
137
|
+
description,
|
|
138
|
+
enabled,
|
|
139
|
+
onToggle,
|
|
140
|
+
}: {
|
|
141
|
+
name: string;
|
|
142
|
+
description?: string;
|
|
143
|
+
enabled: boolean;
|
|
144
|
+
onToggle: () => void;
|
|
145
|
+
}) {
|
|
146
|
+
return (
|
|
147
|
+
<div
|
|
148
|
+
className={cn(
|
|
149
|
+
'flex min-w-0 items-start justify-between gap-3 rounded-md px-2 py-1.5 transition-colors',
|
|
150
|
+
enabled
|
|
151
|
+
? 'hover:bg-[var(--bg-elevated)]/60'
|
|
152
|
+
: 'opacity-60 hover:bg-[var(--bg-elevated)]/40',
|
|
153
|
+
)}
|
|
154
|
+
>
|
|
155
|
+
<div className="min-w-0 flex-1">
|
|
156
|
+
<div className={cn(
|
|
157
|
+
'text-[11px] font-medium',
|
|
158
|
+
enabled ? 'text-[var(--text-secondary)]' : 'text-[var(--text-muted)]',
|
|
159
|
+
)}>
|
|
160
|
+
{name}
|
|
161
|
+
</div>
|
|
162
|
+
{description && (
|
|
163
|
+
<div className="mt-0.5 text-[10px] leading-snug text-[var(--text-muted)]/60">
|
|
164
|
+
{description}
|
|
165
|
+
</div>
|
|
166
|
+
)}
|
|
167
|
+
</div>
|
|
168
|
+
<Switch
|
|
169
|
+
size="sm"
|
|
170
|
+
className="mt-0.5 shrink-0"
|
|
171
|
+
checked={enabled}
|
|
172
|
+
onCheckedChange={onToggle}
|
|
173
|
+
/>
|
|
174
|
+
</div>
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// ── Save Preset Input ───────────────────────────────────────────
|
|
179
|
+
|
|
180
|
+
export function SavePresetInput({
|
|
181
|
+
onSave,
|
|
182
|
+
onCancel,
|
|
183
|
+
}: {
|
|
184
|
+
onSave: (name: string) => void;
|
|
185
|
+
onCancel: () => void;
|
|
186
|
+
}) {
|
|
187
|
+
const [name, setName] = useState('');
|
|
188
|
+
|
|
189
|
+
return (
|
|
190
|
+
<div className="flex items-center gap-2">
|
|
191
|
+
<input aria-label="Preset name"
|
|
192
|
+
type="text"
|
|
193
|
+
value={name}
|
|
194
|
+
onChange={(e) => setName(e.target.value)}
|
|
195
|
+
placeholder="Preset name..."
|
|
196
|
+
className="flex-1 rounded-md border border-border/50 bg-[var(--bg-base)] px-2 py-1 text-xs text-[var(--text-primary)] placeholder:text-[var(--text-muted)] outline-none focus:border-[var(--accent-primary)]"
|
|
197
|
+
onKeyDown={(e) => {
|
|
198
|
+
if (e.key === 'Enter' && name.trim()) onSave(name.trim());
|
|
199
|
+
if (e.key === 'Escape') onCancel();
|
|
200
|
+
}}
|
|
201
|
+
/>
|
|
202
|
+
<button type="button"
|
|
203
|
+
onClick={() => name.trim() && onSave(name.trim())}
|
|
204
|
+
disabled={!name.trim()}
|
|
205
|
+
className="rounded-md bg-[var(--accent-primary)] px-2 py-1 text-[11px] font-medium text-white disabled:opacity-50"
|
|
206
|
+
>
|
|
207
|
+
Save
|
|
208
|
+
</button>
|
|
209
|
+
<button type="button"
|
|
210
|
+
onClick={onCancel}
|
|
211
|
+
className="text-[11px] text-[var(--text-muted)] hover:text-[var(--text-secondary)]"
|
|
212
|
+
>
|
|
213
|
+
Cancel
|
|
214
|
+
</button>
|
|
215
|
+
</div>
|
|
216
|
+
);
|
|
217
|
+
}
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useContextEditorModel — the controlled editing state for the ContextEditor.
|
|
3
|
+
*
|
|
4
|
+
* Holds the working overrides (system prompt, disabled tools/skills, preset
|
|
5
|
+
* selection) and all the toggle/normalize/preset logic, seeded from the
|
|
6
|
+
* `available` context and `initialOverrides`. Host-agnostic: persistence
|
|
7
|
+
* (apply / save preset / delete preset) is injected by the consumer, so the
|
|
8
|
+
* chat session editor and an app module (e.g. an Orchestrator loop) share one
|
|
9
|
+
* implementation.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
13
|
+
import type {
|
|
14
|
+
AvailableContext,
|
|
15
|
+
ContextOverrides,
|
|
16
|
+
ContextPreset,
|
|
17
|
+
ContextSkillInfo,
|
|
18
|
+
ContextToolInfo,
|
|
19
|
+
} from '@sero-ai/common';
|
|
20
|
+
|
|
21
|
+
const NONE_PRESET: ContextPreset = {
|
|
22
|
+
id: '__none__',
|
|
23
|
+
name: 'None',
|
|
24
|
+
systemPrompt: '',
|
|
25
|
+
disabledTools: ['__all__'],
|
|
26
|
+
disabledSkills: ['__all__'],
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const DEFAULT_PRESET: ContextPreset = {
|
|
30
|
+
id: '__default__',
|
|
31
|
+
name: 'Default',
|
|
32
|
+
systemPrompt: null,
|
|
33
|
+
disabledTools: [],
|
|
34
|
+
disabledSkills: [],
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const BUILTIN_PRESETS: ContextPreset[] = [DEFAULT_PRESET, NONE_PRESET];
|
|
38
|
+
|
|
39
|
+
function normalizeDisabledNames(names: string[] | undefined, availableNames: string[]): Set<string> {
|
|
40
|
+
const allowed = new Set(availableNames);
|
|
41
|
+
return new Set((names ?? []).filter((name) => allowed.has(name)));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function appliedSystemPrompt(overrides: ContextOverrides | null): string | null {
|
|
45
|
+
if (!overrides || !Object.prototype.hasOwnProperty.call(overrides, 'systemPrompt')) return null;
|
|
46
|
+
return typeof overrides.systemPrompt === 'string' ? overrides.systemPrompt : null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function enabledCount<TItem extends ContextToolInfo | ContextSkillInfo>(
|
|
50
|
+
items: TItem[],
|
|
51
|
+
disabled: Set<string>,
|
|
52
|
+
allDisabled: boolean,
|
|
53
|
+
): number {
|
|
54
|
+
if (allDisabled) return 0;
|
|
55
|
+
return items.filter((item) => !disabled.has(item.name)).length;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** The persistable body of a preset (id assigned by the consumer). */
|
|
59
|
+
export type ContextPresetBody = Pick<ContextPreset, 'systemPrompt' | 'disabledTools' | 'disabledSkills'>;
|
|
60
|
+
|
|
61
|
+
interface CapabilityState<TItem extends ContextToolInfo | ContextSkillInfo> {
|
|
62
|
+
items: TItem[];
|
|
63
|
+
allDisabled: boolean;
|
|
64
|
+
enabledCount: number;
|
|
65
|
+
isEnabled: (name: string) => boolean;
|
|
66
|
+
onToggle: (name: string) => void;
|
|
67
|
+
onToggleAll: (disabled: boolean) => void;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface ContextEditorModel {
|
|
71
|
+
displayedPrompt: string;
|
|
72
|
+
systemPrompt: string | null;
|
|
73
|
+
setSystemPrompt: (value: string | null) => void;
|
|
74
|
+
preset: {
|
|
75
|
+
allPresets: ContextPreset[];
|
|
76
|
+
activePresetId: string | null;
|
|
77
|
+
activeUserPreset: ContextPreset | null;
|
|
78
|
+
hasOverrides: boolean;
|
|
79
|
+
showSaveInput: boolean;
|
|
80
|
+
onPresetChange: (id: string) => void;
|
|
81
|
+
onDelete: (id: string) => void;
|
|
82
|
+
onReset: () => void;
|
|
83
|
+
onShowSave: () => void;
|
|
84
|
+
onSave: (name: string) => void;
|
|
85
|
+
onCancelSave: () => void;
|
|
86
|
+
};
|
|
87
|
+
tools: CapabilityState<ContextToolInfo>;
|
|
88
|
+
skills: CapabilityState<ContextSkillInfo>;
|
|
89
|
+
/** Build the overrides to persist, or null when nothing is overridden. */
|
|
90
|
+
buildOverrides: () => ContextOverrides | null;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export interface UseContextEditorModelParams {
|
|
94
|
+
/** Whether the editor is open — drives one-time seeding per open. */
|
|
95
|
+
open: boolean;
|
|
96
|
+
available: AvailableContext | null;
|
|
97
|
+
initialOverrides: ContextOverrides | null;
|
|
98
|
+
presets: ContextPreset[];
|
|
99
|
+
onSavePreset: (name: string, preset: ContextPresetBody) => void;
|
|
100
|
+
onDeletePreset: (id: string) => void;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function useContextEditorModel({
|
|
104
|
+
open,
|
|
105
|
+
available,
|
|
106
|
+
initialOverrides,
|
|
107
|
+
presets,
|
|
108
|
+
onSavePreset,
|
|
109
|
+
onDeletePreset,
|
|
110
|
+
}: UseContextEditorModelParams): ContextEditorModel {
|
|
111
|
+
const [systemPrompt, setSystemPromptState] = useState<string | null>(null);
|
|
112
|
+
const [disabledTools, setDisabledTools] = useState<Set<string>>(new Set());
|
|
113
|
+
const [disabledSkills, setDisabledSkills] = useState<Set<string>>(new Set());
|
|
114
|
+
const [allToolsDisabled, setAllToolsDisabled] = useState(false);
|
|
115
|
+
const [allSkillsDisabled, setAllSkillsDisabled] = useState(false);
|
|
116
|
+
const [activePresetId, setActivePresetId] = useState<string | null>('__default__');
|
|
117
|
+
const [showSaveInput, setShowSaveInput] = useState(false);
|
|
118
|
+
|
|
119
|
+
const tools = available?.tools ?? [];
|
|
120
|
+
const skills = available?.skills ?? [];
|
|
121
|
+
|
|
122
|
+
// Seed once per open, after the available context has loaded, so the editor
|
|
123
|
+
// reflects the target's current overrides without clobbering live edits.
|
|
124
|
+
const seededRef = useRef(false);
|
|
125
|
+
useEffect(() => {
|
|
126
|
+
if (!open) {
|
|
127
|
+
seededRef.current = false;
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
if (seededRef.current || !available) return;
|
|
131
|
+
seededRef.current = true;
|
|
132
|
+
|
|
133
|
+
const seedDisabledTools = normalizeDisabledNames(
|
|
134
|
+
initialOverrides?.disabledTools,
|
|
135
|
+
available.tools.map((t) => t.name),
|
|
136
|
+
);
|
|
137
|
+
const seedDisabledSkills = normalizeDisabledNames(
|
|
138
|
+
initialOverrides?.disabledSkills,
|
|
139
|
+
available.skills.map((s) => s.name),
|
|
140
|
+
);
|
|
141
|
+
const allTools = available.tools.length > 0 && seedDisabledTools.size >= available.tools.length;
|
|
142
|
+
const allSkills = available.skills.length > 0 && seedDisabledSkills.size >= available.skills.length;
|
|
143
|
+
|
|
144
|
+
setSystemPromptState(appliedSystemPrompt(initialOverrides));
|
|
145
|
+
setDisabledTools(allTools ? new Set() : seedDisabledTools);
|
|
146
|
+
setDisabledSkills(allSkills ? new Set() : seedDisabledSkills);
|
|
147
|
+
setAllToolsDisabled(allTools);
|
|
148
|
+
setAllSkillsDisabled(allSkills);
|
|
149
|
+
setActivePresetId(initialOverrides ? null : '__default__');
|
|
150
|
+
setShowSaveInput(false);
|
|
151
|
+
}, [open, available, initialOverrides]);
|
|
152
|
+
|
|
153
|
+
const setSystemPrompt = useCallback((value: string | null) => {
|
|
154
|
+
setSystemPromptState(value);
|
|
155
|
+
setActivePresetId(null);
|
|
156
|
+
}, []);
|
|
157
|
+
|
|
158
|
+
const toggleCapability = useCallback(
|
|
159
|
+
(
|
|
160
|
+
name: string,
|
|
161
|
+
items: { name: string }[],
|
|
162
|
+
disabled: Set<string>,
|
|
163
|
+
allDisabled: boolean,
|
|
164
|
+
setDisabled: (s: Set<string>) => void,
|
|
165
|
+
setAll: (b: boolean) => void,
|
|
166
|
+
) => {
|
|
167
|
+
let next: Set<string>;
|
|
168
|
+
if (allDisabled) {
|
|
169
|
+
next = new Set(items.map((i) => i.name));
|
|
170
|
+
next.delete(name);
|
|
171
|
+
} else {
|
|
172
|
+
next = new Set(disabled);
|
|
173
|
+
if (next.has(name)) next.delete(name);
|
|
174
|
+
else next.add(name);
|
|
175
|
+
}
|
|
176
|
+
const total = items.length;
|
|
177
|
+
const allNowDisabled = total > 0 && next.size >= total;
|
|
178
|
+
setDisabled(allNowDisabled ? new Set() : next);
|
|
179
|
+
setAll(allNowDisabled);
|
|
180
|
+
setActivePresetId(null);
|
|
181
|
+
},
|
|
182
|
+
[],
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
const loadPreset = useCallback(
|
|
186
|
+
(presetId: string) => {
|
|
187
|
+
const all = [...BUILTIN_PRESETS, ...presets];
|
|
188
|
+
const preset = all.find((p) => p.id === presetId);
|
|
189
|
+
if (!preset) return;
|
|
190
|
+
const allTools = preset.disabledTools.includes('__all__');
|
|
191
|
+
const allSkills = preset.disabledSkills.includes('__all__');
|
|
192
|
+
setSystemPromptState(preset.systemPrompt);
|
|
193
|
+
setDisabledTools(new Set(allTools ? [] : preset.disabledTools));
|
|
194
|
+
setDisabledSkills(new Set(allSkills ? [] : preset.disabledSkills));
|
|
195
|
+
setAllToolsDisabled(allTools);
|
|
196
|
+
setAllSkillsDisabled(allSkills);
|
|
197
|
+
setActivePresetId(presetId);
|
|
198
|
+
},
|
|
199
|
+
[presets],
|
|
200
|
+
);
|
|
201
|
+
|
|
202
|
+
const resetToDefault = useCallback(() => {
|
|
203
|
+
setSystemPromptState(null);
|
|
204
|
+
setDisabledTools(new Set());
|
|
205
|
+
setDisabledSkills(new Set());
|
|
206
|
+
setAllToolsDisabled(false);
|
|
207
|
+
setAllSkillsDisabled(false);
|
|
208
|
+
setActivePresetId('__default__');
|
|
209
|
+
setShowSaveInput(false);
|
|
210
|
+
}, []);
|
|
211
|
+
|
|
212
|
+
const buildOverrides = useCallback((): ContextOverrides | null => {
|
|
213
|
+
const toolsToDisable = allToolsDisabled ? tools.map((t) => t.name) : Array.from(disabledTools);
|
|
214
|
+
const skillsToDisable = allSkillsDisabled ? skills.map((s) => s.name) : Array.from(disabledSkills);
|
|
215
|
+
const has = systemPrompt !== null || toolsToDisable.length > 0 || skillsToDisable.length > 0;
|
|
216
|
+
if (!has) return null;
|
|
217
|
+
return {
|
|
218
|
+
systemPrompt: systemPrompt ?? undefined,
|
|
219
|
+
disabledTools: toolsToDisable.length > 0 ? toolsToDisable : undefined,
|
|
220
|
+
disabledSkills: skillsToDisable.length > 0 ? skillsToDisable : undefined,
|
|
221
|
+
};
|
|
222
|
+
}, [allToolsDisabled, allSkillsDisabled, tools, skills, disabledTools, disabledSkills, systemPrompt]);
|
|
223
|
+
|
|
224
|
+
const handleSavePreset = useCallback(
|
|
225
|
+
(name: string) => {
|
|
226
|
+
onSavePreset(name, {
|
|
227
|
+
systemPrompt,
|
|
228
|
+
disabledTools: allToolsDisabled ? ['__all__'] : Array.from(disabledTools),
|
|
229
|
+
disabledSkills: allSkillsDisabled ? ['__all__'] : Array.from(disabledSkills),
|
|
230
|
+
});
|
|
231
|
+
setShowSaveInput(false);
|
|
232
|
+
},
|
|
233
|
+
[onSavePreset, systemPrompt, allToolsDisabled, disabledTools, allSkillsDisabled, disabledSkills],
|
|
234
|
+
);
|
|
235
|
+
|
|
236
|
+
const allPresets = useMemo(() => [...BUILTIN_PRESETS, ...presets], [presets]);
|
|
237
|
+
const activeUserPreset = useMemo(
|
|
238
|
+
() =>
|
|
239
|
+
activePresetId && !activePresetId.startsWith('__')
|
|
240
|
+
? allPresets.find((p) => p.id === activePresetId) ?? null
|
|
241
|
+
: null,
|
|
242
|
+
[activePresetId, allPresets],
|
|
243
|
+
);
|
|
244
|
+
const hasOverrides =
|
|
245
|
+
systemPrompt !== null ||
|
|
246
|
+
disabledTools.size > 0 ||
|
|
247
|
+
disabledSkills.size > 0 ||
|
|
248
|
+
allToolsDisabled ||
|
|
249
|
+
allSkillsDisabled;
|
|
250
|
+
|
|
251
|
+
return {
|
|
252
|
+
displayedPrompt: systemPrompt ?? available?.systemPrompt ?? '',
|
|
253
|
+
systemPrompt,
|
|
254
|
+
setSystemPrompt,
|
|
255
|
+
preset: {
|
|
256
|
+
allPresets,
|
|
257
|
+
activePresetId,
|
|
258
|
+
activeUserPreset,
|
|
259
|
+
hasOverrides,
|
|
260
|
+
showSaveInput,
|
|
261
|
+
onPresetChange: loadPreset,
|
|
262
|
+
onDelete: onDeletePreset,
|
|
263
|
+
onReset: resetToDefault,
|
|
264
|
+
onShowSave: () => setShowSaveInput(true),
|
|
265
|
+
onSave: handleSavePreset,
|
|
266
|
+
onCancelSave: () => setShowSaveInput(false),
|
|
267
|
+
},
|
|
268
|
+
tools: {
|
|
269
|
+
items: tools,
|
|
270
|
+
allDisabled: allToolsDisabled,
|
|
271
|
+
enabledCount: enabledCount(tools, disabledTools, allToolsDisabled),
|
|
272
|
+
isEnabled: (name) => !allToolsDisabled && !disabledTools.has(name),
|
|
273
|
+
onToggle: (name) =>
|
|
274
|
+
toggleCapability(name, tools, disabledTools, allToolsDisabled, setDisabledTools, setAllToolsDisabled),
|
|
275
|
+
onToggleAll: (disabled) => {
|
|
276
|
+
setAllToolsDisabled(disabled);
|
|
277
|
+
setDisabledTools(new Set());
|
|
278
|
+
setActivePresetId(null);
|
|
279
|
+
},
|
|
280
|
+
},
|
|
281
|
+
skills: {
|
|
282
|
+
items: skills,
|
|
283
|
+
allDisabled: allSkillsDisabled,
|
|
284
|
+
enabledCount: enabledCount(skills, disabledSkills, allSkillsDisabled),
|
|
285
|
+
isEnabled: (name) => !allSkillsDisabled && !disabledSkills.has(name),
|
|
286
|
+
onToggle: (name) =>
|
|
287
|
+
toggleCapability(name, skills, disabledSkills, allSkillsDisabled, setDisabledSkills, setAllSkillsDisabled),
|
|
288
|
+
onToggleAll: (disabled) => {
|
|
289
|
+
setAllSkillsDisabled(disabled);
|
|
290
|
+
setDisabledSkills(new Set());
|
|
291
|
+
setActivePresetId(null);
|
|
292
|
+
},
|
|
293
|
+
},
|
|
294
|
+
buildOverrides,
|
|
295
|
+
};
|
|
296
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// Catalogue integrity — keeps the typed catalogue honest against the exports.
|
|
2
|
+
|
|
3
|
+
import { describe, expect, it } from "vitest";
|
|
4
|
+
|
|
5
|
+
import { dashboardComponentCatalog } from "../catalog";
|
|
6
|
+
import * as dashboard from "../index";
|
|
7
|
+
|
|
8
|
+
// Reused primitives live in other component folders, not the dashboard barrel.
|
|
9
|
+
const REUSED_PRIMITIVES = new Set(["Badge", "Alert", "Button", "Skeleton"]);
|
|
10
|
+
|
|
11
|
+
const names = dashboardComponentCatalog.map((e) => e.name);
|
|
12
|
+
|
|
13
|
+
describe("dashboard catalogue", () => {
|
|
14
|
+
it("has no duplicate component names", () => {
|
|
15
|
+
expect(new Set(names).size).toBe(names.length);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it("every composite entry is exported from the dashboard barrel", () => {
|
|
19
|
+
const missing = dashboardComponentCatalog
|
|
20
|
+
.filter((e) => e.kind === "composite")
|
|
21
|
+
.filter((e) => !(e.name in dashboard))
|
|
22
|
+
.map((e) => e.name);
|
|
23
|
+
expect(missing).toEqual([]);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("every primitive entry is a known reused primitive", () => {
|
|
27
|
+
const bad = dashboardComponentCatalog
|
|
28
|
+
.filter((e) => e.kind === "primitive")
|
|
29
|
+
.filter((e) => !REUSED_PRIMITIVES.has(e.name))
|
|
30
|
+
.map((e) => e.name);
|
|
31
|
+
expect(bad).toEqual([]);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("every related name resolves to a catalogue entry", () => {
|
|
35
|
+
const known = new Set(names);
|
|
36
|
+
const broken: string[] = [];
|
|
37
|
+
for (const entry of dashboardComponentCatalog) {
|
|
38
|
+
for (const rel of entry.related ?? []) {
|
|
39
|
+
if (!known.has(rel)) broken.push(`${entry.name} -> ${rel}`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
expect(broken).toEqual([]);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("every composite exported from the barrel appears in the catalogue", () => {
|
|
46
|
+
// The barrel also exports helpers (tone maps, gapClass) and types; only
|
|
47
|
+
// check the React components (PascalCase functions) are catalogued.
|
|
48
|
+
const catalogued = new Set(names);
|
|
49
|
+
const uncatalogued = Object.entries(dashboard)
|
|
50
|
+
.filter(
|
|
51
|
+
([name, value]) =>
|
|
52
|
+
/^[A-Z]/.test(name) && typeof value === "function" && !catalogued.has(name),
|
|
53
|
+
)
|
|
54
|
+
.map(([name]) => name);
|
|
55
|
+
expect(uncatalogued).toEqual([]);
|
|
56
|
+
});
|
|
57
|
+
});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// Glass-scope tests: WidgetContent applies the token scope, and surfaces
|
|
2
|
+
// route through the scope-aware `--surface-*` tokens rather than solid ones.
|
|
3
|
+
|
|
4
|
+
import { renderToStaticMarkup } from "react-dom/server";
|
|
5
|
+
import { describe, expect, it } from "vitest";
|
|
6
|
+
|
|
7
|
+
import { WidgetContent } from "../layout";
|
|
8
|
+
import { MetricCard } from "../metric";
|
|
9
|
+
import { ItemList, ItemListItem } from "../item-list";
|
|
10
|
+
|
|
11
|
+
describe("WidgetContent glass scope", () => {
|
|
12
|
+
it("applies the glass class by default", () => {
|
|
13
|
+
const html = renderToStaticMarkup(<WidgetContent />);
|
|
14
|
+
expect(html).toContain("glass");
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("omits the glass class when glass={false}", () => {
|
|
18
|
+
const html = renderToStaticMarkup(<WidgetContent glass={false} />);
|
|
19
|
+
expect(html).not.toContain(' class="glass');
|
|
20
|
+
expect(html).not.toMatch(/\bglass\b/);
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
describe("surface token routing", () => {
|
|
25
|
+
it("MetricCard reads raised surface + rim tokens, not solid bg-surface", () => {
|
|
26
|
+
const html = renderToStaticMarkup(<MetricCard />);
|
|
27
|
+
expect(html).toContain("--surface-raised");
|
|
28
|
+
expect(html).toContain("--surface-line");
|
|
29
|
+
expect(html).toContain("--surface-rim");
|
|
30
|
+
expect(html).not.toContain("--bg-surface");
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("ItemListItem reads the flat surface token, not the muted variant", () => {
|
|
34
|
+
const html = renderToStaticMarkup(
|
|
35
|
+
<ItemList>
|
|
36
|
+
<ItemListItem primary="Row" />
|
|
37
|
+
</ItemList>,
|
|
38
|
+
);
|
|
39
|
+
expect(html).toContain("--surface-flat");
|
|
40
|
+
expect(html).not.toContain("bg-muted/50");
|
|
41
|
+
});
|
|
42
|
+
});
|