From 4884f3a814f6f7c761afecd5856b4cc00885b607 Mon Sep 17 00:00:00 2001 From: Karen Lai <7976322+karkarl@users.noreply.github.com> Date: Mon, 20 Jul 2026 15:28:48 -0700 Subject: [PATCH 1/7] Add Sandbox page prototype to design system Port the WinUI Sandbox settings page ("This Computer" -> Sandbox) into the colophon prototype format so it can be previewed and iterated as a click-through mockup. Adds the screen to prototypes.jsonc and the supporting components (settings controls + app-shell chrome) to components.jsonc. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .agents/design/components.jsonc | 159 ++++++++++++ .agents/design/prototypes.jsonc | 436 ++++++++++++++++++++++++++++++++ 2 files changed, 595 insertions(+) create mode 100644 .agents/design/prototypes.jsonc diff --git a/.agents/design/components.jsonc b/.agents/design/components.jsonc index 3b0d3fb55..2cef1b524 100644 --- a/.agents/design/components.jsonc +++ b/.agents/design/components.jsonc @@ -300,6 +300,165 @@ } ] } + }, + { + "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"] } + ] + } } ] } diff --git a/.agents/design/prototypes.jsonc b/.agents/design/prototypes.jsonc new file mode 100644 index 000000000..7e7b07faa --- /dev/null +++ b/.agents/design/prototypes.jsonc @@ -0,0 +1,436 @@ +// prototypes.jsonc — click-through prototypes built from this repo's design system. +// Framework-agnostic scene graph (not shipping code): layout primitives + references to +// components.jsonc by name + navigation as data. Copilot authors/patches this; review it in +// the Prototype canvas. Node kinds: layout | component | text | image | spacer. Every node +// may carry id, on (interactions), visibleWhen/hiddenWhen. Actions: navigate, back, +// setState, toggle, openModal, closeModal. This file is regenerated with stable key order +// on save — use "note" fields for durable annotations. +{ + "meta": { + "version": 1, + "updatedBy": "copilot", + "note": "OpenClaw Windows — the 'Sandbox' page under 'This Computer', ported from SandboxPage.xaml into a click-through prototype. The master toggle gates the detail sections; the security-level presets and the 'What gets contained' expander are live; 'Add folder' opens a modal. Static rest states stand in for combo flyouts and slider drag." + }, + + // Master sandbox on/off, the selected security preset, and the internet toggle. + "state": { + "sandbox": "on", + "preset": "balanced", + "net": "on" + }, + + "screens": [ + { + "id": "sandbox", + "name": "Sandbox · This Computer", + "device": "winui", + "fit": "screen", + "root": { + "id": "sandbox-root", + "layout": "stack", + "direction": "vertical", + "background": "paper", + "children": [ + + // ── App title bar (HubWindow custom titlebar): lobster + app name, search, status pill ── + { + "id": "app-titlebar", + "layout": "row", + "justify": "space-between", + "align": "center", + "padding": "3", + "background": "surface", + "children": [ + { + "id": "titlebar-left", + "layout": "row", + "gap": "3", + "align": "center", + "children": [ + { "id": "tb-nav-toggle", "text": "☰", "style": "body", "color": "muted" }, + { "id": "tb-icon", "text": "🦞", "style": "heading" }, + { "id": "tb-title", "text": "OpenClaw Windows Companion", "style": "small", "color": "muted" } + ] + }, + { + "id": "titlebar-right", + "layout": "row", + "gap": "3", + "align": "center", + "children": [ + { "id": "tb-search", "component": "SearchField" }, + { "id": "tb-status", "component": "StatusPill" }, + { "id": "tb-caption-reserve", "layout": "stack", "width": 140 } + ] + } + ] + }, + + // ── Shell body: navigation pane + page content ── + { + "id": "app-body", + "layout": "stack", + "direction": "horizontal", + "grow": true, + "children": [ + + // Navigation pane (HubWindow NavigationView). The upper region scrolls; + // the diagnostics footer (Notifications/Debug) stays pinned to the bottom. + { + "id": "nav-pane", + "layout": "stack", + "direction": "vertical", + "gap": "1", + "padding": "3", + "background": "surface", + "children": [ + { + "id": "nav-scroll", + "layout": "stack", + "direction": "vertical", + "grow": true, + "scroll": true, + "gap": "1", + "children": [ + { "id": "nav-chat", "component": "NavItem", "props": { "icon": "💬", "label": "Chat" } }, + { "id": "nav-div-1", "component": "Divider" }, + { "id": "nav-hd-gateway", "component": "NavHeader", "props": { "label": "Gateway" } }, + { "id": "nav-connection", "component": "NavItem", "props": { "icon": "🔌", "label": "Connection" } }, + { "id": "nav-sessions", "component": "NavItem", "props": { "icon": "🗂️", "label": "Sessions" } }, + { "id": "nav-skills", "component": "NavItem", "props": { "icon": "✨", "label": "Skills" } }, + { "id": "nav-channels", "component": "NavItem", "props": { "icon": "📣", "label": "Channels" } }, + { "id": "nav-instances", "component": "NavItem", "props": { "icon": "🖥️", "label": "Instances" } }, + { "id": "nav-cron", "component": "NavItem", "props": { "icon": "⏰", "label": "Cron" } }, + { "id": "nav-advanced", "component": "NavItem", "props": { "icon": "⚙️", "label": "Advanced" } }, + { "id": "nav-div-2", "component": "Divider" }, + { "id": "nav-hd-computer", "component": "NavHeader", "props": { "label": "This Computer" } }, + { "id": "nav-voice", "component": "NavItem", "props": { "icon": "🎙️", "label": "Voice & Audio" } }, + { "id": "nav-permissions", "component": "NavItem", "props": { "icon": "🔑", "label": "Permissions" } }, + { "id": "nav-sandbox", "component": "NavItem", "props": { "icon": "🧪", "label": "Sandbox", "bg": "var(--color-subtlePressed)", "bar": "var(--color-accent)", "weight": "600" } }, + { "id": "nav-settings", "component": "NavItem", "props": { "icon": "🔧", "label": "Settings" } } + ] + }, + { + "id": "nav-footer", + "layout": "stack", + "direction": "vertical", + "gap": "1", + "children": [ + { "id": "nav-div-3", "component": "Divider" }, + { "id": "nav-notifications", "component": "NavItem", "props": { "icon": "🔔", "label": "Notifications" } }, + { "id": "nav-debug", "component": "NavItem", "props": { "icon": "🐞", "label": "Debug" } } + ] + } + ] + }, + + // Page content — the Sandbox settings page, scrolls under the titlebar. + { + "id": "sandbox-content", + "layout": "stack", + "direction": "vertical", + "grow": true, + "scroll": true, + "gap": "5", + "padding": "6", + "background": "paper", + "children": [ + + // ── Status header: shield + title + subtitle, master toggle on the right ── + { + "id": "sandbox-status", + "layout": "row", + "justify": "space-between", + "align": "center", + "children": [ + { + "id": "sandbox-status-left", + "layout": "row", + "gap": "4", + "align": "center", + "children": [ + { "id": "sandbox-shield", "text": "🛡", "style": "title" }, + { + "id": "sandbox-heading", + "layout": "stack", + "direction": "vertical", + "gap": "1", + "children": [ + { "id": "sandbox-title", "text": "Node sandbox is on", "style": "title" }, + { "id": "sandbox-sub", "text": "Programs the agent runs on this PC are contained.", "style": "small", "color": "muted" } + ] + } + ] + }, + { "id": "sandbox-toggle-on", "component": "Toggle", "visibleWhen": { "sandbox": "on" }, "on": { "tap": { "setState": { "sandbox": "off" } } } }, + { "id": "sandbox-toggle-off", "component": "Toggle", "props": { "trackBg": "transparent", "trackBorder": "var(--color-muted)", "knobBg": "var(--color-muted)", "knobJustify": "flex-start" }, "visibleWhen": { "sandbox": "off" }, "on": { "tap": { "setState": { "sandbox": "on" } } } } + ] + }, + + // ── "What gets contained" — a collapsible scope note (tap the header) ── + { + "id": "scope-wrap", + "layout": "stack", + "direction": "vertical", + "gap": "3", + "padding": "4", + "background": "surface", + "radius": "md", + "children": [ + { + "id": "scope-head", + "layout": "row", + "justify": "space-between", + "align": "center", + "on": { "tap": { "toggle": "scopeOpen" } }, + "children": [ + { + "id": "scope-head-left", + "layout": "stack", + "direction": "vertical", + "gap": "1", + "children": [ + { "id": "scope-head-title", "text": "What gets contained", "style": "body" }, + { "id": "scope-head-sub", "text": "See which commands this page covers — and which need different controls.", "style": "caption", "color": "muted" } + ] + }, + { "id": "scope-chevron", "text": "⌄", "style": "heading", "color": "muted" } + ] + }, + { + "id": "scope-body", + "layout": "stack", + "direction": "vertical", + "gap": "3", + "visibleWhen": { "scopeOpen": true }, + "children": [ + { + "id": "scope-row-ok", + "layout": "row", + "gap": "3", + "align": "flex-start", + "children": [ + { "id": "scope-ok-icon", "text": "✓", "style": "body", "color": "positive" }, + { "id": "scope-ok-text", "text": "Commands the agent runs through the Windows node (system.run) are contained by the settings below.", "style": "small" } + ] + }, + { + "id": "scope-row-warn", + "layout": "row", + "gap": "3", + "align": "flex-start", + "children": [ + { "id": "scope-warn-icon", "text": "⚠", "style": "body", "color": "warning" }, + { "id": "scope-warn-text", "text": "Commands the agent runs directly on the gateway aren't. If the gateway is on this PC they may still reach your Windows files, clipboard, and network — check your gateway's configuration.", "style": "small", "color": "muted" } + ] + } + ] + } + ] + }, + + // ── Security level: one-click presets (the selected tile carries the accent) ── + { + "id": "preset-wrap", + "layout": "stack", + "direction": "vertical", + "gap": "2", + "padding": "5", + "background": "surface", + "radius": "md", + "border": "line", + "children": [ + { "id": "preset-title", "text": "Security level", "style": "heading" }, + { "id": "preset-sub", "text": "One click to set every control below. Custom folders stay as you set them.", "style": "small", "color": "muted" }, + { + "id": "preset-row", + "layout": "grid", + "columns": 3, + "gap": "3", + "children": [ + { "id": "preset-locked-sel", "component": "PresetCard", "grow": true, "props": { "title": "🔒 Locked Down", "body": "No internet, no clipboard, no standard user folders.", "bw": "2px", "bc": "var(--color-accent)" }, "visibleWhen": { "preset": "locked" }, "on": { "tap": { "setState": { "preset": "locked" } } } }, + { "id": "preset-locked", "component": "PresetCard", "grow": true, "props": { "title": "🔒 Locked Down", "body": "No internet, no clipboard, no standard user folders." }, "hiddenWhen": { "preset": "locked" }, "on": { "tap": { "setState": { "preset": "locked" } } } }, + + { "id": "preset-balanced-sel", "component": "PresetCard", "grow": true, "props": { "title": "🛡 Recommended", "body": "Internet on. Read-only on Documents, Downloads, Desktop. Clipboard read.", "bw": "2px", "bc": "var(--color-accent)" }, "visibleWhen": { "preset": "balanced" }, "on": { "tap": { "setState": { "preset": "balanced" } } } }, + { "id": "preset-balanced", "component": "PresetCard", "grow": true, "props": { "title": "🛡 Recommended", "body": "Internet on. Read-only on Documents, Downloads, Desktop. Clipboard read." }, "hiddenWhen": { "preset": "balanced" }, "on": { "tap": { "setState": { "preset": "balanced" } } } }, + + { "id": "preset-permissive-sel", "component": "PresetCard", "grow": true, "props": { "title": "⚠ Unprotected", "body": "Internet on. Read+write on Documents, Downloads, Desktop. Clipboard read+write.", "bw": "2px", "bc": "var(--color-accent)" }, "visibleWhen": { "preset": "permissive" }, "on": { "tap": { "setState": { "preset": "permissive" } } } }, + { "id": "preset-permissive", "component": "PresetCard", "grow": true, "props": { "title": "⚠ Unprotected", "body": "Internet on. Read+write on Documents, Downloads, Desktop. Clipboard read+write." }, "hiddenWhen": { "preset": "permissive" }, "on": { "tap": { "setState": { "preset": "permissive" } } } } + ] + } + ] + }, + + // Shown when the master toggle is off — the detail controls collapse away. + { "id": "sandbox-off-hint", "text": "Turn the sandbox on to configure containment.", "style": "body", "color": "muted", "visibleWhen": { "sandbox": "off" } }, + + // ── Detail sections — gated by the master toggle ── + { + "id": "sandbox-controls", + "layout": "stack", + "direction": "vertical", + "gap": "5", + "visibleWhen": { "sandbox": "on" }, + "children": [ + + // Files + { + "id": "files-wrap", + "layout": "stack", + "direction": "vertical", + "gap": "3", + "padding": "4", + "background": "surface", + "radius": "md", + "children": [ + { "id": "files-title", "text": "📁 Files", "style": "body" }, + { "id": "files-desc", "text": "By default the agent only has a temporary workspace folder it can read and write. Grant access to user folders below. SSH keys, browser profiles, and OpenClaw's own settings are always blocked.", "style": "small", "color": "muted" }, + { + "id": "files-grid", + "layout": "grid", + "columns": 2, + "gap": "3", + "align": "center", + "children": [ + { "id": "files-docs-label", "text": "Documents", "style": "body" }, + { "id": "files-docs", "component": "Select", "props": { "value": "Read only" } }, + { "id": "files-dl-label", "text": "Downloads", "style": "body" }, + { "id": "files-dl", "component": "Select", "props": { "value": "Read only" } }, + { "id": "files-desk-label", "text": "Desktop", "style": "body" }, + { "id": "files-desk", "component": "Select", "props": { "value": "Blocked" } } + ] + }, + { "id": "files-path-note", "text": "Tool directories on your PATH are also granted read-only, so git, node, python, and dotnet stay usable. Drive roots are never granted.", "style": "caption", "color": "muted" }, + { + "id": "custom-wrap", + "layout": "stack", + "direction": "vertical", + "gap": "2", + "padding": "3", + "background": "paper", + "radius": "sm", + "children": [ + { "id": "custom-title", "text": "Custom folders", "style": "body" }, + { "id": "custom-empty", "text": "No custom folders added.", "style": "small", "color": "muted" }, + { "id": "custom-add", "component": "Button", "props": { "children": "Add folder", "variant": "secondary" }, "on": { "tap": { "openModal": "add-folder" } } } + ] + } + ] + }, + + // Clipboard + { + "id": "clip-wrap", + "layout": "stack", + "direction": "vertical", + "gap": "3", + "padding": "4", + "background": "surface", + "radius": "md", + "children": [ + { "id": "clip-title", "text": "📋 Clipboard", "style": "body" }, + { "id": "clip-desc", "text": "Clipboard often contains passwords, tokens, and private text. Read access may leak this to the agent (and over the internet, if enabled).", "style": "small", "color": "muted" }, + { "id": "clip-none", "component": "RadioRow", "props": { "label": "None (default)", "ring": "var(--color-accent)", "dot": "var(--color-accent)" } }, + { "id": "clip-read", "component": "RadioRow", "props": { "label": "Read — agent can see what you copied" } }, + { "id": "clip-write", "component": "RadioRow", "props": { "label": "Write — agent can replace your clipboard (cannot read it)" } }, + { "id": "clip-both", "component": "RadioRow", "props": { "label": "Read & write" } } + ] + }, + + // Network + { + "id": "net-wrap", + "layout": "stack", + "direction": "vertical", + "gap": "3", + "padding": "4", + "background": "surface", + "radius": "md", + "children": [ + { "id": "net-title", "text": "📡 Network", "style": "body" }, + { + "id": "net-row", + "layout": "row", + "justify": "space-between", + "align": "center", + "children": [ + { + "id": "net-left", + "layout": "stack", + "direction": "vertical", + "gap": "1", + "children": [ + { "id": "net-label", "text": "Allow internet", "style": "body" }, + { "id": "net-cap-on", "text": "On — agent can connect to public internet endpoints.", "style": "small", "color": "muted", "visibleWhen": { "net": "on" } }, + { "id": "net-cap-off", "text": "Off — agent cannot reach the internet.", "style": "small", "color": "muted", "visibleWhen": { "net": "off" } } + ] + }, + { "id": "net-toggle-on", "component": "Toggle", "visibleWhen": { "net": "on" }, "on": { "tap": { "setState": { "net": "off" } } } }, + { "id": "net-toggle-off", "component": "Toggle", "props": { "trackBg": "transparent", "trackBorder": "var(--color-muted)", "knobBg": "var(--color-muted)", "knobJustify": "flex-start" }, "visibleWhen": { "net": "off" }, "on": { "tap": { "setState": { "net": "on" } } } } + ] + }, + { "id": "net-note", "text": "If file or clipboard read access is enabled, the agent may send that data over the internet.", "style": "caption", "color": "muted" } + ] + }, + + // Limits + { + "id": "lim-wrap", + "layout": "stack", + "direction": "vertical", + "gap": "3", + "padding": "4", + "background": "surface", + "radius": "md", + "children": [ + { "id": "lim-title", "text": "⏱ Limits", "style": "body" }, + { "id": "lim-slider", "component": "Slider", "props": { "label": "Command timeout", "value": "30 sec", "fill": "9%" } }, + { "id": "lim-out-label", "text": "Maximum captured output (truncates beyond this; does not limit disk, memory, or CPU):", "style": "small", "color": "muted" }, + { "id": "lim-out", "component": "Select", "props": { "value": "4 MiB (default)" } } + ] + } + + ] + } + ] + } + ] + } + ] + }, + + "modals": [ + { + "id": "add-folder", + "root": { + "id": "af-root", + "layout": "stack", + "direction": "vertical", + "gap": "3", + "padding": "5", + "background": "surface", + "radius": "lg", + "children": [ + { "id": "af-title", "text": "Add a folder", "style": "heading" }, + { "id": "af-body", "text": "Choose a folder to grant the agent access to. Custom grants are added on top of the row rules above; you pick Blocked, Read only, or Read & write per folder.", "style": "body", "color": "muted" }, + { + "id": "af-actions", + "layout": "row", + "gap": "2", + "justify": "flex-end", + "children": [ + { "id": "af-cancel", "component": "Button", "props": { "children": "Cancel", "variant": "secondary" }, "on": { "tap": { "closeModal": true } } }, + { "id": "af-pick", "component": "Button", "props": { "children": "Choose folder…", "variant": "primary" }, "on": { "tap": { "closeModal": true } } } + ] + } + ] + } + } + ] + } + ], + + "flows": [ + { "id": "sandbox", "name": "Sandbox settings", "start": "sandbox" } + ] +} From a5e3241f145084bc64de2584788cd4a3f15491d8 Mon Sep 17 00:00:00 2001 From: Karen Lai <7976322+karkarl@users.noreply.github.com> Date: Mon, 20 Jul 2026 19:56:30 -0700 Subject: [PATCH 2/7] Refine containment risk bar: acrylic surface, slim pill, overhang, sizing Improve the floating ContainmentRiskBar recap on the sandbox-riskbar prototype screen: - Strengthen the acrylic (92% surface tint + blur(40px) saturate(1.6)) so page content reads legibly beneath it instead of plain transparency. - Slim the level pill to match the header row's line height (drop fixed 30px height, 8px dot). - Extend the bar 12px past the cards on the left and right via a widened width and negative side margins. - Tune vertical padding so the acrylic extends below the tick meter. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .agents/design/components.jsonc | 1391 +++++++++++++++++++++++++++++++ .agents/design/prototypes.jsonc | 1118 ++++++++++++++++++++++++- 2 files changed, 2507 insertions(+), 2 deletions(-) diff --git a/.agents/design/components.jsonc b/.agents/design/components.jsonc index 2cef1b524..3b4d00c4e 100644 --- a/.agents/design/components.jsonc +++ b/.agents/design/components.jsonc @@ -459,6 +459,1397 @@ { "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": "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