Compare commits

..
Author SHA1 Message Date
Peter Steinberger 548ead9984 chore: drop old control ui assets (#1745) (thanks @EnzeD) 2026-01-25 10:04:37 +00:00
Peter Steinberger 6be4e2c0cc fix: refresh control ui assets (#1745) (thanks @EnzeD) 2026-01-25 10:04:20 +00:00
EnzeD fa3ec47b16 UI: refresh design system with new color palette and icons
- Replace orange accent (#f59f4a) with signature red (#ff4d4d)
- Switch from IBM Plex/Unbounded/Work Sans to Inter/JetBrains Mono
- Replace emoji icons with Lucide-style SVG icons throughout
- Add comprehensive CSS design tokens (colors, borders, semantic states)
- Update tool-display.json to use icon names instead of emoji
- Rebuild control-ui dist bundle
2026-01-25 09:47:22 +00:00
34 changed files with 4975 additions and 7164 deletions

No files matched your search

+1 -1
View File
@@ -9,6 +9,7 @@ Docs: https://docs.clawd.bot
- Venius (Venice AI): highlight provider guide + cross-links + expanded guidance. https://docs.clawd.bot/providers/venice - Venius (Venice AI): highlight provider guide + cross-links + expanded guidance. https://docs.clawd.bot/providers/venice
### Changes ### Changes
- Control UI: refresh design, typography, and iconography; ship local Clawdbot logo asset. (#1745) Thanks @EnzeD.
- TTS: add Edge TTS provider fallback, defaulting to keyless Edge with MP3 retry on format failures. (#1668) Thanks @steipete. https://docs.clawd.bot/tts - TTS: add Edge TTS provider fallback, defaulting to keyless Edge with MP3 retry on format failures. (#1668) Thanks @steipete. https://docs.clawd.bot/tts
- Web search: add Brave freshness filter parameter for time-scoped results. (#1688) Thanks @JonUleis. https://docs.clawd.bot/tools/web - Web search: add Brave freshness filter parameter for time-scoped results. (#1688) Thanks @JonUleis. https://docs.clawd.bot/tools/web
- TTS: add auto mode enum (off/always/inbound/tagged) with per-session `/tts` override. (#1667) Thanks @sebslight. https://docs.clawd.bot/tts - TTS: add auto mode enum (off/always/inbound/tagged) with per-session `/tts` override. (#1667) Thanks @sebslight. https://docs.clawd.bot/tts
@@ -28,7 +29,6 @@ Docs: https://docs.clawd.bot
- Web UI: hide internal `message_id` hints in chat bubbles. - Web UI: hide internal `message_id` hints in chat bubbles.
- Web UI: show Stop button during active runs, swap back to New session when idle. (#1664) Thanks @ndbroadbent. - Web UI: show Stop button during active runs, swap back to New session when idle. (#1664) Thanks @ndbroadbent.
- Web UI: clear stale disconnect banners on reconnect; allow form saves with unsupported schema paths but block missing schema. (#1707) Thanks @Glucksberg. - Web UI: clear stale disconnect banners on reconnect; allow form saves with unsupported schema paths but block missing schema. (#1707) Thanks @Glucksberg.
- Auto-reply: don't treat `/models` as a `/model` directive. (#1753) Thanks @uos-status.
- Heartbeat: normalize target identifiers for consistent routing. - Heartbeat: normalize target identifiers for consistent routing.
- TUI: reload history after gateway reconnect to restore session state. (#1663) - TUI: reload history after gateway reconnect to restore session state. (#1663)
- Telegram: use wrapped fetch for long-polling on Node to normalize AbortSignal handling. (#1639) - Telegram: use wrapped fetch for long-polling on Node to normalize AbortSignal handling. (#1639)
-1
View File
@@ -1 +0,0 @@
@import"https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500&family=Unbounded:wght@400;500;600&family=Work+Sans:wght@400;500;600;700&display=swap";:root{--bg: #0a0f14;--bg-accent: #111826;--bg-grad-1: #162031;--bg-grad-2: #1f2a22;--bg-overlay: rgba(255, 255, 255, .05);--bg-glow: rgba(245, 159, 74, .12);--panel: rgba(14, 20, 30, .88);--panel-strong: rgba(18, 26, 38, .96);--chrome: rgba(9, 14, 20, .72);--chrome-strong: rgba(9, 14, 20, .86);--text: rgba(244, 246, 251, .96);--chat-text: rgba(231, 237, 244, .92);--muted: rgba(156, 169, 189, .72);--border: rgba(255, 255, 255, .09);--border-strong: rgba(255, 255, 255, .16);--accent: #f59f4a;--accent-2: #34c7b7;--ok: #2bd97f;--warn: #f2c94c;--danger: #ff6b6b;--focus: rgba(245, 159, 74, .35);--grid-line: rgba(255, 255, 255, .04);--theme-switch-x: 50%;--theme-switch-y: 50%;--mono: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--font-body: "Work Sans", system-ui, sans-serif;--font-display: "Unbounded", "Times New Roman", serif;color-scheme:dark}:root[data-theme=light]{--bg: #f5f1ea;--bg-accent: #ffffff;--bg-grad-1: #f1e6d6;--bg-grad-2: #e5eef4;--bg-overlay: rgba(28, 32, 46, .05);--bg-glow: rgba(52, 199, 183, .14);--panel: rgba(255, 255, 255, .9);--panel-strong: rgba(255, 255, 255, .97);--chrome: rgba(255, 255, 255, .75);--chrome-strong: rgba(255, 255, 255, .88);--text: rgba(27, 36, 50, .98);--chat-text: rgba(36, 48, 66, .9);--muted: rgba(80, 94, 114, .7);--border: rgba(18, 24, 40, .12);--border-strong: rgba(18, 24, 40, .2);--accent: #e28a3f;--accent-2: #1ba99d;--ok: #1aa86c;--warn: #b3771c;--danger: #d44848;--focus: rgba(226, 138, 63, .35);--grid-line: rgba(18, 24, 40, .06);color-scheme:light}*{box-sizing:border-box}html,body{height:100%}body{margin:0;font:15px/1.5 var(--font-body);background:radial-gradient(1200px 900px at 15% -10%,var(--bg-grad-1) 0%,transparent 55%) fixed,radial-gradient(900px 700px at 80% 10%,var(--bg-grad-2) 0%,transparent 60%) fixed,linear-gradient(160deg,var(--bg) 0%,var(--bg-accent) 100%) fixed;color:var(--text)}body:before{content:"";position:fixed;inset:0;background:linear-gradient(140deg,var(--bg-overlay) 0%,rgba(255,255,255,0) 40%),radial-gradient(620px 420px at 75% 75%,var(--bg-glow),transparent 60%);pointer-events:none;z-index:0}@keyframes theme-circle-transition{0%{clip-path:circle(0% at var(--theme-switch-x, 50%) var(--theme-switch-y, 50%))}to{clip-path:circle(150% at var(--theme-switch-x, 50%) var(--theme-switch-y, 50%))}}html.theme-transition{view-transition-name:theme}html.theme-transition::view-transition-old(theme){mix-blend-mode:normal;animation:none;z-index:1}html.theme-transition::view-transition-new(theme){mix-blend-mode:normal;z-index:2;animation:theme-circle-transition .45s ease-out forwards}@media(prefers-reduced-motion:reduce){html.theme-transition::view-transition-old(theme),html.theme-transition::view-transition-new(theme){animation:none!important}}clawdbot-app{display:block;position:relative;z-index:1;min-height:100vh}a{color:inherit}button,input,textarea,select{font:inherit;color:inherit}@keyframes rise{0%{opacity:0;transform:translateY(6px)}to{opacity:1;transform:translateY(0)}}@keyframes dashboard-enter{0%{opacity:0;transform:translateY(12px)}to{opacity:1;transform:translateY(0)}}.shell{--shell-pad: 16px;--shell-gap: 16px;--shell-nav-width: 220px;--shell-topbar-height: 56px;--shell-focus-duration: .22s;--shell-focus-ease: cubic-bezier(.2, .85, .25, 1);height:100vh;display:grid;grid-template-columns:var(--shell-nav-width) minmax(0,1fr);grid-template-rows:var(--shell-topbar-height) 1fr;grid-template-areas:"topbar topbar" "nav content";gap:0;animation:dashboard-enter .6s ease-out;transition:grid-template-columns var(--shell-focus-duration) var(--shell-focus-ease);overflow:hidden}@supports (height: 100dvh){.shell{height:100dvh}}.shell--chat{min-height:100vh;height:100vh;overflow:hidden}@supports (height: 100dvh){.shell--chat{height:100dvh}}.shell--nav-collapsed,.shell--chat-focus{grid-template-columns:0px minmax(0,1fr)}.shell--onboarding{grid-template-rows:0 1fr}.shell--onboarding .topbar{display:none}.shell--onboarding .content{padding-top:0}.shell--chat-focus .content{padding-top:0;gap:0}.topbar{grid-area:topbar;position:sticky;top:0;z-index:40;display:flex;justify-content:space-between;align-items:center;gap:16px;padding:0 20px;height:var(--shell-topbar-height);border-bottom:1px solid var(--border);background:var(--panel);-webkit-backdrop-filter:blur(18px);backdrop-filter:blur(18px)}.topbar-left{display:flex;align-items:center;gap:12px}.topbar .nav-collapse-toggle{width:44px;height:44px;margin-bottom:0}.topbar .nav-collapse-toggle__icon{font-size:22px}.brand{display:flex;flex-direction:column;gap:2px}.brand-title{font-family:var(--font-display);font-size:16px;letter-spacing:1px;text-transform:uppercase;font-weight:600;line-height:1.1}.brand-sub{font-size:10px;color:var(--muted);letter-spacing:.8px;text-transform:uppercase;line-heightLine truncated
File diff suppressed because it is too large. Load diff
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"index-CJVJFuAg.js","sources":["../../../node_modules/.pnpm/@lit+reactive-element@2.1.2/node_modules/@lit/reactive-element/css-tag.js","../../../node_modules/.pnpm/@lit+reactive-element@2.1.2/node_modules/@lit/reactive-element/reactive-element.js","../../../node_modules/.pnpm/lit-html@3.3.2/node_modules/lit-html/lit-html.js","../../../node_modules/.pnpm/lit-element@4.2.2/node_modules/lit-element/lit-element.js","../../../node_modules/.pnpm/@lit+reactive-element@2.1.2/node_modules/@lit/reactive-element/decorators/custom-element.js","../../../node_modules/.pnpm/@lit+reactive-element@2.1.2/node_modules/@lit/reactive-element/decorators/property.js","../../../node_modules/.pnpm/@lit+reactive-element@2.1.2/node_modules/@lit/reactive-element/decorators/state.js","../../../ui/src/ui/assistant-identity.ts","../../../ui/src/ui/storage.ts","../../../src/sessions/session-key-utils.ts","../../../ui/src/ui/navigation.ts","../../../ui/src/ui/icons.ts","../../../src/shared/text/reasoning-tags.ts","../../../ui/src/ui/format.ts","../../../ui/src/ui/chat/message-extract.ts","../../../ui/src/ui/uuid.ts","../../../ui/src/ui/controllers/chat.ts","../../../ui/src/ui/controllers/sessions.ts","../../../ui/src/ui/app-tool-stream.ts","../../../ui/src/ui/app-scroll.ts","../../../ui/src/ui/controllers/config/form-utils.ts","../../../ui/src/ui/controllers/config.ts","../../../ui/src/ui/controllers/cron.ts","../../../ui/src/ui/controllers/channels.ts","../../../ui/src/ui/controllers/debug.ts","../../../ui/src/ui/controllers/logs.ts","../../../node_modules/.pnpm/@noble+ed25519@3.0.0/node_modules/@noble/ed25519/index.js","../../../ui/src/ui/device-identity.ts","../../../ui/src/ui/device-auth.ts","../../../ui/src/ui/controllers/devices.ts","../../../ui/src/ui/controllers/nodes.ts","../../../ui/src/ui/controllers/exec-approvals.ts","../../../ui/src/ui/controllers/presence.ts","../../../ui/src/ui/controllers/skills.ts","../../../ui/src/ui/theme.ts","../../../ui/src/ui/theme-transition.ts","../../../ui/src/ui/app-polling.ts","../../../ui/src/ui/app-settings.ts","../../../ui/src/ui/app-chat.ts","../../../node_modules/.pnpm/lit-html@3.3.2/node_modules/lit-html/directive.js","../../../node_modules/.pnpm/lit-html@3.3.2/node_modules/lit-html/directive-helpers.js","../../../node_modules/.pnpm/lit-html@3.3.2/node_modules/lit-html/directives/repeat.js","../../../ui/src/ui/chat/message-normalizer.ts","../../../node_modules/.pnpm/lit-html@3.3.2/node_modules/lit-html/directives/unsafe-html.js","../../../node_modules/.pnpm/dompurify@3.3.1/node_modules/dompurify/dist/purify.es.mjs","../../../node_modules/.pnpm/marked@17.0.1/node_modules/marked/lib/marked.esm.js","../../../ui/src/ui/markdown.ts","../../../ui/src/ui/chat/copy-as-markdown.ts","../../../ui/src/ui/tool-display.ts","../../../ui/src/ui/chat/constants.ts","../../../ui/src/ui/chat/tool-helpers.ts","../../../ui/src/ui/chat/tool-cards.ts","../../../ui/src/ui/chat/grouped-render.ts","../../../ui/src/ui/views/markdown-sidebar.ts","../../../ui/src/ui/components/resizable-divider.ts","../../../ui/src/ui/views/chat.ts","../../../ui/src/ui/views/config-form.shared.ts","../../../ui/src/ui/views/config-form.node.ts","../../../ui/src/ui/views/config-form.render.ts","../../../ui/src/ui/views/config-form.analyze.ts","../../../ui/src/ui/views/config.ts","../../../ui/src/ui/views/channels.shared.ts","../../../ui/src/ui/views/channels.config.ts","../../../ui/src/ui/views/channels.discord.ts","../../../ui/src/ui/views/channels.googlechat.ts","../../../ui/src/ui/views/channels.imessage.ts","../../../ui/src/ui/views/channels.nostr-profile-form.ts","../../../ui/src/ui/views/channels.nostr.ts","../../../ui/src/ui/views/channels.signal.ts","../../../ui/src/ui/views/channels.slack.ts","../../../ui/src/ui/views/channels.telegram.ts","../../../ui/src/ui/views/channels.whatsapp.ts","../../../ui/src/ui/views/channels.ts","../../../ui/src/ui/presenter.ts","../../../ui/src/ui/views/cron.ts","../../../ui/src/ui/views/debug.ts","../../../ui/src/ui/views/instances.ts","../../../ui/src/ui/views/logs.ts","../../../ui/src/ui/views/nodes.ts","../../../ui/src/ui/views/overview.ts","../../../ui/src/ui/views/sessions.ts","../../../ui/src/ui/views/exec-approval.ts","../../../ui/src/ui/views/skills.ts","../../../ui/src/ui/app-render.helpers.ts","../../../ui/src/ui/app-render.ts","../../../ui/src/ui/app-defaults.ts","../../../ui/src/ui/controllers/agents.ts","../../../src/gateway/protocol/client-info.ts","../../../src/gateway/device-auth.ts","../../../ui/src/ui/gateway.ts","../../../ui/src/ui/controllers/exec-approval.ts","../../../ui/src/ui/controllers/assistant-identity.ts","../../../ui/src/ui/app-gateway.ts","../../../ui/src/ui/app-lifecycle.ts","../../../ui/src/ui/app-channels.ts","../../../ui/src/ui/app.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2019 Google LLC\n * SPDX-License-Identifier: BSD-3-Clause\n */\nconst t=globalThis,e=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow)&&\"adoptedStyLine truncated
File diff suppressed because it is too large. Load diff
-1
View File
@@ -1 +0,0 @@
{"version":3,"file":"index-DsXRcnEw.js","sources":["../../../node_modules/.pnpm/@lit+reactive-element@2.1.2/node_modules/@lit/reactive-element/css-tag.js","../../../node_modules/.pnpm/@lit+reactive-element@2.1.2/node_modules/@lit/reactive-element/reactive-element.js","../../../node_modules/.pnpm/lit-html@3.3.2/node_modules/lit-html/lit-html.js","../../../node_modules/.pnpm/lit-element@4.2.2/node_modules/lit-element/lit-element.js","../../../node_modules/.pnpm/@lit+reactive-element@2.1.2/node_modules/@lit/reactive-element/decorators/custom-element.js","../../../node_modules/.pnpm/@lit+reactive-element@2.1.2/node_modules/@lit/reactive-element/decorators/property.js","../../../node_modules/.pnpm/@lit+reactive-element@2.1.2/node_modules/@lit/reactive-element/decorators/state.js","../../../ui/src/ui/assistant-identity.ts","../../../ui/src/ui/storage.ts","../../../src/sessions/session-key-utils.ts","../../../ui/src/ui/navigation.ts","../../../ui/src/ui/format.ts","../../../ui/src/ui/chat/message-extract.ts","../../../ui/src/ui/uuid.ts","../../../ui/src/ui/controllers/chat.ts","../../../ui/src/ui/controllers/sessions.ts","../../../ui/src/ui/app-tool-stream.ts","../../../ui/src/ui/app-scroll.ts","../../../ui/src/ui/controllers/config/form-utils.ts","../../../ui/src/ui/controllers/config.ts","../../../ui/src/ui/controllers/cron.ts","../../../ui/src/ui/controllers/channels.ts","../../../ui/src/ui/controllers/debug.ts","../../../ui/src/ui/controllers/logs.ts","../../../node_modules/.pnpm/@noble+ed25519@3.0.0/node_modules/@noble/ed25519/index.js","../../../ui/src/ui/device-identity.ts","../../../ui/src/ui/device-auth.ts","../../../ui/src/ui/controllers/devices.ts","../../../ui/src/ui/controllers/nodes.ts","../../../ui/src/ui/controllers/exec-approvals.ts","../../../ui/src/ui/controllers/presence.ts","../../../ui/src/ui/controllers/skills.ts","../../../ui/src/ui/theme.ts","../../../ui/src/ui/theme-transition.ts","../../../ui/src/ui/app-polling.ts","../../../ui/src/ui/app-settings.ts","../../../ui/src/ui/app-chat.ts","../../../node_modules/.pnpm/lit-html@3.3.2/node_modules/lit-html/directive.js","../../../node_modules/.pnpm/lit-html@3.3.2/node_modules/lit-html/directive-helpers.js","../../../node_modules/.pnpm/lit-html@3.3.2/node_modules/lit-html/directives/repeat.js","../../../ui/src/ui/chat/message-normalizer.ts","../../../node_modules/.pnpm/lit-html@3.3.2/node_modules/lit-html/directives/unsafe-html.js","../../../node_modules/.pnpm/dompurify@3.3.1/node_modules/dompurify/dist/purify.es.mjs","../../../node_modules/.pnpm/marked@17.0.1/node_modules/marked/lib/marked.esm.js","../../../ui/src/ui/markdown.ts","../../../ui/src/ui/icons.ts","../../../ui/src/ui/chat/copy-as-markdown.ts","../../../ui/src/ui/tool-display.ts","../../../ui/src/ui/chat/constants.ts","../../../ui/src/ui/chat/tool-helpers.ts","../../../ui/src/ui/chat/tool-cards.ts","../../../ui/src/ui/chat/grouped-render.ts","../../../ui/src/ui/views/markdown-sidebar.ts","../../../ui/src/ui/components/resizable-divider.ts","../../../ui/src/ui/views/chat.ts","../../../ui/src/ui/views/config-form.shared.ts","../../../ui/src/ui/views/config-form.node.ts","../../../ui/src/ui/views/config-form.render.ts","../../../ui/src/ui/views/config-form.analyze.ts","../../../ui/src/ui/views/config.ts","../../../ui/src/ui/views/channels.shared.ts","../../../ui/src/ui/views/channels.config.ts","../../../ui/src/ui/views/channels.discord.ts","../../../ui/src/ui/views/channels.imessage.ts","../../../ui/src/ui/views/channels.nostr-profile-form.ts","../../../ui/src/ui/views/channels.nostr.ts","../../../ui/src/ui/views/channels.signal.ts","../../../ui/src/ui/views/channels.slack.ts","../../../ui/src/ui/views/channels.telegram.ts","../../../ui/src/ui/views/channels.whatsapp.ts","../../../ui/src/ui/views/channels.ts","../../../ui/src/ui/presenter.ts","../../../ui/src/ui/views/cron.ts","../../../ui/src/ui/views/debug.ts","../../../ui/src/ui/views/instances.ts","../../../ui/src/ui/views/logs.ts","../../../ui/src/ui/views/nodes.ts","../../../ui/src/ui/views/overview.ts","../../../ui/src/ui/views/sessions.ts","../../../ui/src/ui/views/exec-approval.ts","../../../ui/src/ui/views/skills.ts","../../../ui/src/ui/app-render.helpers.ts","../../../ui/src/ui/app-render.ts","../../../ui/src/ui/app-defaults.ts","../../../ui/src/ui/controllers/agents.ts","../../../src/gateway/protocol/client-info.ts","../../../src/gateway/device-auth.ts","../../../ui/src/ui/gateway.ts","../../../ui/src/ui/controllers/exec-approval.ts","../../../ui/src/ui/controllers/assistant-identity.ts","../../../ui/src/ui/app-gateway.ts","../../../ui/src/ui/app-lifecycle.ts","../../../ui/src/ui/app-channels.ts","../../../ui/src/ui/app.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2019 Google LLC\n * SPDX-License-Identifier: BSD-3-Clause\n */\nconst t=globalThis,e=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow)&&\"adoptedStyleSheets\"in Document.prototype&&\"replace\"in CSSStyleSheet.prototype,s=Symbol(),o=new WeakMapLine truncated
File diff suppressed because it is too large. Load diff
-1
View File
@@ -1 +0,0 @@
{"version":3,"file":"index-bYQnHP3a.js","sources":["../../../node_modules/.pnpm/@lit+reactive-element@2.1.2/node_modules/@lit/reactive-element/css-tag.js","../../../node_modules/.pnpm/@lit+reactive-element@2.1.2/node_modules/@lit/reactive-element/reactive-element.js","../../../node_modules/.pnpm/lit-html@3.3.2/node_modules/lit-html/lit-html.js","../../../node_modules/.pnpm/lit-element@4.2.2/node_modules/lit-element/lit-element.js","../../../node_modules/.pnpm/@lit+reactive-element@2.1.2/node_modules/@lit/reactive-element/decorators/custom-element.js","../../../node_modules/.pnpm/@lit+reactive-element@2.1.2/node_modules/@lit/reactive-element/decorators/property.js","../../../node_modules/.pnpm/@lit+reactive-element@2.1.2/node_modules/@lit/reactive-element/decorators/state.js","../../../ui/src/ui/assistant-identity.ts","../../../ui/src/ui/storage.ts","../../../src/sessions/session-key-utils.ts","../../../ui/src/ui/navigation.ts","../../../ui/src/ui/format.ts","../../../ui/src/ui/chat/message-extract.ts","../../../ui/src/ui/uuid.ts","../../../ui/src/ui/controllers/chat.ts","../../../ui/src/ui/controllers/sessions.ts","../../../ui/src/ui/app-tool-stream.ts","../../../ui/src/ui/app-scroll.ts","../../../ui/src/ui/controllers/config/form-utils.ts","../../../ui/src/ui/controllers/config.ts","../../../ui/src/ui/controllers/cron.ts","../../../ui/src/ui/controllers/channels.ts","../../../ui/src/ui/controllers/debug.ts","../../../ui/src/ui/controllers/logs.ts","../../../node_modules/.pnpm/@noble+ed25519@3.0.0/node_modules/@noble/ed25519/index.js","../../../ui/src/ui/device-identity.ts","../../../ui/src/ui/device-auth.ts","../../../ui/src/ui/controllers/devices.ts","../../../ui/src/ui/controllers/nodes.ts","../../../ui/src/ui/controllers/exec-approvals.ts","../../../ui/src/ui/controllers/presence.ts","../../../ui/src/ui/controllers/skills.ts","../../../ui/src/ui/theme.ts","../../../ui/src/ui/theme-transition.ts","../../../ui/src/ui/app-polling.ts","../../../ui/src/ui/app-settings.ts","../../../ui/src/ui/app-chat.ts","../../../node_modules/.pnpm/lit-html@3.3.2/node_modules/lit-html/directive.js","../../../node_modules/.pnpm/lit-html@3.3.2/node_modules/lit-html/directive-helpers.js","../../../node_modules/.pnpm/lit-html@3.3.2/node_modules/lit-html/directives/repeat.js","../../../ui/src/ui/chat/message-normalizer.ts","../../../node_modules/.pnpm/lit-html@3.3.2/node_modules/lit-html/directives/unsafe-html.js","../../../node_modules/.pnpm/dompurify@3.3.1/node_modules/dompurify/dist/purify.es.mjs","../../../node_modules/.pnpm/marked@17.0.1/node_modules/marked/lib/marked.esm.js","../../../ui/src/ui/markdown.ts","../../../ui/src/ui/icons.ts","../../../ui/src/ui/chat/copy-as-markdown.ts","../../../ui/src/ui/tool-display.ts","../../../ui/src/ui/chat/constants.ts","../../../ui/src/ui/chat/tool-helpers.ts","../../../ui/src/ui/chat/tool-cards.ts","../../../ui/src/ui/chat/grouped-render.ts","../../../ui/src/ui/views/markdown-sidebar.ts","../../../ui/src/ui/components/resizable-divider.ts","../../../ui/src/ui/views/chat.ts","../../../ui/src/ui/views/config-form.shared.ts","../../../ui/src/ui/views/config-form.node.ts","../../../ui/src/ui/views/config-form.render.ts","../../../ui/src/ui/views/config-form.analyze.ts","../../../ui/src/ui/views/config.ts","../../../ui/src/ui/views/channels.shared.ts","../../../ui/src/ui/views/channels.config.ts","../../../ui/src/ui/views/channels.discord.ts","../../../ui/src/ui/views/channels.imessage.ts","../../../ui/src/ui/views/channels.nostr-profile-form.ts","../../../ui/src/ui/views/channels.nostr.ts","../../../ui/src/ui/views/channels.signal.ts","../../../ui/src/ui/views/channels.slack.ts","../../../ui/src/ui/views/channels.telegram.ts","../../../ui/src/ui/views/channels.whatsapp.ts","../../../ui/src/ui/views/channels.ts","../../../ui/src/ui/presenter.ts","../../../ui/src/ui/views/cron.ts","../../../ui/src/ui/views/debug.ts","../../../ui/src/ui/views/instances.ts","../../../ui/src/ui/views/logs.ts","../../../ui/src/ui/views/nodes.ts","../../../ui/src/ui/views/overview.ts","../../../ui/src/ui/views/sessions.ts","../../../ui/src/ui/views/exec-approval.ts","../../../ui/src/ui/views/skills.ts","../../../ui/src/ui/app-render.helpers.ts","../../../ui/src/ui/app-render.ts","../../../ui/src/ui/app-defaults.ts","../../../ui/src/ui/controllers/agents.ts","../../../src/gateway/protocol/client-info.ts","../../../src/gateway/device-auth.ts","../../../ui/src/ui/gateway.ts","../../../ui/src/ui/controllers/exec-approval.ts","../../../ui/src/ui/controllers/assistant-identity.ts","../../../ui/src/ui/app-gateway.ts","../../../ui/src/ui/app-lifecycle.ts","../../../ui/src/ui/app-channels.ts","../../../ui/src/ui/app.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2019 Google LLC\n * SPDX-License-Identifier: BSD-3-Clause\n */\nconst t=globalThis,e=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow)&&\"adoptedStyleSheets\"in Document.prototype&&\"replace\"in CSSStyleSheet.prototype,s=Symbol(),o=new WeakMapLine truncated
+1
View File
@@ -0,0 +1 @@
@import"https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&family=JetBrains+Mono:wght@400;500&display=swap";:root{--bg: #0c0d12;--bg-accent: #0d0e14;--bg-elevated: #181a21;--bg-hover: #252830;--bg-muted: #252830;--card: #13151c;--card-foreground: #f8fafc;--card-highlight: rgba(255, 255, 255, .04);--popover: #13151c;--popover-foreground: #f8fafc;--panel: #0c0d12;--panel-strong: #181a21;--panel-hover: #252830;--chrome: rgba(12, 13, 18, .95);--chrome-strong: rgba(12, 13, 18, .98);--text: #f8fafc;--text-strong: #ffffff;--chat-text: #f8fafc;--muted: #94a3b8;--muted-strong: #64748b;--muted-foreground: #94a3b8;--border: #333842;--border-strong: #454d5c;--border-hover: #5a6373;--input: #333842;--ring: #ff4d4d;--accent: #ff4d4d;--accent-hover: #ff6666;--accent-muted: #ff4d4d;--accent-subtle: rgba(255, 77, 77, .12);--accent-foreground: #f8fafc;--primary: #ff4d4d;--primary-foreground: #ffffff;--secondary: #252830;--secondary-foreground: #f8fafc;--accent-2: #3b82f6;--accent-2-muted: rgba(59, 130, 246, .7);--ok: #22c55e;--ok-muted: rgba(34, 197, 94, .7);--ok-subtle: rgba(34, 197, 94, .1);--destructive: #ef4444;--destructive-foreground: #fafafa;--warn: #eab308;--warn-muted: rgba(234, 179, 8, .7);--warn-subtle: rgba(234, 179, 8, .1);--danger: #ef4444;--danger-muted: rgba(239, 68, 68, .7);--danger-subtle: rgba(239, 68, 68, .1);--info: #3b82f6;--focus: rgba(255, 77, 77, .2);--focus-ring: 0 0 0 2px var(--bg), 0 0 0 4px var(--ring);--grid-line: rgba(255, 255, 255, .03);--theme-switch-x: 50%;--theme-switch-y: 50%;--mono: "JetBrains Mono", ui-monospace, SFMono-Regular, "SF Mono", Menlo, Monaco, Consolas, monospace;--font-body: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;--font-display: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, .05);--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, .1), 0 2px 4px -2px rgba(0, 0, 0, .1);--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, .1), 0 4px 6px -4px rgba(0, 0, 0, .1);--shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, .1), 0 8px 10px -6px rgba(0, 0, 0, .1);--radius-sm: 4px;--radius-md: 6px;--radius-lg: 8px;--radius-xl: 12px;--radius-full: 9999px;--radius: 6px;--ease-out: cubic-bezier(.16, 1, .3, 1);--ease-in-out: cubic-bezier(.4, 0, .2, 1);--duration-fast: .15s;--duration-normal: .2s;--duration-slow: .3s;color-scheme:dark}:root[data-theme=light]{--bg: #f8f8f7;--bg-accent: #f3f2f0;--bg-elevated: #ffffff;--bg-hover: #eae8e6;--bg-muted: #eae8e6;--bg-content: #f0efed;--card: #ffffff;--card-foreground: #1c1917;--card-highlight: rgba(0, 0, 0, .04);--popover: #ffffff;--popover-foreground: #1c1917;--panel: #f8f8f7;--panel-strong: #f0efed;--panel-hover: #e5e3e1;--chrome: rgba(248, 248, 247, .95);--chrome-strong: rgba(248, 248, 247, .98);--text: #44403c;--text-strong: #292524;--chat-text: #44403c;--muted: #5c5856;--muted-strong: #44403c;--muted-foreground: #5c5856;--border: #e0dedc;--border-strong: #d6d3d1;--border-hover: #a8a5a0;--input: #e0dedc;--accent: #b91c1c;--accent-hover: #dc2626;--accent-muted: #b91c1c;--accent-subtle: rgba(185, 28, 28, .18);--accent-foreground: #ffffff;--primary: #b91c1c;--primary-foreground: #ffffff;--secondary: #eae8e6;--secondary-foreground: #44403c;--ok: #15803d;--ok-muted: rgba(21, 128, 61, .75);--ok-subtle: rgba(21, 128, 61, .12);--destructive: #b91c1c;--destructive-foreground: #fafafa;--warn: #a16207;--warn-muted: rgba(161, 98, 7, .75);--warn-subtle: rgba(161, 98, 7, .12);--danger: #b91c1c;--danger-muted: rgba(185, 28, 28, .75);--danger-subtle: rgba(185, 28, 28, .12);--info: #1d4ed8;--focus: rgba(185, 28, 28, .25);--grid-line: rgba(0, 0, 0, .06);color-scheme:light}*{box-sizing:border-box}html,body{height:100%}body{margin:0;font:400 14px/1.5 var(--font-body);letter-spacing:-.011em;background:var(--bg);color:var(--text);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}@keyframes theme-circle-transition{0%{clip-path:circle(0% at var(--theme-switch-x, 50%) var(--theme-switch-y, 50%))}to{clip-path:circle(150% at var(--theme-switch-x, 50%) var(--theme-switch-y, 50%))}}html.theme-transition{view-transition-name:theme}html.theme-transition::view-transition-old(theme){mix-blend-mode:normal;animation:none;z-index:1}html.theme-transition::view-transition-new(theme){mix-blend-mode:normal;z-index:2;animation:theme-circle-transition .4s var(--ease-out) forwards}@media(prefers-reduced-motion:reduce){html.theme-transition::view-transition-old(theme),html.theme-transition::view-transition-new(theme){animation:none!important}}clawdbot-app{display:block;position:relative;z-index:1;min-height:100vh}a{color:var(--accent);text-decoration:none}a:hover{text-decoration:underline}button,input,textarea,select{font:inherit;color:inherit}::selection{background:var(--accent-subtle);color:var(--text-strong)}::-webkit-scrollbar{width:8px;height:8px}::-webkit-scrollbar-track{background:transparent}::-webkit-scrollbar-thumb{background:var(--border);border-radius:var(--radius-full)}::-webLine truncated
+2 -2
View File
@@ -6,8 +6,8 @@
<title>Clawdbot Control</title> <title>Clawdbot Control</title>
<meta name="color-scheme" content="dark light" /> <meta name="color-scheme" content="dark light" />
<link rel="icon" href="./favicon.ico" sizes="any" /> <link rel="icon" href="./favicon.ico" sizes="any" />
<script type="module" crossorigin src="./assets/index-DsXRcnEw.js"></script> <script type="module" crossorigin src="./assets/index-CJVJFuAg.js"></script>
<link rel="stylesheet" crossorigin href="./assets/index-BvhR9FCb.css"> <link rel="stylesheet" crossorigin href="./assets/index-tlTzxSME.css">
</head> </head>
<body> <body>
<clawdbot-app></clawdbot-app> <clawdbot-app></clawdbot-app>
+60
View File
@@ -0,0 +1,60 @@
<?xml version="1.0" encoding="utf-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 16 16" role="img" aria-label="Pixel lobster">
<rect width="16" height="16" fill="none"/>
<g fill="#3a0a0d">
<rect x="1" y="5" width="1" height="3"/>
<rect x="2" y="4" width="1" height="1"/>
<rect x="2" y="8" width="1" height="1"/>
<rect x="3" y="3" width="1" height="1"/>
<rect x="3" y="9" width="1" height="1"/>
<rect x="4" y="2" width="1" height="1"/>
<rect x="4" y="10" width="1" height="1"/>
<rect x="5" y="2" width="6" height="1"/>
<rect x="11" y="2" width="1" height="1"/>
<rect x="12" y="3" width="1" height="1"/>
<rect x="12" y="9" width="1" height="1"/>
<rect x="13" y="4" width="1" height="1"/>
<rect x="13" y="8" width="1" height="1"/>
<rect x="14" y="5" width="1" height="3"/>
<rect x="5" y="11" width="6" height="1"/>
<rect x="4" y="12" width="1" height="1"/>
<rect x="11" y="12" width="1" height="1"/>
<rect x="3" y="13" width="1" height="1"/>
<rect x="12" y="13" width="1" height="1"/>
<rect x="5" y="14" width="6" height="1"/>
</g>
<g fill="#ff4f40">
<rect x="5" y="3" width="6" height="1"/>
<rect x="4" y="4" width="8" height="1"/>
<rect x="3" y="5" width="10" height="1"/>
<rect x="3" y="6" width="10" height="1"/>
<rect x="3" y="7" width="10" height="1"/>
<rect x="4" y="8" width="8" height="1"/>
<rect x="5" y="9" width="6" height="1"/>
<rect x="5" y="12" width="6" height="1"/>
<rect x="6" y="13" width="4" height="1"/>
</g>
<g fill="#ff775f">
<rect x="1" y="6" width="2" height="1"/>
<rect x="2" y="5" width="1" height="1"/>
<rect x="2" y="7" width="1" height="1"/>
<rect x="13" y="6" width="2" height="1"/>
<rect x="13" y="5" width="1" height="1"/>
<rect x="13" y="7" width="1" height="1"/>
</g>
<g fill="#081016">
<rect x="6" y="5" width="1" height="1"/>
<rect x="9" y="5" width="1" height="1"/>
</g>
<g fill="#f5fbff">
<rect x="6" y="4" width="1" height="1"/>
<rect x="9" y="4" width="1" height="1"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

+4 -10
View File
@@ -10,17 +10,11 @@ describe("extractModelDirective", () => {
expect(result.cleaned).toBe(""); expect(result.cleaned).toBe("");
}); });
it("does not treat /models as a /model directive", () => { it("extracts /models with argument", () => {
const result = extractModelDirective("/models gpt-5"); const result = extractModelDirective("/models gpt-5");
expect(result.hasDirective).toBe(false); expect(result.hasDirective).toBe(true);
expect(result.rawModel).toBeUndefined(); expect(result.rawModel).toBe("gpt-5");
expect(result.cleaned).toBe("/models gpt-5"); expect(result.cleaned).toBe("");
});
it("does not parse /models as a /model directive (no args)", () => {
const result = extractModelDirective("/models");
expect(result.hasDirective).toBe(false);
expect(result.cleaned).toBe("/models");
}); });
it("extracts /model with provider/model format", () => { it("extracts /model with provider/model format", () => {
+1 -1
View File
@@ -14,7 +14,7 @@ export function extractModelDirective(
if (!body) return { cleaned: "", hasDirective: false }; if (!body) return { cleaned: "", hasDirective: false };
const modelMatch = body.match( const modelMatch = body.match(
/(?:^|\s)\/model(?=$|\s|:)\s*:?\s*([A-Za-z0-9_.:@-]+(?:\/[A-Za-z0-9_.:@-]+)*)?/i, /(?:^|\s)\/models?(?=$|\s|:)\s*:?\s*([A-Za-z0-9_.:@-]+(?:\/[A-Za-z0-9_.:@-]+)*)?/i,
); );
const aliases = (options?.aliases ?? []).map((alias) => alias.trim()).filter(Boolean); const aliases = (options?.aliases ?? []).map((alias) => alias.trim()).filter(Boolean);
+60
View File
@@ -0,0 +1,60 @@
<?xml version="1.0" encoding="utf-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 16 16" role="img" aria-label="Pixel lobster">
<rect width="16" height="16" fill="none"/>
<g fill="#3a0a0d">
<rect x="1" y="5" width="1" height="3"/>
<rect x="2" y="4" width="1" height="1"/>
<rect x="2" y="8" width="1" height="1"/>
<rect x="3" y="3" width="1" height="1"/>
<rect x="3" y="9" width="1" height="1"/>
<rect x="4" y="2" width="1" height="1"/>
<rect x="4" y="10" width="1" height="1"/>
<rect x="5" y="2" width="6" height="1"/>
<rect x="11" y="2" width="1" height="1"/>
<rect x="12" y="3" width="1" height="1"/>
<rect x="12" y="9" width="1" height="1"/>
<rect x="13" y="4" width="1" height="1"/>
<rect x="13" y="8" width="1" height="1"/>
<rect x="14" y="5" width="1" height="3"/>
<rect x="5" y="11" width="6" height="1"/>
<rect x="4" y="12" width="1" height="1"/>
<rect x="11" y="12" width="1" height="1"/>
<rect x="3" y="13" width="1" height="1"/>
<rect x="12" y="13" width="1" height="1"/>
<rect x="5" y="14" width="6" height="1"/>
</g>
<g fill="#ff4f40">
<rect x="5" y="3" width="6" height="1"/>
<rect x="4" y="4" width="8" height="1"/>
<rect x="3" y="5" width="10" height="1"/>
<rect x="3" y="6" width="10" height="1"/>
<rect x="3" y="7" width="10" height="1"/>
<rect x="4" y="8" width="8" height="1"/>
<rect x="5" y="9" width="6" height="1"/>
<rect x="5" y="12" width="6" height="1"/>
<rect x="6" y="13" width="4" height="1"/>
</g>
<g fill="#ff775f">
<rect x="1" y="6" width="2" height="1"/>
<rect x="2" y="5" width="1" height="1"/>
<rect x="2" y="7" width="1" height="1"/>
<rect x="13" y="6" width="2" height="1"/>
<rect x="13" y="5" width="1" height="1"/>
<rect x="13" y="7" width="1" height="1"/>
</g>
<g fill="#081016">
<rect x="6" y="5" width="1" height="1"/>
<rect x="9" y="5" width="1" height="1"/>
</g>
<g fill="#f5fbff">
<rect x="6" y="4" width="1" height="1"/>
<rect x="9" y="4" width="1" height="1"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

+238 -84
View File
@@ -1,60 +1,172 @@
@import url("https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500&family=Unbounded:wght@400;500;600&family=Work+Sans:wght@400;500;600;700&display=swap"); @import url("https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&family=JetBrains+Mono:wght@400;500&display=swap");
:root { :root {
--bg: #0a0f14; /* Background - Deep Navy Slate */
--bg-accent: #111826; --bg: #0c0d12;
--bg-grad-1: #162031; --bg-accent: #0d0e14;
--bg-grad-2: #1f2a22; --bg-elevated: #181a21;
--bg-overlay: rgba(255, 255, 255, 0.05); --bg-hover: #252830;
--bg-glow: rgba(245, 159, 74, 0.12); --bg-muted: #252830;
--panel: rgba(14, 20, 30, 0.88);
--panel-strong: rgba(18, 26, 38, 0.96); /* Card / Surface */
--chrome: rgba(9, 14, 20, 0.72); --card: #13151c;
--chrome-strong: rgba(9, 14, 20, 0.86); --card-foreground: #f8fafc;
--text: rgba(244, 246, 251, 0.96); --card-highlight: rgba(255, 255, 255, 0.04);
--chat-text: rgba(231, 237, 244, 0.92); --popover: #13151c;
--muted: rgba(156, 169, 189, 0.72); --popover-foreground: #f8fafc;
--border: rgba(255, 255, 255, 0.09);
--border-strong: rgba(255, 255, 255, 0.16); /* Panel */
--accent: #f59f4a; --panel: #0c0d12;
--accent-2: #34c7b7; --panel-strong: #181a21;
--ok: #2bd97f; --panel-hover: #252830;
--warn: #f2c94c; --chrome: rgba(12, 13, 18, 0.95);
--danger: #ff6b6b; --chrome-strong: rgba(12, 13, 18, 0.98);
--focus: rgba(245, 159, 74, 0.35);
--grid-line: rgba(255, 255, 255, 0.04); /* Text */
--text: #f8fafc;
--text-strong: #ffffff;
--chat-text: #f8fafc;
--muted: #94a3b8;
--muted-strong: #64748b;
--muted-foreground: #94a3b8;
/* Border */
--border: #333842;
--border-strong: #454d5c;
--border-hover: #5a6373;
--input: #333842;
--ring: #ff4d4d;
/* Accent - The signature red */
--accent: #ff4d4d;
--accent-hover: #ff6666;
--accent-muted: #ff4d4d;
--accent-subtle: rgba(255, 77, 77, 0.12);
--accent-foreground: #f8fafc;
--primary: #ff4d4d;
--primary-foreground: #ffffff;
/* Secondary */
--secondary: #252830;
--secondary-foreground: #f8fafc;
--accent-2: #3b82f6;
--accent-2-muted: rgba(59, 130, 246, 0.7);
/* Semantic */
--ok: #22c55e;
--ok-muted: rgba(34, 197, 94, 0.7);
--ok-subtle: rgba(34, 197, 94, 0.1);
--destructive: #ef4444;
--destructive-foreground: #fafafa;
--warn: #eab308;
--warn-muted: rgba(234, 179, 8, 0.7);
--warn-subtle: rgba(234, 179, 8, 0.1);
--danger: #ef4444;
--danger-muted: rgba(239, 68, 68, 0.7);
--danger-subtle: rgba(239, 68, 68, 0.1);
--info: #3b82f6;
/* Focus */
--focus: rgba(255, 77, 77, 0.2);
--focus-ring: 0 0 0 2px var(--bg), 0 0 0 4px var(--ring);
/* Grid */
--grid-line: rgba(255, 255, 255, 0.03);
/* Theme transition */
--theme-switch-x: 50%; --theme-switch-x: 50%;
--theme-switch-y: 50%; --theme-switch-y: 50%;
--mono: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
"Liberation Mono", "Courier New", monospace; /* Typography */
--font-body: "Work Sans", system-ui, sans-serif; --mono: "JetBrains Mono", ui-monospace, SFMono-Regular, "SF Mono", Menlo, Monaco, Consolas, monospace;
--font-display: "Unbounded", "Times New Roman", serif; --font-body: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
--font-display: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
/* Shadows - minimal */
--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.1);
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1);
--shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1);
/* Radii - shadcn uses smaller radii */
--radius-sm: 4px;
--radius-md: 6px;
--radius-lg: 8px;
--radius-xl: 12px;
--radius-full: 9999px;
--radius: 6px;
/* Transitions */
--ease-out: cubic-bezier(0.16, 1, 0.3, 1);
--ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
--duration-fast: 150ms;
--duration-normal: 200ms;
--duration-slow: 300ms;
color-scheme: dark; color-scheme: dark;
} }
/* Light theme */
:root[data-theme="light"] { :root[data-theme="light"] {
--bg: #f5f1ea; --bg: #f8f8f7;
--bg-accent: #ffffff; --bg-accent: #f3f2f0;
--bg-grad-1: #f1e6d6; --bg-elevated: #ffffff;
--bg-grad-2: #e5eef4; --bg-hover: #eae8e6;
--bg-overlay: rgba(28, 32, 46, 0.05); --bg-muted: #eae8e6;
--bg-glow: rgba(52, 199, 183, 0.14); --bg-content: #f0efed;
--panel: rgba(255, 255, 255, 0.9);
--panel-strong: rgba(255, 255, 255, 0.97); --card: #ffffff;
--chrome: rgba(255, 255, 255, 0.75); --card-foreground: #1c1917;
--chrome-strong: rgba(255, 255, 255, 0.88); --card-highlight: rgba(0, 0, 0, 0.04);
--text: rgba(27, 36, 50, 0.98); --popover: #ffffff;
--chat-text: rgba(36, 48, 66, 0.9); --popover-foreground: #1c1917;
--muted: rgba(80, 94, 114, 0.7);
--border: rgba(18, 24, 40, 0.12); --panel: #f8f8f7;
--border-strong: rgba(18, 24, 40, 0.2); --panel-strong: #f0efed;
--accent: #e28a3f; --panel-hover: #e5e3e1;
--accent-2: #1ba99d; --chrome: rgba(248, 248, 247, 0.95);
--ok: #1aa86c; --chrome-strong: rgba(248, 248, 247, 0.98);
--warn: #b3771c;
--danger: #d44848; --text: #44403c;
--focus: rgba(226, 138, 63, 0.35); --text-strong: #292524;
--grid-line: rgba(18, 24, 40, 0.06); --chat-text: #44403c;
--muted: #5c5856;
--muted-strong: #44403c;
--muted-foreground: #5c5856;
--border: #e0dedc;
--border-strong: #d6d3d1;
--border-hover: #a8a5a0;
--input: #e0dedc;
--accent: #b91c1c;
--accent-hover: #dc2626;
--accent-muted: #b91c1c;
--accent-subtle: rgba(185, 28, 28, 0.18);
--accent-foreground: #ffffff;
--primary: #b91c1c;
--primary-foreground: #ffffff;
--secondary: #eae8e6;
--secondary-foreground: #44403c;
--ok: #15803d;
--ok-muted: rgba(21, 128, 61, 0.75);
--ok-subtle: rgba(21, 128, 61, 0.12);
--destructive: #b91c1c;
--destructive-foreground: #fafafa;
--warn: #a16207;
--warn-muted: rgba(161, 98, 7, 0.75);
--warn-subtle: rgba(161, 98, 7, 0.12);
--danger: #b91c1c;
--danger-muted: rgba(185, 28, 28, 0.75);
--danger-subtle: rgba(185, 28, 28, 0.12);
--info: #1d4ed8;
--focus: rgba(185, 28, 28, 0.25);
--grid-line: rgba(0, 0, 0, 0.06);
color-scheme: light; color-scheme: light;
} }
@@ -69,44 +181,21 @@ body {
body { body {
margin: 0; margin: 0;
font: 15px/1.5 var(--font-body); font: 400 14px/1.5 var(--font-body);
background: letter-spacing: -0.011em;
radial-gradient(1200px 900px at 15% -10%, var(--bg-grad-1) 0%, transparent 55%) background: var(--bg);
fixed,
radial-gradient(900px 700px at 80% 10%, var(--bg-grad-2) 0%, transparent 60%)
fixed,
linear-gradient(160deg, var(--bg) 0%, var(--bg-accent) 100%) fixed;
color: var(--text); color: var(--text);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
} }
body::before { /* Theme transition */
content: "";
position: fixed;
inset: 0;
background:
linear-gradient(
140deg,
var(--bg-overlay) 0%,
rgba(255, 255, 255, 0) 40%
),
radial-gradient(620px 420px at 75% 75%, var(--bg-glow), transparent 60%);
pointer-events: none;
z-index: 0;
}
/* Grid overlay removed for cleaner look */
@keyframes theme-circle-transition { @keyframes theme-circle-transition {
0% { 0% {
clip-path: circle( clip-path: circle(0% at var(--theme-switch-x, 50%) var(--theme-switch-y, 50%));
0% at var(--theme-switch-x, 50%) var(--theme-switch-y, 50%)
);
} }
100% { 100% {
clip-path: circle( clip-path: circle(150% at var(--theme-switch-x, 50%) var(--theme-switch-y, 50%));
150% at var(--theme-switch-x, 50%) var(--theme-switch-y, 50%)
);
} }
} }
@@ -123,7 +212,7 @@ html.theme-transition::view-transition-old(theme) {
html.theme-transition::view-transition-new(theme) { html.theme-transition::view-transition-new(theme) {
mix-blend-mode: normal; mix-blend-mode: normal;
z-index: 2; z-index: 2;
animation: theme-circle-transition 0.45s ease-out forwards; animation: theme-circle-transition 0.4s var(--ease-out) forwards;
} }
@media (prefers-reduced-motion: reduce) { @media (prefers-reduced-motion: reduce) {
@@ -141,7 +230,12 @@ clawdbot-app {
} }
a { a {
color: inherit; color: var(--accent);
text-decoration: none;
}
a:hover {
text-decoration: underline;
} }
button, button,
@@ -152,10 +246,35 @@ select {
color: inherit; color: inherit;
} }
::selection {
background: var(--accent-subtle);
color: var(--text-strong);
}
/* Scrollbar styling */
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-thumb {
background: var(--border);
border-radius: var(--radius-full);
}
::-webkit-scrollbar-thumb:hover {
background: var(--border-strong);
}
/* Animations */
@keyframes rise { @keyframes rise {
from { from {
opacity: 0; opacity: 0;
transform: translateY(6px); transform: translateY(4px);
} }
to { to {
opacity: 1; opacity: 1;
@@ -163,13 +282,48 @@ select {
} }
} }
@keyframes fade-in {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes scale-in {
from {
opacity: 0;
transform: scale(0.96);
}
to {
opacity: 1;
transform: scale(1);
}
}
@keyframes dashboard-enter { @keyframes dashboard-enter {
from { from {
opacity: 0; opacity: 0;
transform: translateY(12px); transform: translateY(8px);
} }
to { to {
opacity: 1; opacity: 1;
transform: translateY(0); transform: translateY(0);
} }
} }
@keyframes shimmer {
0% {
background-position: -200% 0;
}
100% {
background-position: 200% 0;
}
}
/* Focus visible styles */
:focus-visible {
outline: none;
box-shadow: var(--focus-ring);
}
+70 -28
View File
@@ -8,7 +8,7 @@
gap: 12px; gap: 12px;
align-items: flex-start; align-items: flex-start;
margin-bottom: 16px; margin-bottom: 16px;
margin-left: 16px; margin-left: 4px;
margin-right: 16px; margin-right: 16px;
} }
@@ -70,23 +70,23 @@
} }
.chat-avatar.user { .chat-avatar.user {
background: rgba(245, 159, 74, 0.2); background: var(--accent-subtle);
color: rgba(245, 159, 74, 1); color: var(--accent);
} }
.chat-avatar.assistant { .chat-avatar.assistant {
background: rgba(52, 199, 183, 0.2); background: var(--secondary);
color: rgba(52, 199, 183, 1); color: var(--muted);
} }
.chat-avatar.other { .chat-avatar.other {
background: rgba(150, 150, 150, 0.2); background: var(--secondary);
color: rgba(150, 150, 150, 1); color: var(--muted);
} }
.chat-avatar.tool { .chat-avatar.tool {
background: rgba(134, 142, 150, 0.2); background: var(--secondary);
color: rgba(134, 142, 150, 1); color: var(--muted);
} }
/* Image avatar support */ /* Image avatar support */
@@ -100,9 +100,9 @@ img.chat-avatar {
.chat-bubble { .chat-bubble {
position: relative; position: relative;
display: inline-block; display: inline-block;
border: 1px solid var(--border); border: 1px solid transparent;
background: rgba(0, 0, 0, 0.12); background: var(--card);
border-radius: 12px; border-radius: var(--radius-lg);
padding: 10px 14px; padding: 10px 14px;
box-shadow: none; box-shadow: none;
transition: background 150ms ease-out, border-color 150ms ease-out; transition: background 150ms ease-out, border-color 150ms ease-out;
@@ -119,9 +119,9 @@ img.chat-avatar {
top: 6px; top: 6px;
right: 8px; right: 8px;
border: 1px solid var(--border); border: 1px solid var(--border);
background: rgba(0, 0, 0, 0.22); background: var(--bg);
color: var(--muted); color: var(--muted);
border-radius: 8px; border-radius: var(--radius-md);
padding: 4px 6px; padding: 4px 6px;
font-size: 14px; font-size: 14px;
line-height: 1; line-height: 1;
@@ -132,9 +132,40 @@ img.chat-avatar {
} }
.chat-copy-btn__icon { .chat-copy-btn__icon {
display: inline-block; display: inline-flex;
width: 1em; width: 14px;
text-align: center; height: 14px;
position: relative;
}
.chat-copy-btn__icon svg {
width: 14px;
height: 14px;
stroke: currentColor;
fill: none;
stroke-width: 1.5px;
stroke-linecap: round;
stroke-linejoin: round;
}
.chat-copy-btn__icon-copy,
.chat-copy-btn__icon-check {
position: absolute;
top: 0;
left: 0;
transition: opacity 150ms ease;
}
.chat-copy-btn__icon-check {
opacity: 0;
}
.chat-copy-btn[data-copied="1"] .chat-copy-btn__icon-copy {
opacity: 0;
}
.chat-copy-btn[data-copied="1"] .chat-copy-btn__icon-check {
opacity: 1;
} }
.chat-bubble:hover .chat-copy-btn { .chat-bubble:hover .chat-copy-btn {
@@ -143,7 +174,7 @@ img.chat-avatar {
} }
.chat-copy-btn:hover { .chat-copy-btn:hover {
background: rgba(0, 0, 0, 0.3); background: var(--bg-hover);
} }
.chat-copy-btn[data-copying="1"] { .chat-copy-btn[data-copying="1"] {
@@ -154,17 +185,17 @@ img.chat-avatar {
.chat-copy-btn[data-error="1"] { .chat-copy-btn[data-error="1"] {
opacity: 1; opacity: 1;
pointer-events: auto; pointer-events: auto;
border-color: rgba(255, 69, 58, 0.8); border-color: var(--danger-subtle);
background: rgba(255, 69, 58, 0.18); background: var(--danger-subtle);
color: rgba(255, 69, 58, 1); color: var(--danger);
} }
.chat-copy-btn[data-copied="1"] { .chat-copy-btn[data-copied="1"] {
opacity: 1; opacity: 1;
pointer-events: auto; pointer-events: auto;
border-color: rgba(52, 199, 183, 0.8); border-color: var(--ok-subtle);
background: rgba(52, 199, 183, 0.18); background: var(--ok-subtle);
color: rgba(52, 199, 183, 1); color: var(--ok);
} }
.chat-copy-btn:focus-visible { .chat-copy-btn:focus-visible {
@@ -181,18 +212,29 @@ img.chat-avatar {
} }
} }
/* Light mode: restore borders */
:root[data-theme="light"] .chat-bubble {
border-color: var(--border);
box-shadow: inset 0 1px 0 var(--card-highlight);
}
.chat-bubble:hover { .chat-bubble:hover {
background: rgba(0, 0, 0, 0.18); background: var(--bg-hover);
} }
/* User bubbles have different styling */ /* User bubbles have different styling */
.chat-group.user .chat-bubble { .chat-group.user .chat-bubble {
background: rgba(245, 159, 74, 0.15); background: var(--accent-subtle);
border-color: rgba(245, 159, 74, 0.3); border-color: transparent;
}
:root[data-theme="light"] .chat-group.user .chat-bubble {
border-color: rgba(234, 88, 12, 0.2);
background: rgba(251, 146, 60, 0.12);
} }
.chat-group.user .chat-bubble:hover { .chat-group.user .chat-bubble:hover {
background: rgba(245, 159, 74, 0.22); background: rgba(255, 77, 77, 0.15);
} }
/* Streaming animation */ /* Streaming animation */
+44 -15
View File
@@ -52,8 +52,8 @@
flex: 1 1 0; /* Grow, shrink, and use 0 base for proper scrolling */ flex: 1 1 0; /* Grow, shrink, and use 0 base for proper scrolling */
overflow-y: auto; overflow-y: auto;
overflow-x: hidden; overflow-x: hidden;
padding: 12px; padding: 12px 4px;
margin: 0 -12px; margin: 0 -4px;
min-height: 0; /* Allow shrinking for flex scroll behavior */ min-height: 0; /* Allow shrinking for flex scroll behavior */
border-radius: 12px; border-radius: 12px;
background: transparent; background: transparent;
@@ -87,23 +87,39 @@
border-color: var(--accent); border-color: var(--accent);
} }
.chat-focus-exit svg {
width: 16px;
height: 16px;
stroke: currentColor;
fill: none;
stroke-width: 2px;
stroke-linecap: round;
stroke-linejoin: round;
}
/* Chat compose - sticky at bottom */ /* Chat compose - sticky at bottom */
.chat-compose { .chat-compose {
position: sticky; position: sticky;
bottom: 0; bottom: 0;
flex-shrink: 0; flex-shrink: 0;
display: flex; display: flex;
align-items: flex-end; align-items: stretch;
gap: 12px; gap: 12px;
margin-top: auto; /* Push to bottom of flex container */ margin-top: auto; /* Push to bottom of flex container */
padding: 16px 0 4px; padding: 12px 4px 4px;
background: linear-gradient(to bottom, transparent, var(--bg) 20%); background: linear-gradient(to bottom, transparent, var(--bg) 20%);
z-index: 10; z-index: 10;
} }
:root[data-theme="light"] .chat-compose {
background: linear-gradient(to bottom, transparent, var(--bg-content) 20%);
}
.chat-compose__field { .chat-compose__field {
flex: 1 1 auto; flex: 1 1 auto;
min-width: 0; min-width: 0;
display: flex;
align-items: stretch;
} }
/* Hide the "Message" label - keep textarea only */ /* Hide the "Message" label - keep textarea only */
@@ -114,10 +130,11 @@
/* Override .field textarea min-height (180px) from components.css */ /* Override .field textarea min-height (180px) from components.css */
.chat-compose .chat-compose__field textarea { .chat-compose .chat-compose__field textarea {
width: 100%; width: 100%;
min-height: 36px; height: 40px;
min-height: 40px;
max-height: 150px; max-height: 150px;
padding: 8px 12px; padding: 9px 12px;
border-radius: 10px; border-radius: 8px;
resize: vertical; resize: vertical;
white-space: pre-wrap; white-space: pre-wrap;
font-family: var(--font-body); font-family: var(--font-body);
@@ -134,13 +151,18 @@
flex-shrink: 0; flex-shrink: 0;
display: flex; display: flex;
align-items: stretch; align-items: stretch;
gap: 8px;
} }
.chat-compose .chat-compose__actions .btn { .chat-compose .chat-compose__actions .btn {
padding: 8px 16px; padding: 0 16px;
font-size: 13px; font-size: 13px;
min-height: 36px; height: 40px;
min-height: 40px;
max-height: 40px;
line-height: 1;
white-space: nowrap; white-space: nowrap;
box-sizing: border-box;
} }
/* Chat controls - moved to content-header area, left aligned */ /* Chat controls - moved to content-header area, left aligned */
@@ -194,20 +216,27 @@
/* Light theme icon button overrides */ /* Light theme icon button overrides */
:root[data-theme="light"] .btn--icon { :root[data-theme="light"] .btn--icon {
background: rgba(255, 255, 255, 0.9); background: #ffffff;
border-color: rgba(16, 24, 40, 0.2); border-color: var(--border);
box-shadow: 0 1px 2px rgba(16, 24, 40, 0.05); box-shadow: 0 1px 2px rgba(16, 24, 40, 0.05);
color: rgba(16, 24, 40, 0.7); color: var(--muted);
} }
:root[data-theme="light"] .btn--icon:hover { :root[data-theme="light"] .btn--icon:hover {
background: rgba(255, 255, 255, 1); background: #ffffff;
border-color: rgba(16, 24, 40, 0.3); border-color: var(--border-strong);
color: rgba(16, 24, 40, 0.9); color: var(--text);
} }
.btn--icon svg { .btn--icon svg {
display: block; display: block;
width: 18px;
height: 18px;
stroke: currentColor;
fill: none;
stroke-width: 1.5px;
stroke-linecap: round;
stroke-linejoin: round;
} }
.chat-controls__session select { .chat-controls__session select {
+40 -3
View File
@@ -14,8 +14,8 @@
} }
:root[data-theme="light"] .chat-thinking { :root[data-theme="light"] .chat-thinking {
border-color: rgba(16, 24, 40, 0.18); border-color: rgba(16, 24, 40, 0.25);
background: rgba(16, 24, 40, 0.03); background: rgba(16, 24, 40, 0.04);
} }
.chat-text { .chat-text {
@@ -75,9 +75,46 @@
} }
.chat-text :where(blockquote) { .chat-text :where(blockquote) {
border-left: 3px solid var(--border); border-left: 3px solid var(--border-strong);
padding-left: 12px; padding-left: 12px;
margin-left: 0;
color: var(--muted); color: var(--muted);
background: rgba(255, 255, 255, 0.02);
padding: 8px 12px;
border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
}
.chat-text :where(blockquote blockquote) {
margin-top: 8px;
border-left-color: var(--border-hover);
background: rgba(255, 255, 255, 0.03);
}
.chat-text :where(blockquote blockquote blockquote) {
border-left-color: var(--muted-strong);
background: rgba(255, 255, 255, 0.04);
}
:root[data-theme="light"] .chat-text :where(blockquote) {
background: rgba(0, 0, 0, 0.03);
}
:root[data-theme="light"] .chat-text :where(blockquote blockquote) {
background: rgba(0, 0, 0, 0.05);
}
:root[data-theme="light"] .chat-text :where(blockquote blockquote blockquote) {
background: rgba(0, 0, 0, 0.04);
}
:root[data-theme="light"] .chat-text :where(:not(pre) > code) {
background: rgba(0, 0, 0, 0.08);
border: 1px solid rgba(0, 0, 0, 0.1);
}
:root[data-theme="light"] .chat-text :where(pre) {
background: rgba(0, 0, 0, 0.05);
border: 1px solid rgba(0, 0, 0, 0.1);
} }
.chat-text :where(hr) { .chat-text :where(hr) {
+48 -16
View File
@@ -4,6 +4,8 @@
border-radius: 8px; border-radius: 8px;
padding: 12px; padding: 12px;
margin-top: 8px; margin-top: 8px;
background: var(--card);
box-shadow: inset 0 1px 0 var(--card-highlight);
transition: border-color 150ms ease-out, background 150ms ease-out; transition: border-color 150ms ease-out, background 150ms ease-out;
/* Fixed max-height to ensure cards don't expand too much */ /* Fixed max-height to ensure cards don't expand too much */
max-height: 120px; max-height: 120px;
@@ -11,8 +13,8 @@
} }
.chat-tool-card:hover { .chat-tool-card:hover {
border-color: var(--accent); border-color: var(--border-strong);
background: rgba(0, 0, 0, 0.06); background: var(--bg-hover);
} }
/* First tool card in a group - no top margin */ /* First tool card in a group - no top margin */
@@ -50,33 +52,63 @@
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
width: 18px; width: 16px;
height: 18px; height: 16px;
font-size: 14px;
line-height: 1;
font-family: "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", sans-serif;
vertical-align: middle;
flex-shrink: 0; flex-shrink: 0;
} }
.chat-tool-card__icon svg {
width: 14px;
height: 14px;
stroke: currentColor;
fill: none;
stroke-width: 1.5px;
stroke-linecap: round;
stroke-linejoin: round;
}
/* "View >" action link */ /* "View >" action link */
.chat-tool-card__action { .chat-tool-card__action {
display: inline-flex;
align-items: center;
gap: 4px;
font-size: 12px; font-size: 12px;
color: var(--accent); color: var(--accent);
opacity: 0.8; opacity: 0.8;
transition: opacity 150ms ease-out; transition: opacity 150ms ease-out;
} }
.chat-tool-card__action svg {
width: 12px;
height: 12px;
stroke: currentColor;
fill: none;
stroke-width: 1.5px;
stroke-linecap: round;
stroke-linejoin: round;
}
.chat-tool-card--clickable:hover .chat-tool-card__action { .chat-tool-card--clickable:hover .chat-tool-card__action {
opacity: 1; opacity: 1;
} }
/* Status indicator for completed/empty results */ /* Status indicator for completed/empty results */
.chat-tool-card__status { .chat-tool-card__status {
font-size: 14px; display: inline-flex;
align-items: center;
color: var(--ok); color: var(--ok);
} }
.chat-tool-card__status svg {
width: 14px;
height: 14px;
stroke: currentColor;
fill: none;
stroke-width: 2px;
stroke-linecap: round;
stroke-linejoin: round;
}
.chat-tool-card__status-text { .chat-tool-card__status-text {
font-size: 11px; font-size: 11px;
margin-top: 4px; margin-top: 4px;
@@ -94,18 +126,18 @@
color: var(--muted); color: var(--muted);
margin-top: 8px; margin-top: 8px;
padding: 8px 10px; padding: 8px 10px;
background: rgba(0, 0, 0, 0.08); background: var(--secondary);
border-radius: 6px; border-radius: var(--radius-md);
white-space: pre-wrap; white-space: pre-wrap;
overflow: hidden; overflow: hidden;
max-height: 44px; max-height: 44px;
line-height: 1.4; line-height: 1.4;
border: 1px solid rgba(255, 255, 255, 0.04); border: 1px solid var(--border);
} }
.chat-tool-card--clickable:hover .chat-tool-card__preview { .chat-tool-card--clickable:hover .chat-tool-card__preview {
background: rgba(0, 0, 0, 0.12); background: var(--bg-hover);
border-color: rgba(255, 255, 255, 0.08); border-color: var(--border-strong);
} }
/* Short inline output */ /* Short inline output */
@@ -114,8 +146,8 @@
color: var(--text); color: var(--text);
margin-top: 6px; margin-top: 6px;
padding: 6px 8px; padding: 6px 8px;
background: rgba(0, 0, 0, 0.06); background: var(--secondary);
border-radius: 4px; border-radius: var(--radius-sm);
white-space: pre-wrap; white-space: pre-wrap;
word-break: break-word; word-break: break-word;
} }
File diff suppressed because it is too large. Load diff
+348 -304
View File
File diff suppressed because it is too large. Load diff
+205 -114
View File
@@ -1,10 +1,14 @@
/* ===========================================
Shell Layout
=========================================== */
.shell { .shell {
--shell-pad: 16px; --shell-pad: 16px;
--shell-gap: 16px; --shell-gap: 16px;
--shell-nav-width: 220px; --shell-nav-width: 220px;
--shell-topbar-height: 56px; --shell-topbar-height: 56px;
--shell-focus-duration: 220ms; --shell-focus-duration: 200ms;
--shell-focus-ease: cubic-bezier(0.2, 0.85, 0.25, 1); --shell-focus-ease: var(--ease-out);
height: 100vh; height: 100vh;
display: grid; display: grid;
grid-template-columns: var(--shell-nav-width) minmax(0, 1fr); grid-template-columns: var(--shell-nav-width) minmax(0, 1fr);
@@ -13,7 +17,7 @@
"topbar topbar" "topbar topbar"
"nav content"; "nav content";
gap: 0; gap: 0;
animation: dashboard-enter 0.6s ease-out; animation: dashboard-enter 0.4s var(--ease-out);
transition: grid-template-columns var(--shell-focus-duration) var(--shell-focus-ease); transition: grid-template-columns var(--shell-focus-duration) var(--shell-focus-ease);
overflow: hidden; overflow: hidden;
} }
@@ -61,6 +65,10 @@
gap: 0; gap: 0;
} }
/* ===========================================
Topbar
=========================================== */
.topbar { .topbar {
grid-area: topbar; grid-area: topbar;
position: sticky; position: sticky;
@@ -73,8 +81,7 @@
padding: 0 20px; padding: 0 20px;
height: var(--shell-topbar-height); height: var(--shell-topbar-height);
border-bottom: 1px solid var(--border); border-bottom: 1px solid var(--border);
background: var(--panel); background: var(--bg);
backdrop-filter: blur(18px);
} }
.topbar-left { .topbar-left {
@@ -84,49 +91,83 @@
} }
.topbar .nav-collapse-toggle { .topbar .nav-collapse-toggle {
width: 44px; width: 36px;
height: 44px; height: 36px;
margin-bottom: 0; margin-bottom: 0;
} }
.topbar .nav-collapse-toggle__icon { .topbar .nav-collapse-toggle__icon {
font-size: 22px; width: 20px;
height: 20px;
} }
.topbar .nav-collapse-toggle__icon svg {
width: 20px;
height: 20px;
}
/* Brand */
.brand { .brand {
display: flex;
align-items: center;
gap: 10px;
}
.brand-logo {
width: 28px;
height: 28px;
flex-shrink: 0;
}
.brand-logo img {
width: 100%;
height: 100%;
object-fit: contain;
}
.brand-text {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 2px; gap: 1px;
} }
.brand-title { .brand-title {
font-family: var(--font-display); font-size: 15px;
font-size: 16px;
letter-spacing: 1px;
text-transform: uppercase;
font-weight: 600; font-weight: 600;
letter-spacing: -0.02em;
line-height: 1.1; line-height: 1.1;
color: var(--text-strong);
} }
.brand-sub { .brand-sub {
font-size: 10px; font-size: 11px;
font-weight: 500;
color: var(--muted); color: var(--muted);
letter-spacing: 0.8px; letter-spacing: 0.02em;
text-transform: uppercase;
line-height: 1; line-height: 1;
} }
/* Topbar status */
.topbar-status { .topbar-status {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 8px; gap: 8px;
} }
/* Smaller pill and theme toggle in topbar */
.topbar-status .pill { .topbar-status .pill {
padding: 4px 10px; padding: 6px 10px;
gap: 6px; gap: 6px;
font-size: 11px; font-size: 12px;
font-weight: 500;
height: 32px;
box-sizing: border-box;
}
.topbar-status .pill .mono {
display: flex;
align-items: center;
line-height: 1;
margin-top: 0px;
} }
.topbar-status .statusDot { .topbar-status .statusDot {
@@ -135,9 +176,9 @@
} }
.topbar-status .theme-toggle { .topbar-status .theme-toggle {
--theme-item: 22px; --theme-item: 24px;
--theme-gap: 4px; --theme-gap: 2px;
--theme-pad: 4px; --theme-pad: 3px;
} }
.topbar-status .theme-icon { .topbar-status .theme-icon {
@@ -145,17 +186,22 @@
height: 12px; height: 12px;
} }
/* ===========================================
Navigation Sidebar
=========================================== */
.nav { .nav {
grid-area: nav; grid-area: nav;
overflow-y: auto; overflow-y: auto;
overflow-x: hidden; overflow-x: hidden;
padding: 16px; padding: 16px 12px;
border-right: 1px solid var(--border); border-right: 1px solid var(--border);
background: var(--panel); background: var(--bg);
backdrop-filter: blur(18px); transition:
transition: width var(--shell-focus-duration) var(--shell-focus-ease), width var(--shell-focus-duration) var(--shell-focus-ease),
padding var(--shell-focus-duration) var(--shell-focus-ease); padding var(--shell-focus-duration) var(--shell-focus-ease),
min-height: 0; /* Allow grid item to shrink and enable scrolling */ opacity var(--shell-focus-duration) var(--shell-focus-ease);
min-height: 0;
} }
.shell--chat-focus .nav { .shell--chat-focus .nav {
@@ -164,9 +210,9 @@
border-width: 0; border-width: 0;
overflow: hidden; overflow: hidden;
pointer-events: none; pointer-events: none;
opacity: 0;
} }
/* Collapsed nav sidebar - completely hidden */
.nav--collapsed { .nav--collapsed {
width: 0; width: 0;
min-width: 0; min-width: 0;
@@ -177,7 +223,7 @@
pointer-events: none; pointer-events: none;
} }
/* Nav collapse toggle button */ /* Nav collapse toggle */
.nav-collapse-toggle { .nav-collapse-toggle {
width: 32px; width: 32px;
height: 32px; height: 32px;
@@ -186,71 +232,88 @@
justify-content: center; justify-content: center;
background: transparent; background: transparent;
border: 1px solid transparent; border: 1px solid transparent;
border-radius: 6px; border-radius: var(--radius-md);
cursor: pointer; cursor: pointer;
transition: background 150ms ease, border-color 150ms ease; transition:
background var(--duration-fast) ease,
border-color var(--duration-fast) ease;
margin-bottom: 16px; margin-bottom: 16px;
} }
.nav-collapse-toggle:hover { .nav-collapse-toggle:hover {
background: rgba(255, 255, 255, 0.08); background: var(--bg-hover);
border-color: var(--border); border-color: var(--border);
} }
:root[data-theme="light"] .nav-collapse-toggle:hover {
background: rgba(0, 0, 0, 0.06);
}
.nav-collapse-toggle__icon { .nav-collapse-toggle__icon {
font-size: 16px; display: flex;
align-items: center;
justify-content: center;
width: 18px;
height: 18px;
color: var(--muted); color: var(--muted);
transition: color var(--duration-fast) ease;
} }
.nav-collapse-toggle__icon svg {
width: 18px;
height: 18px;
stroke: currentColor;
fill: none;
stroke-width: 1.5px;
stroke-linecap: round;
stroke-linejoin: round;
}
.nav-collapse-toggle:hover .nav-collapse-toggle__icon {
color: var(--text);
}
/* Nav groups */
.nav-group { .nav-group {
margin-bottom: 18px; margin-bottom: 20px;
display: grid; display: grid;
gap: 6px; gap: 2px;
padding-bottom: 12px;
border-bottom: 1px dashed rgba(255, 255, 255, 0.08);
} }
.nav-group:last-child { .nav-group:last-child {
margin-bottom: 0; margin-bottom: 0;
padding-bottom: 0;
border-bottom: none;
} }
.nav-group__items { .nav-group__items {
display: grid; display: grid;
gap: 4px; gap: 1px;
} }
.nav-group--collapsed .nav-group__items { .nav-group--collapsed .nav-group__items {
display: none; display: none;
} }
/* Nav label */
.nav-label { .nav-label {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
gap: 8px; gap: 8px;
width: 100%; width: 100%;
padding: 4px 0; padding: 6px 10px;
font-size: 11px; font-size: 11px;
font-weight: 500; font-weight: 500;
text-transform: uppercase; color: var(--muted);
letter-spacing: 1.4px;
color: var(--text);
opacity: 0.7;
margin-bottom: 4px; margin-bottom: 4px;
background: transparent; background: transparent;
border: none; border: none;
cursor: pointer; cursor: pointer;
text-align: left; text-align: left;
border-radius: var(--radius-sm);
transition:
color var(--duration-fast) ease,
background var(--duration-fast) ease;
} }
.nav-label:hover { .nav-label:hover {
opacity: 1; color: var(--text);
background: var(--bg-hover);
} }
.nav-label--static { .nav-label--static {
@@ -258,7 +321,8 @@
} }
.nav-label--static:hover { .nav-label--static:hover {
opacity: 0.7; color: var(--muted);
background: transparent;
} }
.nav-label__text { .nav-label__text {
@@ -266,131 +330,152 @@
} }
.nav-label__chevron { .nav-label__chevron {
font-size: 12px; font-size: 10px;
opacity: 0.6; opacity: 0.5;
transition: transform var(--duration-fast) ease;
} }
.nav-group--collapsed .nav-label__chevron {
transform: rotate(-90deg);
}
/* Nav items */
.nav-item { .nav-item {
position: relative; position: relative;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: flex-start; justify-content: flex-start;
gap: 8px; gap: 10px;
padding: 10px 12px 10px 14px; padding: 8px 10px;
border-radius: 12px; border-radius: var(--radius-md);
border: 1px solid transparent; border: 1px solid transparent;
background: transparent; background: transparent;
color: var(--muted); color: var(--muted);
cursor: pointer; cursor: pointer;
text-decoration: none; text-decoration: none;
transition: border-color 160ms ease, background 160ms ease, color 160ms ease; transition:
border-color var(--duration-fast) ease,
background var(--duration-fast) ease,
color var(--duration-fast) ease;
} }
.nav-item__icon { .nav-item__icon {
font-size: 16px; width: 16px;
width: 18px; height: 16px;
height: 18px;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
flex-shrink: 0; flex-shrink: 0;
opacity: 0.7;
transition: opacity var(--duration-fast) ease;
}
.nav-item__icon svg {
width: 16px;
height: 16px;
stroke: currentColor;
fill: none;
stroke-width: 1.5px;
stroke-linecap: round;
stroke-linejoin: round;
} }
.nav-item__text { .nav-item__text {
font-size: 13px; font-size: 13px;
font-weight: 500;
white-space: nowrap; white-space: nowrap;
} }
.nav-item:hover { .nav-item:hover {
color: var(--text); color: var(--text);
border-color: rgba(255, 255, 255, 0.12); background: var(--bg-hover);
background: rgba(255, 255, 255, 0.06); text-decoration: none;
} }
.nav-item::before { .nav-item:hover .nav-item__icon {
content: ""; opacity: 1;
position: absolute;
left: 0;
top: 50%;
width: 4px;
height: 60%;
border-radius: 0 999px 999px 0;
transform: translateY(-50%);
background: transparent;
} }
.nav-item.active { .nav-item.active {
color: var(--text); color: var(--text-strong);
border-color: rgba(245, 159, 74, 0.45); background: var(--accent-subtle);
background: rgba(245, 159, 74, 0.12);
} }
.nav-item.active::before { .nav-item.active .nav-item__icon {
background: var(--accent); opacity: 1;
box-shadow: 0 0 12px rgba(245, 159, 74, 0.4); color: var(--accent);
} }
/* ===========================================
Content Area
=========================================== */
.content { .content {
grid-area: content; grid-area: content;
padding: 8px 6px 20px; padding: 8px 8px 24px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 20px; gap: 20px;
min-height: 0; min-height: 0;
overflow-y: auto; /* Enable vertical scrolling for pages with long content */ overflow-y: auto;
overflow-x: hidden; overflow-x: hidden;
} }
/* Chat handles its own scrolling (chat-thread); avoid double scrollbars. */ :root[data-theme="light"] .content {
background: var(--bg-content);
}
.content--chat { .content--chat {
overflow: hidden; overflow: hidden;
padding-bottom: 0;
} }
.shell--chat .content { /* Content header */
/* No-op: keep chat layout consistent with other tabs */
}
.content-header { .content-header {
display: flex; display: flex;
align-items: flex-end; align-items: flex-end;
justify-content: space-between; justify-content: space-between;
gap: 12px; gap: 16px;
padding: 0 6px; padding: 4px 8px;
overflow: hidden; overflow: hidden;
transform-origin: top center; transform-origin: top center;
transition: opacity var(--shell-focus-duration) var(--shell-focus-ease), transition:
opacity var(--shell-focus-duration) var(--shell-focus-ease),
transform var(--shell-focus-duration) var(--shell-focus-ease), transform var(--shell-focus-duration) var(--shell-focus-ease),
max-height var(--shell-focus-duration) var(--shell-focus-ease), max-height var(--shell-focus-duration) var(--shell-focus-ease),
padding var(--shell-focus-duration) var(--shell-focus-ease); padding var(--shell-focus-duration) var(--shell-focus-ease);
max-height: 90px; max-height: 80px;
} }
.shell--chat-focus .content-header { .shell--chat-focus .content-header {
opacity: 0; opacity: 0;
transform: translateY(-10px); transform: translateY(-8px);
max-height: 0px; max-height: 0px;
padding: 0; padding: 0;
pointer-events: none; pointer-events: none;
} }
.page-title { .page-title {
font-family: var(--font-display); font-size: 24px;
font-size: 26px; font-weight: 600;
letter-spacing: 0.6px; letter-spacing: -0.02em;
line-height: 1.2;
color: var(--text-strong);
} }
.page-sub { .page-sub {
color: var(--muted); color: var(--muted);
font-size: 12px; font-size: 13px;
letter-spacing: 0.4px; font-weight: 400;
margin-top: 4px;
} }
.page-meta { .page-meta {
display: flex; display: flex;
gap: 10px; gap: 8px;
} }
/* Chat view: header and controls side by side */ /* Chat view header adjustments */
.content--chat .content-header { .content--chat .content-header {
flex-direction: row; flex-direction: row;
align-items: center; align-items: center;
@@ -410,9 +495,13 @@
flex-shrink: 0; flex-shrink: 0;
} }
/* ===========================================
Grid Utilities
=========================================== */
.grid { .grid {
display: grid; display: grid;
gap: 18px; gap: 16px;
} }
.grid-cols-2 { .grid-cols-2 {
@@ -425,39 +514,42 @@
.stat-grid { .stat-grid {
display: grid; display: grid;
gap: 14px; gap: 12px;
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
} }
.note-grid { .note-grid {
display: grid; display: grid;
gap: 14px; gap: 12px;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
} }
.row { .row {
display: flex; display: flex;
gap: 12px; gap: 10px;
align-items: center; align-items: center;
} }
.stack { .stack {
display: grid; display: grid;
gap: 14px; gap: 12px;
} }
.filters { .filters {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
gap: 10px; gap: 8px;
align-items: center; align-items: center;
} }
/* ===========================================
Responsive - Tablet
=========================================== */
@media (max-width: 1100px) { @media (max-width: 1100px) {
.shell { .shell {
--shell-pad: 12px; --shell-pad: 12px;
--shell-gap: 12px; --shell-gap: 12px;
--shell-nav-col: 1fr;
grid-template-columns: 1fr; grid-template-columns: 1fr;
grid-template-rows: auto auto 1fr; grid-template-rows: auto auto 1fr;
grid-template-areas: grid-template-areas:
@@ -470,17 +562,18 @@
position: static; position: static;
max-height: none; max-height: none;
display: flex; display: flex;
gap: 16px; gap: 6px;
overflow-x: auto; overflow-x: auto;
border-right: none; border-right: none;
padding: 12px; border-bottom: 1px solid var(--border);
padding: 10px 14px;
background: var(--bg);
} }
.nav-group { .nav-group {
grid-auto-flow: column; grid-auto-flow: column;
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
border-bottom: none; margin-bottom: 0;
padding-bottom: 0;
} }
.grid-cols-2, .grid-cols-2,
@@ -490,13 +583,11 @@
.topbar { .topbar {
position: static; position: static;
flex-direction: column; padding: 12px 14px;
align-items: flex-start; gap: 10px;
gap: 12px;
} }
.topbar-status { .topbar-status {
width: 100%;
flex-wrap: wrap; flex-wrap: wrap;
} }
+121 -61
View File
@@ -1,12 +1,15 @@
/* Tablet/Mobile nav fix (under 1100px) - single horizontal scroll row */ /* ===========================================
Mobile Layout
=========================================== */
/* Tablet: Horizontal nav */
@media (max-width: 1100px) { @media (max-width: 1100px) {
/* Flatten nav into single horizontal scroll */
.nav { .nav {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
flex-wrap: nowrap; flex-wrap: nowrap;
gap: 6px; gap: 4px;
padding: 10px 12px; padding: 10px 14px;
overflow-x: auto; overflow-x: auto;
-webkit-overflow-scrolling: touch; -webkit-overflow-scrolling: touch;
scrollbar-width: none; scrollbar-width: none;
@@ -16,21 +19,18 @@
display: none; display: none;
} }
/* Nav groups should flow inline, not stack */
.nav-group { .nav-group {
display: contents; /* Flatten group wrapper - items flow directly into .nav */ display: contents;
} }
.nav-group__items { .nav-group__items {
display: contents; /* Flatten items wrapper too */ display: contents;
} }
/* Hide group labels on tablet/mobile */
.nav-label { .nav-label {
display: none; display: none;
} }
/* Don't hide nav items even if group is "collapsed" */
.nav-group--collapsed .nav-group__items { .nav-group--collapsed .nav-group__items {
display: contents; display: contents;
} }
@@ -38,27 +38,22 @@
.nav-item { .nav-item {
padding: 8px 14px; padding: 8px 14px;
font-size: 13px; font-size: 13px;
border-radius: 10px; border-radius: var(--radius-md);
white-space: nowrap; white-space: nowrap;
flex-shrink: 0; flex-shrink: 0;
} }
.nav-item::before {
display: none;
}
} }
/* Mobile-specific improvements */ /* Mobile-specific styles */
@media (max-width: 600px) { @media (max-width: 600px) {
.shell { .shell {
--shell-pad: 8px; --shell-pad: 8px;
--shell-gap: 8px; --shell-gap: 8px;
} }
/* Compact topbar for mobile */ /* Topbar */
.topbar { .topbar {
padding: 10px 12px; padding: 10px 12px;
border-radius: 12px;
gap: 8px; gap: 8px;
flex-direction: row; flex-direction: row;
flex-wrap: wrap; flex-wrap: wrap;
@@ -72,8 +67,7 @@
} }
.brand-title { .brand-title {
font-size: 15px; font-size: 14px;
letter-spacing: 0.3px;
} }
.brand-sub { .brand-sub {
@@ -100,11 +94,10 @@
display: none; display: none;
} }
/* Horizontal scrollable nav for mobile */ /* Nav */
.nav { .nav {
padding: 8px; padding: 8px 10px;
border-radius: 12px; gap: 4px;
gap: 8px;
-webkit-overflow-scrolling: touch; -webkit-overflow-scrolling: touch;
scrollbar-width: none; scrollbar-width: none;
} }
@@ -122,18 +115,14 @@
} }
.nav-item { .nav-item {
padding: 7px 10px; padding: 6px 10px;
font-size: 12px; font-size: 12px;
border-radius: 8px; border-radius: var(--radius-md);
white-space: nowrap; white-space: nowrap;
flex-shrink: 0; flex-shrink: 0;
} }
.nav-item::before { /* Content */
display: none;
}
/* Hide page title on mobile - nav already shows where you are */
.content-header { .content-header {
display: none; display: none;
} }
@@ -143,17 +132,17 @@
gap: 12px; gap: 12px;
} }
/* Smaller cards on mobile */ /* Cards */
.card { .card {
padding: 12px; padding: 12px;
border-radius: 12px; border-radius: var(--radius-md);
} }
.card-title { .card-title {
font-size: 14px; font-size: 13px;
} }
/* Stat grid adjustments */ /* Stats */
.stat-grid { .stat-grid {
gap: 8px; gap: 8px;
grid-template-columns: repeat(2, 1fr); grid-template-columns: repeat(2, 1fr);
@@ -161,24 +150,24 @@
.stat { .stat {
padding: 10px; padding: 10px;
border-radius: 10px; border-radius: var(--radius-md);
} }
.stat-label { .stat-label {
font-size: 10px; font-size: 11px;
} }
.stat-value { .stat-value {
font-size: 16px; font-size: 18px;
} }
/* Notes grid */ /* Notes */
.note-grid { .note-grid {
grid-template-columns: 1fr; grid-template-columns: 1fr;
gap: 10px; gap: 8px;
} }
/* Form fields */ /* Forms */
.form-grid { .form-grid {
grid-template-columns: 1fr; grid-template-columns: 1fr;
gap: 10px; gap: 10px;
@@ -188,14 +177,14 @@
.field textarea, .field textarea,
.field select { .field select {
padding: 8px 10px; padding: 8px 10px;
border-radius: 10px; border-radius: var(--radius-md);
font-size: 14px; font-size: 14px;
} }
/* Buttons */ /* Buttons */
.btn { .btn {
padding: 8px 12px; padding: 8px 12px;
font-size: 13px; font-size: 12px;
} }
/* Pills */ /* Pills */
@@ -204,7 +193,7 @@
font-size: 12px; font-size: 12px;
} }
/* Chat-specific mobile improvements */ /* Chat */
.chat-header { .chat-header {
flex-direction: column; flex-direction: column;
align-items: stretch; align-items: stretch;
@@ -227,17 +216,16 @@
.chat-thread { .chat-thread {
margin-top: 8px; margin-top: 8px;
padding: 10px 8px; padding: 12px 8px;
border-radius: 12px;
} }
.chat-msg { .chat-msg {
max-width: 92%; max-width: 90%;
} }
.chat-bubble { .chat-bubble {
padding: 8px 10px; padding: 8px 12px;
border-radius: 12px; border-radius: var(--radius-md);
} }
.chat-compose { .chat-compose {
@@ -247,14 +235,14 @@
.chat-compose__field textarea { .chat-compose__field textarea {
min-height: 60px; min-height: 60px;
padding: 8px 10px; padding: 8px 10px;
border-radius: 12px; border-radius: var(--radius-md);
font-size: 14px; font-size: 14px;
} }
/* Log stream mobile */ /* Log stream */
.log-stream { .log-stream {
border-radius: 10px; border-radius: var(--radius-md);
max-height: 400px; max-height: 380px;
} }
.log-row { .log-row {
@@ -279,14 +267,14 @@
font-size: 12px; font-size: 12px;
} }
/* List items */ /* Lists */
.list-item { .list-item {
padding: 10px; padding: 10px;
border-radius: 10px; border-radius: var(--radius-md);
} }
.list-title { .list-title {
font-size: 14px; font-size: 13px;
} }
.list-sub { .list-sub {
@@ -296,19 +284,91 @@
/* Code blocks */ /* Code blocks */
.code-block { .code-block {
padding: 8px; padding: 8px;
border-radius: 10px; border-radius: var(--radius-md);
font-size: 11px; font-size: 11px;
} }
/* Theme toggle smaller */ /* Theme toggle */
.theme-toggle { .theme-toggle {
--theme-item: 24px; --theme-item: 24px;
--theme-gap: 4px; --theme-gap: 2px;
--theme-pad: 4px; --theme-pad: 3px;
} }
.theme-icon { .theme-icon {
width: 14px; width: 12px;
height: 14px; height: 12px;
}
}
/* Small mobile */
@media (max-width: 400px) {
.shell {
--shell-pad: 4px;
}
.topbar {
padding: 8px 10px;
}
.brand-title {
font-size: 13px;
}
.nav {
padding: 6px 8px;
}
.nav-item {
padding: 6px 8px;
font-size: 11px;
}
.content {
padding: 4px 4px 12px;
gap: 10px;
}
.card {
padding: 10px;
}
.stat {
padding: 8px;
}
.stat-value {
font-size: 16px;
}
.chat-bubble {
padding: 8px 10px;
}
.chat-compose__field textarea {
min-height: 52px;
padding: 8px 10px;
font-size: 13px;
}
.btn {
padding: 6px 10px;
font-size: 11px;
}
.topbar-status .pill {
padding: 3px 6px;
font-size: 10px;
}
.theme-toggle {
--theme-item: 22px;
--theme-gap: 2px;
--theme-pad: 2px;
}
.theme-icon {
width: 11px;
height: 11px;
} }
} }
+3 -2
View File
@@ -3,6 +3,7 @@ import { repeat } from "lit/directives/repeat.js";
import type { AppViewState } from "./app-view-state"; import type { AppViewState } from "./app-view-state";
import { iconForTab, pathForTab, titleForTab, type Tab } from "./navigation"; import { iconForTab, pathForTab, titleForTab, type Tab } from "./navigation";
import { icons } from "./icons";
import { loadChatHistory } from "./controllers/chat"; import { loadChatHistory } from "./controllers/chat";
import { syncUrlWithSessionKey } from "./app-settings"; import { syncUrlWithSessionKey } from "./app-settings";
import type { SessionsListResult } from "./types"; import type { SessionsListResult } from "./types";
@@ -31,7 +32,7 @@ export function renderTab(state: AppViewState, tab: Tab) {
}} }}
title=${titleForTab(tab)} title=${titleForTab(tab)}
> >
<span class="nav-item__icon" aria-hidden="true">${iconForTab(tab)}</span> <span class="nav-item__icon" aria-hidden="true">${icons[iconForTab(tab)]}</span>
<span class="nav-item__text">${titleForTab(tab)}</span> <span class="nav-item__text">${titleForTab(tab)}</span>
</a> </a>
`; `;
@@ -108,7 +109,7 @@ export function renderChatControls(state: AppViewState) {
? "Disabled during onboarding" ? "Disabled during onboarding"
: "Toggle assistant thinking/working output"} : "Toggle assistant thinking/working output"}
> >
🧠 ${icons.brain}
</button> </button>
<button <button
class="btn btn--sm btn--icon ${focusActive ? "active" : ""}" class="btn btn--sm btn--icon ${focusActive ? "active" : ""}"
+10 -4
View File
@@ -11,6 +11,7 @@ import {
titleForTab, titleForTab,
type Tab, type Tab,
} from "./navigation"; } from "./navigation";
import { icons } from "./icons";
import type { UiSettings } from "./storage"; import type { UiSettings } from "./storage";
import type { ThemeMode } from "./theme"; import type { ThemeMode } from "./theme";
import type { ThemeTransitionContext } from "./theme-transition"; import type { ThemeTransitionContext } from "./theme-transition";
@@ -124,11 +125,16 @@ export function renderApp(state: AppViewState) {
title="${state.settings.navCollapsed ? "Expand sidebar" : "Collapse sidebar"}" title="${state.settings.navCollapsed ? "Expand sidebar" : "Collapse sidebar"}"
aria-label="${state.settings.navCollapsed ? "Expand sidebar" : "Collapse sidebar"}" aria-label="${state.settings.navCollapsed ? "Expand sidebar" : "Collapse sidebar"}"
> >
<span class="nav-collapse-toggle__icon">☰</span> <span class="nav-collapse-toggle__icon">${icons.menu}</span>
</button> </button>
<div class="brand"> <div class="brand">
<div class="brand-title">CLAWDBOT</div> <div class="brand-logo">
<div class="brand-sub">Gateway Dashboard</div> <img src="/pixel-lobster.svg" alt="Clawdbot" />
</div>
<div class="brand-text">
<div class="brand-title">CLAWDBOT</div>
<div class="brand-sub">Gateway Dashboard</div>
</div>
</div> </div>
</div> </div>
<div class="topbar-status"> <div class="topbar-status">
@@ -179,7 +185,7 @@ export function renderApp(state: AppViewState) {
rel="noreferrer" rel="noreferrer"
title="Docs (opens in new tab)" title="Docs (opens in new tab)"
> >
<span class="nav-item__icon" aria-hidden="true">📚</span> <span class="nav-item__icon" aria-hidden="true">${icons.book}</span>
<span class="nav-item__text">Docs</span> <span class="nav-item__text">Docs</span>
</a> </a>
</div> </div>
+6 -10
View File
@@ -1,14 +1,11 @@
import { html, type TemplateResult } from "lit"; import { html, type TemplateResult } from "lit";
import { renderEmojiIcon, setEmojiIcon } from "../icons"; import { icons } from "../icons";
const COPIED_FOR_MS = 1500; const COPIED_FOR_MS = 1500;
const ERROR_FOR_MS = 2000; const ERROR_FOR_MS = 2000;
const COPY_LABEL = "Copy as markdown"; const COPY_LABEL = "Copy as markdown";
const COPIED_LABEL = "Copied"; const COPIED_LABEL = "Copied";
const ERROR_LABEL = "Copy failed"; const ERROR_LABEL = "Copy failed";
const COPY_ICON = "📋";
const COPIED_ICON = "✓";
const ERROR_ICON = "!";
type CopyButtonOptions = { type CopyButtonOptions = {
text: () => string; text: () => string;
@@ -41,7 +38,7 @@ function createCopyButton(options: CopyButtonOptions): TemplateResult {
aria-label=${idleLabel} aria-label=${idleLabel}
@click=${async (e: Event) => { @click=${async (e: Event) => {
const btn = e.currentTarget as HTMLButtonElement | null; const btn = e.currentTarget as HTMLButtonElement | null;
const icon = btn?.querySelector( const iconContainer = btn?.querySelector(
".chat-copy-btn__icon", ".chat-copy-btn__icon",
) as HTMLElement | null; ) as HTMLElement | null;
@@ -61,30 +58,29 @@ function createCopyButton(options: CopyButtonOptions): TemplateResult {
if (!copied) { if (!copied) {
btn.dataset.error = "1"; btn.dataset.error = "1";
setButtonLabel(btn, ERROR_LABEL); setButtonLabel(btn, ERROR_LABEL);
setEmojiIcon(icon, ERROR_ICON);
window.setTimeout(() => { window.setTimeout(() => {
if (!btn.isConnected) return; if (!btn.isConnected) return;
delete btn.dataset.error; delete btn.dataset.error;
setButtonLabel(btn, idleLabel); setButtonLabel(btn, idleLabel);
setEmojiIcon(icon, COPY_ICON);
}, ERROR_FOR_MS); }, ERROR_FOR_MS);
return; return;
} }
btn.dataset.copied = "1"; btn.dataset.copied = "1";
setButtonLabel(btn, COPIED_LABEL); setButtonLabel(btn, COPIED_LABEL);
setEmojiIcon(icon, COPIED_ICON);
window.setTimeout(() => { window.setTimeout(() => {
if (!btn.isConnected) return; if (!btn.isConnected) return;
delete btn.dataset.copied; delete btn.dataset.copied;
setButtonLabel(btn, idleLabel); setButtonLabel(btn, idleLabel);
setEmojiIcon(icon, COPY_ICON);
}, COPIED_FOR_MS); }, COPIED_FOR_MS);
}} }}
> >
${renderEmojiIcon(COPY_ICON, "chat-copy-btn__icon")} <span class="chat-copy-btn__icon" aria-hidden="true">
<span class="chat-copy-btn__icon-copy">${icons.copy}</span>
<span class="chat-copy-btn__icon-check">${icons.check}</span>
</span>
</button> </button>
`; `;
} }
+4 -3
View File
@@ -1,6 +1,7 @@
import { html, nothing } from "lit"; import { html, nothing } from "lit";
import { formatToolDetail, resolveToolDisplay } from "../tool-display"; import { formatToolDetail, resolveToolDisplay } from "../tool-display";
import { icons } from "../icons";
import type { ToolCard } from "../types/chat-types"; import type { ToolCard } from "../types/chat-types";
import { TOOL_INLINE_THRESHOLD } from "./constants"; import { TOOL_INLINE_THRESHOLD } from "./constants";
import { import {
@@ -95,13 +96,13 @@ export function renderToolCardSidebar(
> >
<div class="chat-tool-card__header"> <div class="chat-tool-card__header">
<div class="chat-tool-card__title"> <div class="chat-tool-card__title">
<span class="chat-tool-card__icon">${display.emoji}</span> <span class="chat-tool-card__icon">${icons[display.icon]}</span>
<span>${display.label}</span> <span>${display.label}</span>
</div> </div>
${canClick ${canClick
? html`<span class="chat-tool-card__action">${hasText ? "View ›" : "›"}</span>` ? html`<span class="chat-tool-card__action">${hasText ? "View" : ""} ${icons.check}</span>`
: nothing} : nothing}
${isEmpty && !canClick ? html`<span class="chat-tool-card__status">✓</span>` : nothing} ${isEmpty && !canClick ? html`<span class="chat-tool-card__status">${icons.check}</span>` : nothing}
</div> </div>
${detail ${detail
? html`<div class="chat-tool-card__detail">${detail}</div>` ? html`<div class="chat-tool-card__detail">${detail}</div>`
+54 -2
View File
@@ -1,7 +1,59 @@
import { html, type TemplateResult } from "lit"; import { html, type TemplateResult } from "lit";
export function renderEmojiIcon(icon: string, className: string): TemplateResult { // Lucide-style SVG icons
return html`<span class=${className} aria-hidden="true">${icon}</span>`; // All icons use currentColor for stroke
export const icons = {
// Navigation icons
messageSquare: html`<svg viewBox="0 0 24 24"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>`,
barChart: html`<svg viewBox="0 0 24 24"><line x1="12" x2="12" y1="20" y2="10"/><line x1="18" x2="18" y1="20" y2="4"/><line x1="6" x2="6" y1="20" y2="16"/></svg>`,
link: html`<svg viewBox="0 0 24 24"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/></svg>`,
radio: html`<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="2"/><path d="M16.24 7.76a6 6 0 0 1 0 8.49m-8.48-.01a6 6 0 0 1 0-8.49m11.31-2.82a10 10 0 0 1 0 14.14m-14.14 0a10 10 0 0 1 0-14.14"/></svg>`,
fileText: html`<svg viewBox="0 0 24 24"><path d="M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z"/><polyline points="14 2 14 8 20 8"/><line x1="16" x2="8" y1="13" y2="13"/><line x1="16" x2="8" y1="17" y2="17"/><line x1="10" x2="8" y1="9" y2="9"/></svg>`,
zap: html`<svg viewBox="0 0 24 24"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/></svg>`,
monitor: html`<svg viewBox="0 0 24 24"><rect width="20" height="14" x="2" y="3" rx="2"/><line x1="8" x2="16" y1="21" y2="21"/><line x1="12" x2="12" y1="17" y2="21"/></svg>`,
settings: html`<svg viewBox="0 0 24 24"><path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z"/><circle cx="12" cy="12" r="3"/></svg>`,
bug: html`<svg viewBox="0 0 24 24"><path d="m8 2 1.88 1.88"/><path d="M14.12 3.88 16 2"/><path d="M9 7.13v-1a3.003 3.003 0 1 1 6 0v1"/><path d="M12 20c-3.3 0-6-2.7-6-6v-3a4 4 0 0 1 4-4h4a4 4 0 0 1 4 4v3c0 3.3-2.7 6-6 6"/><path d="M12 20v-9"/><path d="M6.53 9C4.6 8.8 3 7.1 3 5"/><path d="M6 13H2"/><path d="M3 21c0-2.1 1.7-3.9 3.8-4"/><path d="M20.97 5c0 2.1-1.6 3.8-3.5 4"/><path d="M22 13h-4"/><path d="M17.2 17c2.1.1 3.8 1.9 3.8 4"/></svg>`,
scrollText: html`<svg viewBox="0 0 24 24"><path d="M8 21h12a2 2 0 0 0 2-2v-2H10v2a2 2 0 1 1-4 0V5a2 2 0 1 0-4 0v3h4"/><path d="M19 17V5a2 2 0 0 0-2-2H4"/><path d="M15 8h-5"/><path d="M15 12h-5"/></svg>`,
folder: html`<svg viewBox="0 0 24 24"><path d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z"/></svg>`,
// UI icons
menu: html`<svg viewBox="0 0 24 24"><line x1="4" x2="20" y1="12" y2="12"/><line x1="4" x2="20" y1="6" y2="6"/><line x1="4" x2="20" y1="18" y2="18"/></svg>`,
x: html`<svg viewBox="0 0 24 24"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>`,
check: html`<svg viewBox="0 0 24 24"><path d="M20 6 9 17l-5-5"/></svg>`,
copy: html`<svg viewBox="0 0 24 24"><rect width="14" height="14" x="8" y="8" rx="2" ry="2"/><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/></svg>`,
search: html`<svg viewBox="0 0 24 24"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></svg>`,
brain: html`<svg viewBox="0 0 24 24"><path d="M12 5a3 3 0 1 0-5.997.125 4 4 0 0 0-2.526 5.77 4 4 0 0 0 .556 6.588A4 4 0 1 0 12 18Z"/><path d="M12 5a3 3 0 1 1 5.997.125 4 4 0 0 1 2.526 5.77 4 4 0 0 1-.556 6.588A4 4 0 1 1 12 18Z"/><path d="M15 13a4.5 4.5 0 0 1-3-4 4.5 4.5 0 0 1-3 4"/><path d="M17.599 6.5a3 3 0 0 0 .399-1.375"/><path d="M6.003 5.125A3 3 0 0 0 6.401 6.5"/><path d="M3.477 10.896a4 4 0 0 1 .585-.396"/><path d="M19.938 10.5a4 4 0 0 1 .585.396"/><path d="M6 18a4 4 0 0 1-1.967-.516"/><path d="M19.967 17.484A4 4 0 0 1 18 18"/></svg>`,
book: html`<svg viewBox="0 0 24 24"><path d="M4 19.5v-15A2.5 2.5 0 0 1 6.5 2H20v20H6.5a2.5 2.5 0 0 1 0-5H20"/></svg>`,
loader: html`<svg viewBox="0 0 24 24"><path d="M12 2v4"/><path d="m16.2 7.8 2.9-2.9"/><path d="M18 12h4"/><path d="m16.2 16.2 2.9 2.9"/><path d="M12 18v4"/><path d="m4.9 19.1 2.9-2.9"/><path d="M2 12h4"/><path d="m4.9 4.9 2.9 2.9"/></svg>`,
// Tool icons
wrench: html`<svg viewBox="0 0 24 24"><path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"/></svg>`,
fileCode: html`<svg viewBox="0 0 24 24"><path d="M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z"/><polyline points="14 2 14 8 20 8"/><path d="m10 13-2 2 2 2"/><path d="m14 17 2-2-2-2"/></svg>`,
edit: html`<svg viewBox="0 0 24 24"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>`,
penLine: html`<svg viewBox="0 0 24 24"><path d="M12 20h9"/><path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z"/></svg>`,
paperclip: html`<svg viewBox="0 0 24 24"><path d="m21.44 11.05-9.19 9.19a6 6 0 0 1-8.49-8.49l8.57-8.57A4 4 0 1 1 18 8.84l-8.59 8.57a2 2 0 0 1-2.83-2.83l8.49-8.48"/></svg>`,
globe: html`<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><path d="M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20"/><path d="M2 12h20"/></svg>`,
image: html`<svg viewBox="0 0 24 24"><rect width="18" height="18" x="3" y="3" rx="2" ry="2"/><circle cx="9" cy="9" r="2"/><path d="m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21"/></svg>`,
smartphone: html`<svg viewBox="0 0 24 24"><rect width="14" height="20" x="5" y="2" rx="2" ry="2"/><path d="M12 18h.01"/></svg>`,
plug: html`<svg viewBox="0 0 24 24"><path d="M12 22v-5"/><path d="M9 8V2"/><path d="M15 8V2"/><path d="M18 8v5a4 4 0 0 1-4 4h-4a4 4 0 0 1-4-4V8Z"/></svg>`,
circle: html`<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/></svg>`,
puzzle: html`<svg viewBox="0 0 24 24"><path d="M19.439 7.85c-.049.322.059.648.289.878l1.568 1.568c.47.47.706 1.087.706 1.704s-.235 1.233-.706 1.704l-1.611 1.611a.98.98 0 0 1-.837.276c-.47-.07-.802-.48-.968-.925a2.501 2.501 0 1 0-3.214 3.214c.446.166.855.497.925.968a.979.979 0 0 1-.276.837l-1.61 1.61a2.404 2.404 0 0 1-1.705.707 2.402 2.402 0 0 1-1.704-.706l-1.568-1.568a1.026 1.026 0 0 0-.877-.29c-.493.074-.84.504-1.02.968a2.5 2.5 0 1 1-3.237-3.237c.464-.18.894-.527.967-1.02a1.026 1.026 0 0 0-.289-.877l-1.568-1.568A2.402 2.402 0 0 1 1.998 12c0-.617.236-1.234.706-1.704L4.23 8.77c.24-.24.581-.353.917-.303.515.076.874.54 1.02 1.02a2.5 2.5 0 1 0 3.237-3.237c-.48-.146-.944-.505-1.02-1.02a.98.98 0 0 1 .303-.917l1.526-1.526A2.402 2.402 0 0 1 11.998 2c.617 0 1.234.236 1.704.706l1.568 1.568c.23.23.556.338.877.29.493-.074.84-.504 1.02-.968a2.5 2.5 0 1 1 3.236 3.236c-.464.18-.894.527-.967 1.02Z"/></svg>`,
} as const;
export type IconName = keyof typeof icons;
export function icon(name: IconName): TemplateResult {
return icons[name];
}
export function renderIcon(name: IconName, className = "nav-item__icon"): TemplateResult {
return html`<span class=${className} aria-hidden="true">${icons[name]}</span>`;
}
// Legacy function for compatibility
export function renderEmojiIcon(iconContent: string | TemplateResult, className: string): TemplateResult {
return html`<span class=${className} aria-hidden="true">${iconContent}</span>`;
} }
export function setEmojiIcon(target: HTMLElement | null, icon: string): void { export function setEmojiIcon(target: HTMLElement | null, icon: string): void {
+15 -13
View File
@@ -1,3 +1,5 @@
import type { IconName } from "./icons.js";
export const TAB_GROUPS = [ export const TAB_GROUPS = [
{ label: "Chat", tabs: ["chat"] }, { label: "Chat", tabs: ["chat"] },
{ {
@@ -98,32 +100,32 @@ export function inferBasePathFromPathname(pathname: string): string {
return `/${segments.join("/")}`; return `/${segments.join("/")}`;
} }
export function iconForTab(tab: Tab): string { export function iconForTab(tab: Tab): IconName {
switch (tab) { switch (tab) {
case "chat": case "chat":
return "💬"; return "messageSquare";
case "overview": case "overview":
return "📊"; return "barChart";
case "channels": case "channels":
return "🔗"; return "link";
case "instances": case "instances":
return "📡"; return "radio";
case "sessions": case "sessions":
return "📄"; return "fileText";
case "cron": case "cron":
return "⏰"; return "loader";
case "skills": case "skills":
return "⚡️"; return "zap";
case "nodes": case "nodes":
return "🖥️"; return "monitor";
case "config": case "config":
return "⚙️"; return "settings";
case "debug": case "debug":
return "🐞"; return "bug";
case "logs": case "logs":
return "🧾"; return "scrollText";
default: default:
return "📁"; return "folder";
} }
} }
+15 -15
View File
@@ -1,7 +1,7 @@
{ {
"version": 1, "version": 1,
"fallback": { "fallback": {
"emoji": "🧩", "icon": "puzzle",
"detailKeys": [ "detailKeys": [
"command", "command",
"path", "path",
@@ -26,37 +26,37 @@
}, },
"tools": { "tools": {
"bash": { "bash": {
"emoji": "🛠️", "icon": "wrench",
"title": "Bash", "title": "Bash",
"detailKeys": ["command"] "detailKeys": ["command"]
}, },
"process": { "process": {
"emoji": "🧰", "icon": "wrench",
"title": "Process", "title": "Process",
"detailKeys": ["sessionId"] "detailKeys": ["sessionId"]
}, },
"read": { "read": {
"emoji": "📖", "icon": "fileText",
"title": "Read", "title": "Read",
"detailKeys": ["path"] "detailKeys": ["path"]
}, },
"write": { "write": {
"emoji": "✍️", "icon": "edit",
"title": "Write", "title": "Write",
"detailKeys": ["path"] "detailKeys": ["path"]
}, },
"edit": { "edit": {
"emoji": "📝", "icon": "penLine",
"title": "Edit", "title": "Edit",
"detailKeys": ["path"] "detailKeys": ["path"]
}, },
"attach": { "attach": {
"emoji": "📎", "icon": "paperclip",
"title": "Attach", "title": "Attach",
"detailKeys": ["path", "url", "fileName"] "detailKeys": ["path", "url", "fileName"]
}, },
"browser": { "browser": {
"emoji": "🌐", "icon": "globe",
"title": "Browser", "title": "Browser",
"actions": { "actions": {
"status": { "label": "status" }, "status": { "label": "status" },
@@ -95,7 +95,7 @@
} }
}, },
"canvas": { "canvas": {
"emoji": "🖼️", "icon": "image",
"title": "Canvas", "title": "Canvas",
"actions": { "actions": {
"present": { "label": "present", "detailKeys": ["target", "node", "nodeId"] }, "present": { "label": "present", "detailKeys": ["target", "node", "nodeId"] },
@@ -108,7 +108,7 @@
} }
}, },
"nodes": { "nodes": {
"emoji": "📱", "icon": "smartphone",
"title": "Nodes", "title": "Nodes",
"actions": { "actions": {
"status": { "label": "status" }, "status": { "label": "status" },
@@ -127,7 +127,7 @@
} }
}, },
"cron": { "cron": {
"emoji": "⏰", "icon": "loader",
"title": "Cron", "title": "Cron",
"actions": { "actions": {
"status": { "label": "status" }, "status": { "label": "status" },
@@ -144,7 +144,7 @@
} }
}, },
"gateway": { "gateway": {
"emoji": "🔌", "icon": "plug",
"title": "Gateway", "title": "Gateway",
"actions": { "actions": {
"restart": { "label": "restart", "detailKeys": ["reason", "delayMs"] }, "restart": { "label": "restart", "detailKeys": ["reason", "delayMs"] },
@@ -161,7 +161,7 @@
} }
}, },
"whatsapp_login": { "whatsapp_login": {
"emoji": "🟢", "icon": "circle",
"title": "WhatsApp Login", "title": "WhatsApp Login",
"actions": { "actions": {
"start": { "label": "start" }, "start": { "label": "start" },
@@ -169,7 +169,7 @@
} }
}, },
"discord": { "discord": {
"emoji": "💬", "icon": "messageSquare",
"title": "Discord", "title": "Discord",
"actions": { "actions": {
"react": { "label": "react", "detailKeys": ["channelId", "messageId", "emoji"] }, "react": { "label": "react", "detailKeys": ["channelId", "messageId", "emoji"] },
@@ -204,7 +204,7 @@
} }
}, },
"slack": { "slack": {
"emoji": "💬", "icon": "messageSquare",
"title": "Slack", "title": "Slack",
"actions": { "actions": {
"react": { "label": "react", "detailKeys": ["channelId", "messageId", "emoji"] }, "react": { "label": "react", "detailKeys": ["channelId", "messageId", "emoji"] },
+7 -8
View File
@@ -1,4 +1,5 @@
import rawConfig from "./tool-display.json"; import rawConfig from "./tool-display.json";
import type { IconName } from "./icons";
type ToolDisplayActionSpec = { type ToolDisplayActionSpec = {
label?: string; label?: string;
@@ -6,7 +7,7 @@ type ToolDisplayActionSpec = {
}; };
type ToolDisplaySpec = { type ToolDisplaySpec = {
emoji?: string; icon?: string;
title?: string; title?: string;
label?: string; label?: string;
detailKeys?: string[]; detailKeys?: string[];
@@ -21,7 +22,7 @@ type ToolDisplayConfig = {
export type ToolDisplay = { export type ToolDisplay = {
name: string; name: string;
emoji: string; icon: IconName;
title: string; title: string;
label: string; label: string;
verb?: string; verb?: string;
@@ -29,7 +30,7 @@ export type ToolDisplay = {
}; };
const TOOL_DISPLAY_CONFIG = rawConfig as ToolDisplayConfig; const TOOL_DISPLAY_CONFIG = rawConfig as ToolDisplayConfig;
const FALLBACK = TOOL_DISPLAY_CONFIG.fallback ?? { emoji: "🧩" }; const FALLBACK = TOOL_DISPLAY_CONFIG.fallback ?? { icon: "puzzle" };
const TOOL_MAP = TOOL_DISPLAY_CONFIG.tools ?? {}; const TOOL_MAP = TOOL_DISPLAY_CONFIG.tools ?? {};
function normalizeToolName(name?: string): string { function normalizeToolName(name?: string): string {
@@ -135,7 +136,7 @@ export function resolveToolDisplay(params: {
const name = normalizeToolName(params.name); const name = normalizeToolName(params.name);
const key = name.toLowerCase(); const key = name.toLowerCase();
const spec = TOOL_MAP[key]; const spec = TOOL_MAP[key];
const emoji = spec?.emoji ?? FALLBACK.emoji ?? "🧩"; const icon = (spec?.icon ?? FALLBACK.icon ?? "puzzle") as IconName;
const title = spec?.title ?? defaultTitle(name); const title = spec?.title ?? defaultTitle(name);
const label = spec?.label ?? name; const label = spec?.label ?? name;
const actionRaw = const actionRaw =
@@ -168,7 +169,7 @@ export function resolveToolDisplay(params: {
return { return {
name, name,
emoji, icon,
title, title,
label, label,
verb, verb,
@@ -186,9 +187,7 @@ export function formatToolDetail(display: ToolDisplay): string | undefined {
export function formatToolSummary(display: ToolDisplay): string { export function formatToolSummary(display: ToolDisplay): string {
const detail = formatToolDetail(display); const detail = formatToolDetail(display);
return detail return detail ? `${display.label}: ${detail}` : display.label;
? `${display.emoji} ${display.label}: ${detail}`
: `${display.emoji} ${display.label}`;
} }
function shortenHomeInString(input: string): string { function shortenHomeInString(input: string): string {
+7 -6
View File
@@ -3,6 +3,7 @@ import { repeat } from "lit/directives/repeat.js";
import type { SessionsListResult } from "../types"; import type { SessionsListResult } from "../types";
import type { ChatQueueItem } from "../ui-types"; import type { ChatQueueItem } from "../ui-types";
import type { ChatItem, MessageGroup } from "../types/chat-types"; import type { ChatItem, MessageGroup } from "../types/chat-types";
import { icons } from "../icons";
import { import {
normalizeMessage, normalizeMessage,
normalizeRoleForGrouping, normalizeRoleForGrouping,
@@ -74,18 +75,18 @@ function renderCompactionIndicator(status: CompactionIndicatorStatus | null | un
if (status.active) { if (status.active) {
return html` return html`
<div class="callout info compaction-indicator compaction-indicator--active"> <div class="callout info compaction-indicator compaction-indicator--active">
🧹 Compacting context... ${icons.loader} Compacting context...
</div> </div>
`; `;
} }
// Show "compaction complete" briefly after completion // Show "compaction complete" briefly after completion
if (status.completedAt) { if (status.completedAt) {
const elapsed = Date.now() - status.completedAt; const elapsed = Date.now() - status.completedAt;
if (elapsed < COMPACTION_TOAST_DURATION_MS) { if (elapsed < COMPACTION_TOAST_DURATION_MS) {
return html` return html`
<div class="callout success compaction-indicator compaction-indicator--complete"> <div class="callout success compaction-indicator compaction-indicator--complete">
🧹 Context compacted ${icons.check} Context compacted
</div> </div>
`; `;
} }
@@ -171,7 +172,7 @@ export function renderChat(props: ChatProps) {
aria-label="Exit focus mode" aria-label="Exit focus mode"
title="Exit focus mode" title="Exit focus mode"
> >
✕ ${icons.x}
</button> </button>
` `
: nothing} : nothing}
@@ -223,7 +224,7 @@ export function renderChat(props: ChatProps) {
aria-label="Remove queued message" aria-label="Remove queued message"
@click=${() => props.onQueueRemove(item.id)} @click=${() => props.onQueueRemove(item.id)}
> >
✕ ${icons.x}
</button> </button>
</div> </div>
`, `,
@@ -265,7 +266,7 @@ export function renderChat(props: ChatProps) {
?disabled=${!props.connected} ?disabled=${!props.connected}
@click=${props.onSend} @click=${props.onSend}
> >
${isBusy ? "Queue" : "Send"} ${isBusy ? "Queue" : "Send"}<kbd class="btn-kbd">↵</kbd>
</button> </button>
</div> </div>
</div> </div>
+2 -1
View File
@@ -1,5 +1,6 @@
import { html, nothing } from "lit"; import { html, nothing } from "lit";
import type { ConfigUiHints } from "../types"; import type { ConfigUiHints } from "../types";
import { icons } from "../icons";
import { import {
hintForPath, hintForPath,
humanize, humanize,
@@ -189,7 +190,7 @@ export function renderConfigForm(props: ConfigFormProps) {
if (filteredEntries.length === 0) { if (filteredEntries.length === 0) {
return html` return html`
<div class="config-empty"> <div class="config-empty">
<div class="config-empty__icon">🔍</div> <div class="config-empty__icon">${icons.search}</div>
<div class="config-empty__text"> <div class="config-empty__text">
${searchQuery ${searchQuery
? `No settings match "${searchQuery}"` ? `No settings match "${searchQuery}"`
+2 -1
View File
@@ -1,6 +1,7 @@
import { html, nothing } from "lit"; import { html, nothing } from "lit";
import { unsafeHTML } from "lit/directives/unsafe-html.js"; import { unsafeHTML } from "lit/directives/unsafe-html.js";
import { icons } from "../icons";
import { toSanitizedMarkdownHtml } from "../markdown"; import { toSanitizedMarkdownHtml } from "../markdown";
export type MarkdownSidebarProps = { export type MarkdownSidebarProps = {
@@ -16,7 +17,7 @@ export function renderMarkdownSidebar(props: MarkdownSidebarProps) {
<div class="sidebar-header"> <div class="sidebar-header">
<div class="sidebar-title">Tool Output</div> <div class="sidebar-title">Tool Output</div>
<button @click=${props.onClose} class="btn" title="Close sidebar"> <button @click=${props.onClose} class="btn" title="Close sidebar">
✕ ${icons.x}
</button> </button>
</div> </div>
<div class="sidebar-content"> <div class="sidebar-content">