diff --git a/.agents/design/components.jsonc b/.agents/design/components.jsonc index 3b0d3fb55..cee40f4a2 100644 --- a/.agents/design/components.jsonc +++ b/.agents/design/components.jsonc @@ -1,305 +1,2552 @@ -// components.jsonc — the component patterns your team has agreed on, as a structured -// element tree (JSON + comments). One component = one decision already made. -// -// These render live in the Design System + Prototype canvases via a pure JSON→DOM -// interpreter (no React, no build step). Style with the `ds-*` classes and CSS variables -// the design system generates from design.json (var(--color-accent), var(--space-4), -// var(--radius-md), var(--font-display), …). Copilot reads this file to match the house -// style when it builds new UI. -// -// Schema: -// { "meta": {…}, "components": [ { "name", "note"?, "props"?, "root" } ] } -// node = element { "el": "div", "class"?, "attrs"?, "children"?: [node|string] } -// | component { "component": "Button", "props"?: { … } } // ref by name -// | string "literal" or "{prop}" // prop interpolation -// "{prop}" interpolates a prop into class / attr values / text; "{{" and "}}" are -// literal braces. SVG tags (svg/path/rect/…) render in the SVG namespace automatically. -// -// This is DESIGN INTENT for the canvas preview — not shipping code. To ship, port these -// patterns into the app using the port targets in design.json `authority`: most surfaces -// ship as native WinUI 3 / C# (port via github.com/microsoft/win-dev-skills), while the -// chat surface (ChatBubble, ChatComposer, ChatThread) ships via Reactor -// (github.com/microsoft/microsoft-ui-reactor). Don't ship this verbatim. -{ - "meta": { "version": 1, "updatedBy": "copilot", "note": "Windows Fluent UI for OpenClaw — migrated from components.jsx." }, - "components": [ +// components.jsonc — the component patterns your team has agreed on, as a structured +// element tree (JSON + comments). One component = one decision already made. +// +// These render live in the Design System + Prototype canvases via a pure JSON→DOM +// interpreter (no React, no build step). Style with the `ds-*` classes and CSS variables +// the design system generates from design.json (var(--color-accent), var(--space-4), +// var(--radius-md), var(--font-display), …). Copilot reads this file to match the house +// style when it builds new UI. +// +// Schema: +// { "meta": {…}, "components": [ { "name", "note"?, "props"?, "root" } ] } +// node = element { "el": "div", "class"?, "attrs"?, "children"?: [node|string] } +// | component { "component": "Button", "props"?: { … } } // ref by name +// | string "literal" or "{prop}" // prop interpolation +// "{prop}" interpolates a prop into class / attr values / text; "{{" and "}}" are +// literal braces. SVG tags (svg/path/rect/…) render in the SVG namespace automatically. +// +// This is DESIGN INTENT for the canvas preview — not shipping code. To ship, port these +// patterns into the app using the port targets in design.json `authority`: most surfaces +// ship as native WinUI 3 / C# (port via github.com/microsoft/win-dev-skills), while the +// chat surface (ChatBubble, ChatComposer, ChatThread) ships via Reactor +// (github.com/microsoft/microsoft-ui-reactor). Don't ship this verbatim. +{ + "meta": { "version": 1, "updatedBy": "copilot", "note": "Windows Fluent UI for OpenClaw — migrated from components.jsx." }, + "components": [ + { + "name": "Button", + "note": "Primary action. One per view — it's where the eye should land. Padding mirrors WinUI's default ButtonPadding (11,5,11,6 → 5px 11px 6px); MinWidth 100px keeps short labels (OK / Connect) from cramping and gives side-by-side buttons a consistent footprint.", + "props": { "children": "Save changes", "variant": "primary" }, + "root": { + "el": "button", + "class": "ds-btn ds-btn-{variant}", + "attrs": { "style": "padding:5px 11px 6px;min-width:100px" }, + "children": ["{children}"] + } + }, + { + "name": "Field", + "note": "Text input with a quiet label above it — the field is the subject. Input padding mirrors WinUI TextControlThemePadding (10,5,6,6 → 5px 6px 6px 10px) with MinHeight 32px, so a field lines up with a Button on the same row.", + "props": { "label": "Gateway address", "placeholder": "127.0.0.1:18789" }, + "root": { + "el": "label", + "class": "ds-field", + "children": [ + { "el": "span", "class": "ds-field-label", "children": ["{label}"] }, + { + "el": "input", + "class": "ds-input", + "attrs": { "placeholder": "{placeholder}", "style": "padding:5px 6px 6px 10px;min-height:32px" } + } + ] + } + }, + { + "name": "Card", + "note": "Content card. Flat by default — a 1px hairline border, not a drop shadow. Padding mirrors the WinUI Gallery card (16,12 → 12px 16px) with the `md` radius and a `line`-colored border. Never nest a card inside a card.", + "props": { "title": "Windows node", "body": "Paired · 6 capabilities exposed over MCP." }, + "root": { + "el": "article", + "class": "ds-card", + "attrs": { "style": "padding:12px 16px" }, + "children": [ + { "el": "h3", "class": "ds-card-title", "children": ["{title}"] }, + { "el": "p", "class": "ds-card-body", "children": ["{body}"] }, + { "el": "a", "class": "ds-link", "attrs": { "href": "#" }, "children": ["Open report →"] } + ] + } + }, + { + "name": "Badge", + "note": "Status pill. Uses semantic color tokens, never a raw hex.", + "props": { "children": "Connected", "tone": "positive" }, + "root": { "el": "span", "class": "ds-badge ds-badge-{tone}", "children": ["{children}"] } + }, + { + "name": "ComposerPicker", + "note": "Subtle inline picker — text label + chevron, no border until hover in the app. Used for the model and reasoning-effort selectors; reads as a quiet dropdown, not a button. (Preview shows the rest state.)", + "props": { "label": "Model" }, + "root": { + "el": "button", + "attrs": { + "type": "button", + "style": "display:inline-flex;align-items:center;gap:var(--space-1);height:32px;padding:0 var(--space-2);border-radius:var(--radius-sm);border:1px solid transparent;background:transparent;color:var(--color-muted);font-family:var(--font-body);font-size:13px;line-height:18px;cursor:pointer;white-space:nowrap" + }, + "children": [ + { "el": "span", "children": ["{label}"] }, + { + "el": "svg", + "attrs": { "width": "12", "height": "12", "viewBox": "0 0 24 24", "fill": "none", "aria-hidden": "true", "stroke": "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round" }, + "children": [ { "el": "path", "attrs": { "d": "M6 9l6 6 6-6" } } ] + } + ] + } + }, + { + "name": "ChatBubble", + "note": "Assistant chat bubble: a 36×36 avatar (subtle fill, hairline border) to the LEFT of a bubble on a subtle surface with a hairline border. The `bubble` radius (16px) is friendlier than the 8/12px control radii; padding 12/16. Footer reads timestamp · usage · Copy — the same used/context (pct%) string ChatUsageFormatter produces. Mirrors OpenClawChatTimeline. (User messages ride the accent on the right in-app; this component previews the assistant variant.)", + "props": { + "time": "2:14 PM", + "usage": "12.5K/200K (6%)", + "children": "Paired to the gateway — 6 Windows node capabilities are live." + }, + "root": { + "el": "div", + "attrs": { "style": "display:flex;align-items:flex-start;gap:var(--space-2)" }, + "children": [ + { + "el": "div", + "attrs": { "aria-hidden": "true", "style": "flex:0 0 auto;width:36px;height:36px;border-radius:var(--radius-pill);background:var(--color-surface);border:1px solid var(--color-line);color:var(--color-muted);display:inline-flex;align-items:center;justify-content:center" }, + "children": [ + { + "el": "svg", + "attrs": { "width": "18", "height": "18", "viewBox": "0 0 24 24", "fill": "currentColor", "aria-hidden": "true" }, + "children": [ { "el": "path", "attrs": { "d": "M12 2.5l1.7 5.1a4 4 0 0 0 2.7 2.7l5.1 1.7-5.1 1.7a4 4 0 0 0-2.7 2.7L12 21.5l-1.7-5.1a4 4 0 0 0-2.7-2.7L2.5 12l5.1-1.7a4 4 0 0 0 2.7-2.7L12 2.5z" } } ] + } + ] + }, + { + "el": "div", + "attrs": { "style": "display:flex;flex-direction:column;align-items:flex-start;gap:var(--space-1)" }, + "children": [ + { + "el": "div", + "attrs": { "style": "max-width:min(80%, 520px);padding:var(--space-3) var(--space-4);border-radius:var(--radius-bubble);font-family:var(--font-body);font-size:14px;line-height:20px;background:var(--color-surface);color:var(--color-ink);border:1px solid var(--color-line)" }, + "children": ["{children}"] + }, + { + "el": "div", + "attrs": { "style": "display:flex;align-items:center;gap:var(--space-2);padding:0 var(--space-2);font-family:var(--font-body);font-size:12px;line-height:16px;color:var(--color-muted)" }, + "children": [ + { "el": "span", "children": ["{time}"] }, + { "el": "span", "attrs": { "aria-hidden": "true" }, "children": ["·"] }, + { "el": "span", "children": ["{usage}"] }, + { + "el": "button", + "attrs": { "type": "button", "title": "Copy", "aria-label": "Copy message", "style": "display:inline-flex;align-items:center;justify-content:center;width:20px;height:20px;padding:0;border:none;border-radius:var(--radius-sm);background:transparent;color:var(--color-muted);opacity:0.65;cursor:pointer;transform:translateY(2px)" }, + "children": [ + { + "el": "svg", + "attrs": { "width": "14", "height": "14", "viewBox": "0 0 24 24", "fill": "none", "aria-hidden": "true", "stroke": "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round" }, + "children": [ + { "el": "rect", "attrs": { "x": "9", "y": "9", "width": "11", "height": "11", "rx": "2" } }, + { "el": "path", "attrs": { "d": "M6 15H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v1" } } + ] + } + ] + } + ] + } + ] + } + ] + } + }, + { + "name": "ChatComposer", + "note": "Chat input. A rounded surface (md radius / 8px) with a 1px hairline border wrapping a borderless, transparent, auto-growing textarea (min 56 → max 200) over a toolbar. Left cluster = Add, model picker, reasoning-effort picker; right cluster = mic + Send. Only Send spends the accent; every other control stays subtle so the toolbar reads as quiet chrome. (Preview shows the rest state.)", + "props": { + "placeholder": "Message OpenClaw…", + "model": "Claude Opus 4.8", + "effort": "High" + }, + "root": { + "el": "div", + "attrs": { "style": "display:flex;flex-direction:column;gap:var(--space-2);padding:var(--space-2);border-radius:var(--radius-md);background:var(--color-surface);border:1px solid var(--color-line)" }, + "children": [ + { + "el": "textarea", + "attrs": { "rows": "2", "placeholder": "{placeholder}", "style": "min-height:56px;max-height:200px;resize:none;border:none;outline:none;background:transparent;color:var(--color-ink);font-family:var(--font-body);font-size:14px;line-height:20px;padding:var(--space-2)" } + }, + { + "el": "div", + "attrs": { "style": "display:flex;align-items:center;justify-content:space-between;gap:var(--space-2)" }, + "children": [ + { + "el": "div", + "attrs": { "style": "display:flex;align-items:center;gap:var(--space-1);min-width:0" }, + "children": [ + { + "el": "button", + "attrs": { "type": "button", "title": "Add context", "aria-label": "Add context", "style": "display:inline-flex;align-items:center;justify-content:center;width:32px;height:32px;padding:0;border-radius:var(--radius-sm);cursor:pointer;border:1px solid transparent;background:transparent;color:var(--color-muted)" }, + "children": [ + { + "el": "svg", + "attrs": { "width": "18", "height": "18", "viewBox": "0 0 24 24", "fill": "none", "aria-hidden": "true", "stroke": "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round" }, + "children": [ { "el": "path", "attrs": { "d": "M12 5v14M5 12h14" } } ] + } + ] + }, + { "component": "ComposerPicker", "props": { "label": "{model}" } }, + { "component": "ComposerPicker", "props": { "label": "{effort}" } } + ] + }, + { + "el": "div", + "attrs": { "style": "display:flex;align-items:center;gap:var(--space-1)" }, + "children": [ + { + "el": "button", + "attrs": { "type": "button", "title": "Dictate", "aria-label": "Dictate", "style": "display:inline-flex;align-items:center;justify-content:center;width:32px;height:32px;padding:0;border-radius:var(--radius-sm);cursor:pointer;border:1px solid transparent;background:transparent;color:var(--color-muted)" }, + "children": [ + { + "el": "svg", + "attrs": { "width": "18", "height": "18", "viewBox": "0 0 24 24", "fill": "none", "aria-hidden": "true", "stroke": "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round" }, + "children": [ + { "el": "rect", "attrs": { "x": "9", "y": "3", "width": "6", "height": "11", "rx": "3" } }, + { "el": "path", "attrs": { "d": "M5 11a7 7 0 0 0 14 0M12 18v3" } } + ] + } + ] + }, + { + "el": "button", + "attrs": { "type": "button", "title": "Send", "aria-label": "Send", "style": "display:inline-flex;align-items:center;justify-content:center;width:32px;height:32px;padding:0;border-radius:var(--radius-sm);cursor:pointer;border:none;background:var(--color-accent);color:var(--color-accentInk)" }, + "children": [ + { + "el": "svg", + "attrs": { "width": "18", "height": "18", "viewBox": "0 0 24 24", "fill": "none", "aria-hidden": "true", "stroke": "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round" }, + "children": [ { "el": "path", "attrs": { "d": "M12 19V5M5 12l7-7 7 7" } } ] + } + ] + } + ] + } + ] + } + ] + } + }, + { + "name": "ChatThread", + "note": "A short thread: the \"does chat hang together\" check — a user turn (accent bubble on the right) and an assistant turn (with its Copy affordance + context-usage readout), then the composer.", + "root": { + "el": "div", + "attrs": { "style": "display:flex;flex-direction:column;gap:var(--space-3);max-width:560px" }, + "children": [ + { + "el": "div", + "attrs": { "style": "display:flex;justify-content:flex-end" }, + "children": [ + { + "el": "div", + "attrs": { "style": "display:flex;flex-direction:column;align-items:flex-end;gap:var(--space-1)" }, + "children": [ + { + "el": "div", + "attrs": { "style": "max-width:min(80%, 520px);padding:var(--space-3) var(--space-4);border-radius:var(--radius-bubble);font-family:var(--font-body);font-size:14px;line-height:20px;background:var(--color-accent);color:var(--color-accentInk);border:none" }, + "children": ["Can you list the Windows node capabilities?"] + }, + { + "el": "div", + "attrs": { "style": "display:flex;align-items:center;gap:var(--space-2);padding:0 var(--space-2);font-family:var(--font-body);font-size:12px;line-height:16px;color:var(--color-muted)" }, + "children": [ { "el": "span", "children": ["2:14 PM"] } ] + } + ] + } + ] + }, + { "component": "ChatBubble", "props": { "time": "2:14 PM", "usage": "12.5K/200K (6%)", "children": "Paired to the gateway — 6 capabilities are live, including system.run and clipboard access." } }, + { "component": "ChatComposer" } + ] + } + }, + { + "name": "ExampleScreen", + "note": "A representative screen composed from the pieces above — the \"does it hang together\" check for the whole system.", + "root": { + "el": "section", + "class": "ds-screen", + "children": [ + { + "el": "header", + "class": "ds-screen-head", + "children": [ + { + "el": "div", + "children": [ + { "el": "p", "class": "ds-eyebrow", "children": ["Connection"] }, + { "el": "h2", "class": "ds-screen-title", "children": ["OpenClaw Windows Hub"] } + ] + }, + { "component": "Badge", "props": { "tone": "positive", "children": "Connected" } } + ] + }, + { + "el": "div", + "class": "ds-grid", + "children": [ + { "component": "Card", "props": { "title": "Gateway", "body": "Paired to OpenClawGateway · last seen just now." } }, + { "component": "Card", "props": { "title": "Windows node", "body": "6 capabilities exposed over MCP." } } + ] + }, + { + "el": "div", + "class": "ds-row", + "children": [ + { "component": "Field", "props": { "label": "Gateway address", "placeholder": "127.0.0.1:18789" } }, + { "component": "Button", "props": { "children": "Connect" } } + ] + } + ] + } + }, + { + "name": "Toggle", + "note": "WinUI ToggleSwitch — a pill track with a knob. The static preview shows one state: pass the on defaults (accent track, knob right) or override for off (bordered track, muted knob, knob left). In a prototype, swap an on-instance and an off-instance with visibleWhen so a tap flips the visible switch. Track 40×20 mirrors the Fluent ToggleSwitch pill; only the accent is spent when on.", + "props": { "trackBg": "var(--color-accent)", "trackBorder": "var(--color-accent)", "knobBg": "var(--color-accentInk)", "knobJustify": "flex-end" }, + "root": { + "el": "div", + "attrs": { + "role": "switch", + "style": "display:inline-flex;align-items:center;justify-content:{knobJustify};width:40px;height:20px;padding:3px;border-radius:var(--radius-pill);background:{trackBg};border:1px solid {trackBorder};box-sizing:border-box;cursor:pointer" + }, + "children": [ + { "el": "div", "attrs": { "style": "width:12px;height:12px;border-radius:var(--radius-pill);background:{knobBg}" } } + ] + } + }, + { + "name": "Select", + "note": "WinUI ComboBox at rest — a bordered surface showing the selected value with a chevron. Padding mirrors TextControlThemePadding; 32px min-height so it lines up with a Field or Button on the same row. (Preview shows the closed state; open/flyout is out of scope for the static interpreter.)", + "props": { "value": "Read only" }, + "root": { + "el": "button", + "attrs": { + "type": "button", + "style": "display:inline-flex;align-items:center;justify-content:space-between;gap:var(--space-3);min-width:150px;height:32px;padding:5px 10px 6px;border-radius:var(--radius-sm);border:1px solid var(--color-line);background:var(--color-surface);color:var(--color-ink);font-family:var(--font-body);font-size:14px;line-height:20px;cursor:pointer;box-sizing:border-box" + }, + "children": [ + { "el": "span", "children": ["{value}"] }, + { + "el": "svg", + "attrs": { "width": "12", "height": "12", "viewBox": "0 0 24 24", "fill": "none", "aria-hidden": "true", "stroke": "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", "style": "color:var(--color-muted)" }, + "children": [ { "el": "path", "attrs": { "d": "M6 9l6 6 6-6" } } ] + } + ] + } + }, + { + "name": "RadioRow", + "note": "One option in a radio group — a ring + label. Defaults render the unselected look (muted ring, hollow); pass ring/dot accent for the selected option. In a prototype, put the selected and unselected instances behind visibleWhen keyed on a state value to make the group live.", + "props": { "label": "None (default)", "ring": "var(--color-muted)", "dot": "transparent" }, + "root": { + "el": "div", + "attrs": { "style": "display:inline-flex;align-items:center;gap:var(--space-3)" }, + "children": [ + { + "el": "div", + "attrs": { "style": "flex:0 0 auto;width:18px;height:18px;border-radius:var(--radius-pill);border:2px solid {ring};display:inline-flex;align-items:center;justify-content:center;box-sizing:border-box" }, + "children": [ { "el": "div", "attrs": { "style": "width:8px;height:8px;border-radius:var(--radius-pill);background:{dot}" } } ] + }, + { "el": "span", "attrs": { "style": "font-family:var(--font-body);font-size:14px;line-height:20px;color:var(--color-ink)" }, "children": ["{label}"] } + ] + } + }, + { + "name": "Slider", + "note": "WinUI Slider at rest — a label + value on one line over a track with a filled portion and a knob. `fill` is the filled width as a percentage (e.g. \"9%\"); the value string is shown in mono, matching the app's use of Cascadia for numbers and units. Static preview: the knob doesn't drag.", + "props": { "label": "Command timeout", "value": "30 sec", "fill": "9%" }, + "root": { + "el": "div", + "attrs": { "style": "display:flex;flex-direction:column;gap:var(--space-2);width:360px" }, + "children": [ + { + "el": "div", + "attrs": { "style": "display:flex;align-items:center;justify-content:space-between" }, + "children": [ + { "el": "span", "attrs": { "style": "font-family:var(--font-body);font-size:14px;line-height:20px;color:var(--color-ink)" }, "children": ["{label}"] }, + { "el": "span", "attrs": { "style": "font-family:var(--font-mono);font-size:13px;line-height:18px;color:var(--color-muted)" }, "children": ["{value}"] } + ] + }, + { + "el": "div", + "attrs": { "style": "position:relative;height:4px;border-radius:var(--radius-pill);background:var(--color-line)" }, + "children": [ + { "el": "div", "attrs": { "style": "position:absolute;left:0;top:0;height:4px;width:{fill};border-radius:var(--radius-pill);background:var(--color-accent)" } }, + { "el": "div", "attrs": { "style": "position:absolute;top:-6px;left:{fill};width:16px;height:16px;border-radius:var(--radius-pill);background:var(--color-accent);border:2px solid var(--color-surface);box-sizing:border-box;transform:translateX(-8px)" } } + ] + } + ] + } + }, + { + "name": "PresetCard", + "note": "A selectable preset tile — bold title + one-line description, as a bordered card-button. Defaults render the unselected look (1px line border); pass bw \"2px\" / bc accent for the selected tile. Used for one-click security presets — place three in a 3-column grid (each with \"grow\": true) so they share the row evenly and shrink to fit. Fills its container (width:100%).", + "props": { "title": "🛡 Recommended", "body": "Internet on. Read-only on Documents, Downloads, Desktop. Clipboard read.", "bw": "1px", "bc": "var(--color-line)" }, + "root": { + "el": "div", + "attrs": { "style": "display:flex;flex-direction:column;gap:var(--space-2);width:100%;min-height:130px;padding:18px 20px;border-radius:var(--radius-md);border:{bw} solid {bc};background:var(--color-surface);cursor:pointer;box-sizing:border-box" }, + "children": [ + { "el": "span", "attrs": { "style": "font-family:var(--font-body);font-size:16px;line-height:22px;font-weight:600;color:var(--color-ink)" }, "children": ["{title}"] }, + { "el": "span", "attrs": { "style": "font-family:var(--font-body);font-size:13px;line-height:18px;color:var(--color-muted)" }, "children": ["{body}"] } + ] + } + }, + { + "name": "NavItem", + "note": "One row in the HubWindow NavigationView pane — an icon glyph + label, at a fixed 248px width so the pane column stays even (the scene-graph component host is shrink-to-fit; layout nodes can't set width). Defaults render an unselected item; for the current page pass bg subtlePressed, bar accent, weight 600 to show selection (the 3px left bar is Fluent's NavigationViewItem selection indicator). App icons are real SVGs in the app; the prototype uses emoji stand-ins.", + "props": { "icon": "•", "label": "Item", "bg": "transparent", "bar": "transparent", "ink": "var(--color-ink)", "weight": "400" }, + "root": { + "el": "div", + "attrs": { "style": "position:relative;display:flex;align-items:center;gap:10px;width:248px;height:36px;padding:0 12px;border-radius:var(--radius-md);background:{bg};cursor:pointer;box-sizing:border-box" }, + "children": [ + { "el": "div", "attrs": { "style": "position:absolute;left:3px;top:8px;width:3px;height:20px;border-radius:2px;background:{bar}" } }, + { "el": "span", "attrs": { "style": "flex:0 0 auto;width:18px;text-align:center;font-size:15px" }, "children": ["{icon}"] }, + { "el": "span", "attrs": { "style": "font-family:var(--font-body);font-size:14px;line-height:20px;color:{ink};font-weight:{weight}" }, "children": ["{label}"] } + ] + } + }, + { + "name": "NavHeader", + "note": "A section label in the NavigationView pane (\"Gateway\", \"This Computer\") — Fluent's NavigationViewItemHeader. Caption weight, muted, with a little top space to separate groups.", + "props": { "label": "Section" }, + "root": { + "el": "div", + "attrs": { "style": "width:248px;padding:10px 12px 4px;font-family:var(--font-body);font-size:12px;line-height:16px;font-weight:600;color:var(--color-muted);box-sizing:border-box" }, + "children": ["{label}"] + } + }, + { + "name": "Divider", + "note": "A thin horizontal rule for the nav pane (NavigationViewItemSeparator). 1px line token; fixed width to match the pane column.", + "props": {}, + "root": { + "el": "div", + "attrs": { "style": "width:248px;height:1px;margin:4px 0;background:var(--color-line)" } + } + }, + { + "name": "SearchField", + "note": "The title-bar AutoSuggestBox at rest — a recessed rounded field with a find glyph and placeholder. Fixed 360px so it sits between the title and the status pill; the static preview doesn't focus or type.", + "props": { "placeholder": "Search… (Ctrl+E)" }, + "root": { + "el": "div", + "attrs": { "style": "display:inline-flex;align-items:center;gap:8px;width:360px;height:32px;padding:0 10px;border:1px solid var(--color-line);border-radius:var(--radius-md);background:var(--color-paper);box-sizing:border-box" }, + "children": [ + { "el": "span", "attrs": { "style": "font-family:var(--font-body);font-size:14px;line-height:20px;color:var(--color-muted)" }, "children": ["{placeholder}"] }, + { + "el": "svg", + "attrs": { "width": "14", "height": "14", "viewBox": "0 0 24 24", "fill": "none", "aria-hidden": "true", "stroke": "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", "style": "margin-left:auto;color:var(--color-muted)" }, + "children": [ + { "el": "circle", "attrs": { "cx": "11", "cy": "11", "r": "7" } }, + { "el": "path", "attrs": { "d": "M21 21l-4.3-4.3" } } + ] + } + ] + } + }, + { + "name": "StatusPill", + "note": "The title-bar connection status button — a dot + state text + chevron in a rounded pill. Defaults show the disconnected (muted dot) look; pass dot positive/warning for connected/connecting. Mirrors the StatusPillButton flyout trigger in HubWindow.", + "props": { "label": "Disconnected", "dot": "var(--color-muted)" }, + "root": { + "el": "div", + "attrs": { "style": "display:inline-flex;align-items:center;gap:7px;height:30px;padding:0 11px;border:1px solid var(--color-line);border-radius:var(--radius-pill);background:var(--color-surface);cursor:pointer;box-sizing:border-box" }, + "children": [ + { "el": "div", "attrs": { "style": "flex:0 0 auto;width:10px;height:10px;border-radius:var(--radius-pill);background:{dot}" } }, + { "el": "span", "attrs": { "style": "font-family:var(--font-body);font-size:13px;line-height:18px;color:var(--color-ink)" }, "children": ["{label}"] }, + { "el": "span", "attrs": { "style": "font-size:9px;color:var(--color-muted)" }, "children": ["\u25be"] } + ] + } + }, + { + "name": "LevelSlider", + "note": "The shared containment risk meter — a horizontal track with a tone-colored fill and knob plus three fixed stops (Locked down · Recommended · Unprotected). It's the primary Security-level control's visual and is reused inside the floating ContainmentRiskBar so the inline slider and the scrolled recap read identically. Presentational only: `fill` is the knob position as a percentage and `tone` is a state color token (positive / warning / critical) shared by fill and knob. Selection/interaction is wired on the scene-graph nodes that overlay or accompany it. Reuses the Slider visual language (neutral line track + tone fill + knob) so risk stays calm and native; risk reads by knob position and the labels, never by color alone.", + "props": { "fill": "50%", "tone": "positive" }, + "root": { + "el": "div", + "attrs": { "style": "display:flex;flex-direction:column;gap:6px;width:100%" }, + "children": [ + { + "el": "div", + "attrs": { "style": "position:relative;height:6px;border-radius:var(--radius-pill);background:var(--color-line)" }, + "children": [ + { "el": "div", "attrs": { "style": "position:absolute;left:0;top:0;height:6px;width:{fill};border-radius:var(--radius-pill);background:var(--color-{tone})" } }, + { "el": "div", "attrs": { "style": "position:absolute;top:-6px;left:{fill};width:18px;height:18px;border-radius:var(--radius-pill);background:var(--color-{tone});border:2px solid var(--color-surface);box-shadow:var(--shadow-sm);box-sizing:border-box;transform:translateX(-9px)" } } + ] + }, + { + "el": "div", + "attrs": { "style": "display:flex;justify-content:space-between;font-family:var(--font-body);font-size:12px;line-height:16px;color:var(--color-muted)" }, + "children": [ + { "el": "span", "children": ["Locked down"] }, + { "el": "span", "children": ["Recommended"] }, + { "el": "span", "children": ["Unprotected"] } + ] + } + ] + } + }, { - "name": "Button", - "note": "Primary action. One per view — it's where the eye should land. Padding mirrors WinUI's default ButtonPadding (11,5,11,6 → 5px 11px 6px); MinWidth 100px keeps short labels (OK / Connect) from cramping and gives side-by-side buttons a consistent footprint.", - "props": { "children": "Save changes", "variant": "primary" }, + "name": "LevelTicks", + "note": "A segmented containment risk meter drawn as one continuous, densely-spaced row of thin vertical ticks spanning the full width (Fluent volume/EQ language) that behaves like a slider. Left-to-right the ticks read Unprotected -> Recommended -> Locked down in equal thirds, so moving right increases protection. Presentational only: `active` selects the current third (u = Unprotected / r = Recommended / l = Locked down) via a data attribute the embedded