@moonshot-ai/kimi-code 0.33.0 → 0.35.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/main.mjs +25293 -18171
- package/dist/search-worker.mjs +13529 -0
- package/dist-web/assets/{CodeBlockNode-pVrDnT5M.js → CodeBlockNode-BAtAs_qm.js} +2 -2
- package/dist-web/assets/DesignSystemView-CTUhpkDe.js +13 -0
- package/dist-web/assets/DesignSystemView-DVONbdv-.css +1 -0
- package/dist-web/assets/{Tooltip-DP9j1yrm.js → Tooltip-DbYQWF1U.js} +1 -1
- package/dist-web/assets/{abnfDiagram-VRR7QNED-BHRZ0kDP.js → abnfDiagram-VRR7QNED-C0Afmuc1.js} +1 -1
- package/dist-web/assets/{arc-DC-qDOw3.js → arc-IkhU3FHH.js} +1 -1
- package/dist-web/assets/{architectureDiagram-ZJ3FMSHR-DMo1NOCy.js → architectureDiagram-ZJ3FMSHR-CBluWNBt.js} +1 -1
- package/dist-web/assets/{blockDiagram-677ZJIJ3-BB6RrvwG.js → blockDiagram-677ZJIJ3-BNXb88Fr.js} +1 -1
- package/dist-web/assets/{c4Diagram-LMCZKHZV-CTIN-u6u.js → c4Diagram-LMCZKHZV-CUyKVoVi.js} +1 -1
- package/dist-web/assets/channel-xkK6nTGq.js +1 -0
- package/dist-web/assets/{chunk-2Q5K7J3B-BJB08iqf.js → chunk-2Q5K7J3B-DsAC7dRk.js} +1 -1
- package/dist-web/assets/{chunk-32BRIVSS-D2R4swWR.js → chunk-32BRIVSS-DUDRPqmY.js} +1 -1
- package/dist-web/assets/{chunk-5VM5RSS4-DNp0lB9r.js → chunk-5VM5RSS4-yyj9cAyF.js} +1 -1
- package/dist-web/assets/{chunk-EX3LRPZG-DTDAbra3.js → chunk-EX3LRPZG-BCWDroXJ.js} +1 -1
- package/dist-web/assets/{chunk-JWPE2WC7-BiIZjhuH.js → chunk-JWPE2WC7-Dsg3gA8l.js} +1 -1
- package/dist-web/assets/{chunk-MOJQB5TN-CAYle-Q0.js → chunk-MOJQB5TN-JQ2kJR9W.js} +1 -1
- package/dist-web/assets/{chunk-RYQCIY6F-nfBZcgde.js → chunk-RYQCIY6F-Df2V79id.js} +1 -1
- package/dist-web/assets/{chunk-V7JOEXUC-DIDk-4Yh.js → chunk-V7JOEXUC-B4q9plWN.js} +1 -1
- package/dist-web/assets/{chunk-VR4S4FIN-Cveor6Gg.js → chunk-VR4S4FIN-CEH7JYJn.js} +1 -1
- package/dist-web/assets/{chunk-XXDRQBXY-CcyRETTg.js → chunk-XXDRQBXY-5rh7CWvm.js} +1 -1
- package/dist-web/assets/classDiagram-OUVF2IWQ-ClMG95L0.js +1 -0
- package/dist-web/assets/classDiagram-v2-EOCWNBFH-ClMG95L0.js +1 -0
- package/dist-web/assets/{cose-bilkent-JH36ORCC-QGxEehq4.js → cose-bilkent-JH36ORCC-TWQPJk-P.js} +1 -1
- package/dist-web/assets/{cynefin-VYW2F7L2-B2u2H2Wf.js → cynefin-VYW2F7L2-BIlq342y.js} +1 -1
- package/dist-web/assets/{cynefinDiagram-TSTJHNR4-Dkif7MEa.js → cynefinDiagram-TSTJHNR4-zQaCQNIP.js} +1 -1
- package/dist-web/assets/{dagre-VKFMJZFB-BbS-B0nZ.js → dagre-VKFMJZFB-D8gdq5tS.js} +1 -1
- package/dist-web/assets/{diagram-FQU43EPY-BgGpYQg7.js → diagram-FQU43EPY-D2bRXH1a.js} +1 -1
- package/dist-web/assets/{diagram-G47NLZAW-DLPxt9_R.js → diagram-G47NLZAW-BF9x_uf7.js} +1 -1
- package/dist-web/assets/{diagram-NH7WQ7WH-CkFCAjqP.js → diagram-NH7WQ7WH-DUn2m-AO.js} +1 -1
- package/dist-web/assets/{diagram-OA4YK3LP-XuNp_Rrf.js → diagram-OA4YK3LP-BOIp7TNe.js} +1 -1
- package/dist-web/assets/{diagram-WEI45ONY-BxyPHaUz.js → diagram-WEI45ONY-CFwFRAWa.js} +1 -1
- package/dist-web/assets/{ebnfDiagram-CCIWWBDH-um5jZ6Ya.js → ebnfDiagram-CCIWWBDH-B4NTctc_.js} +1 -1
- package/dist-web/assets/{erDiagram-Q63AITRT-ClZyl3be.js → erDiagram-Q63AITRT-DVzumNgk.js} +1 -1
- package/dist-web/assets/{flowDiagram-23GEKE2U-Cda1-vDp.js → flowDiagram-23GEKE2U-CzI-GKO4.js} +1 -1
- package/dist-web/assets/{ganttDiagram-NO4QXBWP-BRVPZvRL.js → ganttDiagram-NO4QXBWP-B2lfrNfh.js} +1 -1
- package/dist-web/assets/{gitGraphDiagram-IHSO6WYX-BfyW9kzv.js → gitGraphDiagram-IHSO6WYX-D7UBC8np.js} +1 -1
- package/dist-web/assets/{index-DUhLXAkw.js → index-BZFTzQ6y.js} +1 -1
- package/dist-web/assets/index-D-7nOosq.js +638 -0
- package/dist-web/assets/index-DGHD7Bg9.css +1 -0
- package/dist-web/assets/{index-CALEJJyh.js → index-DzfhniX8.js} +2 -2
- package/dist-web/assets/{index-ZTpPpnYb.js → index-ZmzTmhry.js} +1 -1
- package/dist-web/assets/{index10-BA5VPLT7.js → index10-BCo1_xRY.js} +1 -1
- package/dist-web/assets/{index11-BTFBrOND.js → index11-Ci8_PlMN.js} +1 -1
- package/dist-web/assets/{index5-RZzprraT.js → index5-Cn2jfVMX.js} +1 -1
- package/dist-web/assets/{index6-CHOJPToe.js → index6-D4fZsFMu.js} +1 -1
- package/dist-web/assets/{index7-BqOx7RWV.js → index7-BT2SBznQ.js} +1 -1
- package/dist-web/assets/{index8-CNrUHH_V.js → index8-BaK3y7fN.js} +1 -1
- package/dist-web/assets/{infoDiagram-FWYZ7A6U-BWKmR6mQ.js → infoDiagram-FWYZ7A6U-DASw56fH.js} +1 -1
- package/dist-web/assets/{ishikawaDiagram-FXEZZL3T-Dxub-WlA.js → ishikawaDiagram-FXEZZL3T-CcPuml-k.js} +1 -1
- package/dist-web/assets/{journeyDiagram-5HDEW3XC-BbQqB9Jp.js → journeyDiagram-5HDEW3XC-BShuBRgf.js} +1 -1
- package/dist-web/assets/{kanban-definition-HUTT4EX6-D9rO7JqK.js → kanban-definition-HUTT4EX6-_UoHLqzR.js} +1 -1
- package/dist-web/assets/{linear-CmRMBRqQ.js → linear-DH49UJnN.js} +1 -1
- package/dist-web/assets/{mermaid.core-BMAf1qng.js → mermaid.core-CJB1tAev.js} +4 -4
- package/dist-web/assets/{mindmap-definition-LN4V7U3C-DTp1GWYE.js → mindmap-definition-LN4V7U3C-HXhM1kRL.js} +1 -1
- package/dist-web/assets/{pegDiagram-2B236MQR-CZ-v0hli.js → pegDiagram-2B236MQR-_6D7zUy-.js} +1 -1
- package/dist-web/assets/{pieDiagram-ENE6RG2P-DQ4-sW9o.js → pieDiagram-ENE6RG2P-f3F4At6v.js} +1 -1
- package/dist-web/assets/{quadrantDiagram-ABIIQ3AL-BLnW7KuD.js → quadrantDiagram-ABIIQ3AL-3t7sFhfl.js} +1 -1
- package/dist-web/assets/{railroadDiagram-RFXS5EU6-BslILK0i.js → railroadDiagram-RFXS5EU6-W9nf8fYD.js} +1 -1
- package/dist-web/assets/{requirementDiagram-TGXJPOKE-CRL6Ek64.js → requirementDiagram-TGXJPOKE-Bzvt0v7J.js} +1 -1
- package/dist-web/assets/{sankeyDiagram-HTMAVEWB-CnczhSio.js → sankeyDiagram-HTMAVEWB-B5WnWxzh.js} +1 -1
- package/dist-web/assets/{sequenceDiagram-DBY2YBRQ-qM_aECMO.js → sequenceDiagram-DBY2YBRQ-CnV0H-kS.js} +1 -1
- package/dist-web/assets/{sizeCapture-X5ZJPWSS-CiMXtF_n.js → sizeCapture-X5ZJPWSS-D5GqjpM0.js} +1 -1
- package/dist-web/assets/{stateDiagram-2N3HPSRC-FiChRpmX.js → stateDiagram-2N3HPSRC-GIVsAB2M.js} +1 -1
- package/dist-web/assets/stateDiagram-v2-6OUMAXLB-0KuGlzV7.js +1 -0
- package/dist-web/assets/{swimlanes-5IMT3BWC-DnYOgNnC.js → swimlanes-5IMT3BWC-D6xMtJ1E.js} +2 -2
- package/dist-web/assets/swimlanesDiagram-G3AALYLV-DKIx012r.js +8 -0
- package/dist-web/assets/{timeline-definition-FHXFAJF6-Dhsd8f4I.js → timeline-definition-FHXFAJF6-5u0AN8o0.js} +1 -1
- package/dist-web/assets/{vennDiagram-L72KCM5P-BOIhPKpV.js → vennDiagram-L72KCM5P-ozNTLnJz.js} +1 -1
- package/dist-web/assets/{vue.runtime.esm-bundler-0N7BeFaa.js → vue.runtime.esm-bundler-J0WjtLlK.js} +1 -1
- package/dist-web/assets/{wardleyDiagram-EHGQE667-DElAUW3r.js → wardleyDiagram-EHGQE667-BuQJYWm-.js} +1 -1
- package/dist-web/assets/{xychartDiagram-FW5EYKEG-DiJMaRBQ.js → xychartDiagram-FW5EYKEG-yQImOWPy.js} +1 -1
- package/dist-web/boot.js +1 -1
- package/dist-web/index.html +2 -2
- package/package.json +7 -7
- package/dist-web/assets/DesignSystemView-B7NHKYWW.js +0 -13
- package/dist-web/assets/DesignSystemView-BOdJHjg1.css +0 -1
- package/dist-web/assets/channel-DV0CaBk6.js +0 -1
- package/dist-web/assets/classDiagram-OUVF2IWQ-DSlPMBy0.js +0 -1
- package/dist-web/assets/classDiagram-v2-EOCWNBFH-DSlPMBy0.js +0 -1
- package/dist-web/assets/index-B-HzRssS.js +0 -626
- package/dist-web/assets/index-C6xpOIeW.css +0 -1
- package/dist-web/assets/stateDiagram-v2-6OUMAXLB-CDYO-PcE.js +0 -1
- package/dist-web/assets/swimlanesDiagram-G3AALYLV-D6x5uNZn.js +0 -8
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import{M as T,aD as z,aI as q,aL as o,u as i,v as a,G as t,H as s,F as p,aX as w,bb as y,I as c,bk as b,cx as h,cy as B,cz as k,cA as A,cB as M}from"./index-B-HzRssS.js";const I={class:"ds-page"},V={class:"layout"},E={class:"content"},H={class:"content-inner"},L={id:"tokens"},D={class:"icon-sizes"},W={class:"sz"},O={class:"p-ic",style:{width:"14px",height:"14px"},viewBox:"0 0 24 24",fill:"currentColor"},R={class:"sz"},U={class:"p-ic",style:{width:"16px",height:"16px"},viewBox:"0 0 24 24",fill:"currentColor"},N={class:"sz"},F={class:"p-ic",style:{width:"20px",height:"20px"},viewBox:"0 0 24 24",fill:"currentColor"},P={class:"icon-grid"},j={class:"icon-group-label"},K={class:"ic-name"},G={id:"primitives"},_={class:"stage-wrap"},J={class:"stage p"},Q={class:"p-pill",style:{color:"var(--p-warning)"}},Y={class:"stage-wrap"},Z={class:"stage p col"},X={class:"demo-row"},$={class:"p-btn primary disabled"},ee={class:"p-spinner sm",viewBox:"0 0 24 24",style:{"--p-accent":"#fff","--p-line":"rgba(255,255,255,.35)"}},ae={class:"stage-wrap"},te={class:"stage p col"},se={class:"demo-row"},de={class:"stage-wrap"},oe={class:"stage p col",style:{gap:"0",background:"var(--p-surface)",padding:"0","max-width":"300px","align-items":"stretch"}},ie={style:{display:"flex","align-items":"center",gap:"8px",padding:"7px 10px",margin:"1px 6px","border-radius":"8px",color:"var(--p-text)","font-size":"13px"}},ne={style:{color:"var(--d-fg-faint)",flex:"none"},width:"14",height:"14",viewBox:"0 0 24 24",fill:"currentColor"},le={style:{display:"flex","align-items":"center",gap:"8px",padding:"7px 10px",margin:"1px 6px","border-radius":"8px",color:"var(--p-text)","font-size":"13px"}},re={style:{color:"var(--d-fg-faint)",flex:"none"},width:"14",height:"14",viewBox:"0 0 24 24",fill:"currentColor"},ce={id:"chat"},ve={class:"stage-wrap"},be={class:"stage p col"},pe={style:{"max-width":"560px",width:"100%"}},he={class:"stage-wrap"},ue={class:"stage p col",style:{"align-items":"center",background:"#fff"}},ge={class:"p-composer",style:{width:"100%","max-width":"620px"}},me={class:"p-composer-bar"},fe={class:"p-composer-left"},we={class:"p-pill",style:{color:"var(--p-warning)"}},ye="/repo",ke=T({__name:"DesignSystemView",emits:["close"],setup(xe,{emit:x}){const C=[{path:"/repo/apps/web/src/components/chat/TurnFilesSummary.vue",added:19,removed:4,hasWrite:!1,statsIncomplete:!1,diff:null},{path:"/repo/apps/web/src/composables/useFilePreview.ts",added:8,removed:1,hasWrite:!1,statsIncomplete:!1,diff:null},{path:"/repo/apps/web/src/components/chatTurnRendering.ts",added:0,removed:0,hasWrite:!0,statsIncomplete:!0,diff:null},{path:"/repo/apps/web/src/lib/toolDiff.ts",added:3,removed:2,hasWrite:!1,statsIncomplete:!1,diff:null}];function u(){}const S=x;function g(){S("close")}let v=null;function m(r){r.key==="Escape"&&g()}return z(()=>{document.addEventListener("keydown",m);const r=Array.prototype.slice.call(document.querySelectorAll('#nav a[href^="#"]')),e=new Map;r.forEach(l=>{const d=l.getAttribute("href");if(!d)return;const f=document.getElementById(d.slice(1));f&&e.set(f,l)});let n=null;v=new IntersectionObserver(l=>{l.forEach(d=>{d.isIntersecting&&(n&&n.classList.remove("active"),n=e.get(d.target)??null,n&&n.classList.add("active"))})},{rootMargin:"-20% 0px -70% 0px",threshold:0}),e.forEach((l,d)=>v.observe(d)),r.length&&r[0].classList.add("active")}),q(()=>{document.removeEventListener("keydown",m),v&&(v.disconnect(),v=null)}),(r,e)=>(o(),i("div",I,[a("div",{class:"ds-topbar"},[a("button",{class:"ds-back",type:"button",onClick:g},"← Back"),e[0]||(e[0]=a("span",{class:"ds-topbar-title"},"Design system",-1))]),a("div",V,[e[44]||(e[44]=t('<aside class="sidebar" data-v-be77961e><div class="brand" data-v-be77961e><div class="brand-mark" data-v-be77961e>K</div><div class="brand-name" data-v-be77961e>Kimi Web</div></div><div class="brand-sub" data-v-be77961e>Design System · v1.0</div><div class="nav-group" data-v-be77961e>Navigate</div><nav class="nav" id="nav" data-v-be77961e><a href="#overview" data-v-be77961e><span class="num" data-v-be77961e>00</span>Overview</a><a href="#principles" data-v-be77961e><span class="num" data-v-be77961e>01</span>Design Principles</a><a href="#tokens" data-v-be77961e><span class="num" data-v-be77961e>02</span>Design Tokens</a><a href="#primitives" data-v-be77961e><span class="num" data-v-be77961e>03</span>Primitives</a><a href="#chat" data-v-be77961e><span class="num" data-v-be77961e>04</span>Chat Interface</a><a href="#themes" data-v-be77961e><span class="num" data-v-be77961e>05</span>Theming</a><a href="#rules" data-v-be77961e><span class="num" data-v-be77961e>06</span>Style Rules</a><a href="#shell" data-v-be77961e><span class="num" data-v-be77961e>07</span>App Shell & Sidebar</a><a href="#a11y" data-v-be77961e><span class="num" data-v-be77961e>08</span>Accessibility</a><a href="#dialogs" data-v-be77961e><span class="num" data-v-be77961e>09</span>Dialogs</a></nav><div class="nav-group" data-v-be77961e>Companion output</div><nav class="nav" data-v-be77961e><a href="#tokens" data-v-be77961e><span class="num" data-v-be77961e>↗</span>Token list</a><a href="#primitives" data-v-be77961e><span class="num" data-v-be77961e>↗</span>Component API</a><a href="#rules" data-v-be77961e><span class="num" data-v-be77961e>↗</span>Style rules</a></nav></aside>',1)),a("main",E,[a("div",H,[e[42]||(e[42]=t('<section id="overview" data-v-be77961e><div class="hero" data-v-be77961e><span class="eyebrow" data-v-be77961e>● Design System · v1.0</span><h1 data-v-be77961e>Kimi Web <span class="grad" data-v-be77961e>Design System</span></h1><p class="lead" data-v-be77961e> This document defines the visual language and component specification for Kimi Web — design tokens, component primitives, the chat interface, theming, and style rules. All UI work is grounded in it: unified, restrained, token-driven, and themeable. </p><div class="hero-meta" data-v-be77961e><span class="meta-chip" data-v-be77961e><span class="dot" data-v-be77961e></span> Scope <b data-v-be77961e>apps/kimi-web</b></span><span class="meta-chip" data-v-be77961e>Component primitives</span><span class="meta-chip" data-v-be77961e>Theme <b data-v-be77961e>1 set · 4 customizable colors</b></span><span class="meta-chip" data-v-be77961e>Light / dark mode</span></div></div><div class="callout info" data-v-be77961e><span class="ico" data-v-be77961e>i</span><div data-v-be77961e><b data-v-be77961e>This spec is the single reference when changing the web UI.</b> Before adding or modifying a component, style, layout, or theme, read this document first; color, font, radius, spacing, shadow, z-index, and motion always use the §02 tokens, components reuse the §03 primitives, and the §06 style rules are followed. </div></div></section><section id="principles" data-v-be77961e><div class="sec-head" data-v-be77961e><span class="sec-num" data-v-be77961e>01</span><h2 class="sec-title" data-v-be77961e>Design Principles</h2></div><p class="sec-desc" data-v-be77961e> Every UI decision traces back to the following principles. Kimi Web is a local Agent tool for developers: quick scanning, long stretches of staring, often in the dark — the design serves the task, and is restrained, clinical, and density-first. </p><ul class="clean check" data-v-be77961e><li data-v-be77961e><b data-v-be77961e>Consistency</b> —— The same semantics use the same component. The primary button, dialog, input, and badge should each have exactly "one" correct way to be written across the entire site.</li><li data-v-be77961e><b data-v-be77961e>Hierarchy</b> —— Build a clear hierarchy through size, weight, color, and whitespace; emphasize through "restraint" rather than "bolder and bigger".</li><li data-v-be77961e><b data-v-be77961e>Proximity</b> —— Group related elements, leave whitespace between unrelated ones. A card's padding, line spacing, and group spacing all come from the same spacing scale.</li><li data-v-be77961e><b data-v-be77961e>Feedback</b> —— hover / active / focus / loading / success / error all have visible states, and the state language is unified.</li><li data-v-be77961e><b data-v-be77961e>Breathing room</b> —— Control density with the spacing scale rather than arbitrary pixels; prefer restrained whitespace over cramming controls together.</li><li data-v-be77961e><b data-v-be77961e>Accessibility (A11y)</b> —— Text contrast ≥ 4.5:1, visible focus rings, touch targets ≥ 32px, and states that don't rely on color alone.</li><li data-v-be77961e><b data-v-be77961e>Reduction</b> —— The number of colors, radii, shadow levels, and type sizes all converge to a finite set of tokens; delete stray values.</li></ul><div class="callout good" data-v-be77961e><span class="ico" data-v-be77961e>✓</span><div data-v-be77961e><b data-v-be77961e>Brand tone (the do-not list)</b>: calm, clinical, never exaggerated. <span class="pill red" style="margin:0 4px;" data-v-be77961e>Reject</span> purple gradients, glassmorphism, glowing shadows, AI purple / blue glows, endlessly looping fussy micro-animations, "Boost your productivity"-style marketing copy, and using emoji as icons. These are all common tells of AI-generated interfaces (an "AI tell"), deliberately avoided. </div></div><div class="callout info" data-v-be77961e><span class="ico" data-v-be77961e>i</span><div data-v-be77961e><b data-v-be77961e>Declare design intent first (Design Read)</b>: before adding a component / page, write one sentence describing its scenario, audience, and tone (for example, "a lightweight tool card embedded in a conversation, for developers, calm and restrained"), then build. If the intent isn't clear, ask one question first rather than defaulting to the nearest existing style. </div></div></section>',2)),a("section",L,[e[7]||(e[7]=t(`<div class="sec-head" data-v-be77961e><span class="sec-num" data-v-be77961e>02</span><h2 class="sec-title" data-v-be77961e>Design Tokens</h2></div><p class="sec-desc" data-v-be77961e> Collapse every visual decision into tokens. <b data-v-be77961e>Color tokens keep the existing short names and fill out the semantics</b> (lowering migration cost), while <b data-v-be77961e>spacing, z-index, motion, and font-weight</b> fill in the scales that are currently missing. Every token has: name, light value, dark value, and usage. </p><div class="callout info" data-v-be77961e><span class="ico" data-v-be77961e>i</span><div data-v-be77961e><b data-v-be77961e>Naming convention</b>: <code data-v-be77961e>--<category>-<role>-<state></code>. For example <code data-v-be77961e>--color-text-muted</code>, <code data-v-be77961e>--radius-md</code>, <code data-v-be77961e>--space-4</code>. To reduce churn, the existing short names (<code data-v-be77961e>--bg</code> / <code data-v-be77961e>--ink</code> / <code data-v-be77961e>--line</code> / <code data-v-be77961e>--blue</code> …) are kept as <b data-v-be77961e>compatibility aliases</b> for one release cycle. </div></div><h3 class="sub" data-v-be77961e>Color</h3><p data-v-be77961e>Semantic-first, in three layers: <b data-v-be77961e>background / text / border</b> + <b data-v-be77961e>accent</b> + <b data-v-be77961e>status colors</b>. All colors are defined in light / dark pairs, with contrast ≥ 4.5:1.</p><div class="callout info" data-v-be77961e><span class="ico" data-v-be77961e>i</span><div data-v-be77961e>The table below shows the <b data-v-be77961e>semantic tokens</b>. Each ships a light value in <code data-v-be77961e>:root</code> and a dark override in the <code data-v-be77961e>data-color-scheme</code> blocks — for example <code data-v-be77961e>--color-bg</code> is <code data-v-be77961e>#ffffff</code> in light and <code data-v-be77961e>#121212</code> in dark; <code data-v-be77961e>--color-accent</code> is the brand blue (<code data-v-be77961e>#1783ff</code> light / <code data-v-be77961e>#1a88ff</code> dark). The <b data-v-be77961e>semantic status colors</b> (success / warning / danger / info) are independent palettes, one set each for light / dark.</div></div><div class="palette" data-v-be77961e><div class="color-card" data-v-be77961e><div class="color-chip" style="background:#ffffff;" data-v-be77961e></div><div class="color-meta" data-v-be77961e><div class="cn" data-v-be77961e>bg</div><div class="cv" data-v-be77961e>#ffffff / #121212</div></div></div><div class="color-card" data-v-be77961e><div class="color-chip" style="background:#f5f5f5;" data-v-be77961e></div><div class="color-meta" data-v-be77961e><div class="cn" data-v-be77961e>surface</div><div class="cv" data-v-be77961e>#f5f5f5 / #1f1f1f</div></div></div><div class="color-card" data-v-be77961e><div class="color-chip" style="background:#f5f5f5;" data-v-be77961e></div><div class="color-meta" data-v-be77961e><div class="cn" data-v-be77961e>surface-sunken</div><div class="cv" data-v-be77961e>#f5f5f5 / #121212</div></div></div><div class="color-card" data-v-be77961e><div class="color-chip" style="background:#f5f5f5;" data-v-be77961e></div><div class="color-meta" data-v-be77961e><div class="cn" data-v-be77961e>well</div><div class="cv" data-v-be77961e>#f5f5f5 / #1f1f1f</div></div></div><div class="color-card" data-v-be77961e><div class="color-chip" style="background:#f5f5f5;" data-v-be77961e></div><div class="color-meta" data-v-be77961e><div class="cn" data-v-be77961e>surface-deep</div><div class="cv" data-v-be77961e>#f5f5f5 / #0d0d0d</div></div></div><div class="color-card" data-v-be77961e><div class="color-chip" style="background:#fff;border:0.5px solid rgba(0,0,0,.13);" data-v-be77961e></div><div class="color-meta" data-v-be77961e><div class="cn" data-v-be77961e>surface-overlay</div><div class="cv" data-v-be77961e>#ffffff / rgba(255,255,255,.1)</div></div></div><div class="color-card" data-v-be77961e><div class="color-chip" style="background:rgba(0,0,0,.05);" data-v-be77961e></div><div class="color-meta" data-v-be77961e><div class="cn" data-v-be77961e>selected</div><div class="cv" data-v-be77961e>rgba(0,0,0,.05) / rgba(255,255,255,.1)</div></div></div><div class="color-card" data-v-be77961e><div class="color-chip" style="background:rgba(0,0,0,.9);" data-v-be77961e></div><div class="color-meta" data-v-be77961e><div class="cn" data-v-be77961e>fg</div><div class="cv" data-v-be77961e>rgba(0,0,0,.9) / rgba(255,255,255,.84)</div></div></div><div class="color-card" data-v-be77961e><div class="color-chip" style="background:rgba(0,0,0,.6);" data-v-be77961e></div><div class="color-meta" data-v-be77961e><div class="cn" data-v-be77961e>fg-muted</div><div class="cv" data-v-be77961e>rgba(0,0,0,.6) / rgba(255,255,255,.56)</div></div></div><div class="color-card" data-v-be77961e><div class="color-chip" style="background:rgba(0,0,0,.13);" data-v-be77961e></div><div class="color-meta" data-v-be77961e><div class="cn" data-v-be77961e>line</div><div class="cv" data-v-be77961e>rgba(0,0,0,.13) / rgba(255,255,255,.12)</div></div></div><div class="color-card" data-v-be77961e><div class="color-chip" style="background:rgba(0,0,0,.05);" data-v-be77961e></div><div class="color-meta" data-v-be77961e><div class="cn" data-v-be77961e>subtle</div><div class="cv" data-v-be77961e>rgba(0,0,0,.05) / rgba(255,255,255,.05)</div></div></div><div class="color-card" data-v-be77961e><div class="color-chip" style="background:#1783ff;" data-v-be77961e></div><div class="color-meta" data-v-be77961e><div class="cn" data-v-be77961e>accent (KMBlue)</div><div class="cv" data-v-be77961e>#1783ff / #1a88ff</div></div></div><div class="color-card" data-v-be77961e><div class="color-chip" style="background:#e8f3ff;" data-v-be77961e></div><div class="color-meta" data-v-be77961e><div class="cn" data-v-be77961e>accent-soft</div><div class="cv" data-v-be77961e>#e8f3ff / rgba(26,136,255,.1)</div></div></div></div><table class="dt" data-v-be77961e><thead data-v-be77961e><tr data-v-be77961e><th data-v-be77961e>Token</th><th data-v-be77961e>Light</th><th data-v-be77961e>Dark</th><th data-v-be77961e>Usage</th></tr></thead><tbody data-v-be77961e><tr data-v-be77961e><td class="tk" data-v-be77961e>--color-bg</td><td class="val" data-v-be77961e><span class="swatch" style="background:#fff;" data-v-be77961e></span>#ffffff</td><td class="val" data-v-be77961e><span class="swatch" style="background:#121212;" data-v-be77961e></span>#121212</td><td data-v-be77961e>Page background</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>--color-surface</td><td class="val" data-v-be77961e><span class="swatch" style="background:#f5f5f5;" data-v-be77961e></span>#f5f5f5</td><td class="val" data-v-be77961e><span class="swatch" style="background:#1f1f1f;" data-v-be77961e></span>#1f1f1f</td><td data-v-be77961e>Panel / sidebar / card head</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>--color-surface-raised</td><td class="val" data-v-be77961e><span class="swatch" style="background:#fff;" data-v-be77961e></span>#ffffff</td><td class="val" data-v-be77961e><span class="swatch" style="background:#292929;" data-v-be77961e></span>#292929</td><td data-v-be77961e>Raised card / dialog / input</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>--color-menu-bg</td><td class="val" data-v-be77961e><span class="swatch" style="background:rgba(255,255,255,.95);" data-v-be77961e></span>rgba(255,255,255,.95)</td><td class="val" data-v-be77961e><span class="swatch" style="background:rgba(41,41,41,.95);" data-v-be77961e></span>rgba(41,41,41,.95)</td><td data-v-be77961e>Floating menu panel — frosted glass over <code data-v-be77961e>--p-menu-backdrop</code> blur</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>--color-surface-overlay</td><td class="val" data-v-be77961e><span class="swatch" style="background:#fff;" data-v-be77961e></span>#ffffff</td><td class="val" data-v-be77961e><span class="swatch" style="background:rgba(255,255,255,.1);" data-v-be77961e></span>rgba(255,255,255,.1)</td><td data-v-be77961e>Field-control fill on raised cards (selects, steppers) — top rung; light tops out at white (the level is carried by the border), dark steps one rung above raised. Floating layers stay at raised</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>--color-well</td><td class="val" data-v-be77961e><span class="swatch" style="background:#f5f5f5;" data-v-be77961e></span>#f5f5f5</td><td class="val" data-v-be77961e><span class="swatch" style="background:#1f1f1f;" data-v-be77961e></span>#1f1f1f</td><td data-v-be77961e>Content well on the page (code blocks, tool-output panels, match/file lists, media thumbnails) — light reuses the sunken recess; dark lifts one rung ABOVE the page, because a true recess (<code data-v-be77961e>#121212</code>) vanishes into the page there</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>--color-surface-deep</td><td class="val" data-v-be77961e><span class="swatch" style="background:#f5f5f5;" data-v-be77961e></span>#f5f5f5</td><td class="val" data-v-be77961e><span class="swatch" style="background:#0d0d0d;" data-v-be77961e></span>#0d0d0d</td><td data-v-be77961e>Deep chrome plane one step BELOW the page (panel headers, diff gutters) — dark drops under <code data-v-be77961e>--color-bg</code> so chrome framing stays darker than the content it frames</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>--color-text</td><td class="val" data-v-be77961e><span class="swatch" style="background:rgba(0,0,0,.9);" data-v-be77961e></span>rgba(0,0,0,.9)</td><td class="val" data-v-be77961e><span class="swatch" style="background:rgba(255,255,255,.84);" data-v-be77961e></span>rgba(255,255,255,.84)</td><td data-v-be77961e>Body text / headings</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>--color-text-strong</td><td class="val" data-v-be77961e><span class="swatch" style="background:#000;" data-v-be77961e></span>#000000</td><td class="val" data-v-be77961e><span class="swatch" style="background:#fff;box-shadow:inset 0 0 0 1px #ddd;" data-v-be77961e></span>#ffffff</td><td data-v-be77961e>Max foreground emphasis — menu-row label & icon on hover</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>--color-text-muted</td><td class="val" data-v-be77961e><span class="swatch" style="background:rgba(0,0,0,.6);" data-v-be77961e></span>rgba(0,0,0,.6)</td><td class="val" data-v-be77961e><span class="swatch" style="background:rgba(255,255,255,.56);" data-v-be77961e></span>rgba(255,255,255,.56)</td><td data-v-be77961e>Secondary text / placeholder</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>--color-line</td><td class="val" data-v-be77961e><span class="swatch" style="background:rgba(0,0,0,.13);" data-v-be77961e></span>rgba(0,0,0,.13)</td><td class="val" data-v-be77961e><span class="swatch" style="background:rgba(255,255,255,.12);" data-v-be77961e></span>rgba(255,255,255,.12)</td><td data-v-be77961e>Divider / card border</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>--color-subtle</td><td class="val" data-v-be77961e><span class="swatch" style="background:rgba(0,0,0,.05);" data-v-be77961e></span>rgba(0,0,0,.05)</td><td class="val" data-v-be77961e><span class="swatch" style="background:rgba(255,255,255,.05);" data-v-be77961e></span>rgba(255,255,255,.05)</td><td data-v-be77961e>Subtle hairline — tertiary separators below <code data-v-be77961e>--color-line</code> (diff-gutter column rules, quiet dividers inside wells)</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>--color-selected</td><td class="val" data-v-be77961e><span class="swatch" style="background:rgba(0,0,0,.05);" data-v-be77961e></span>rgba(0,0,0,.05)</td><td class="val" data-v-be77961e><span class="swatch" style="background:rgba(255,255,255,.1);" data-v-be77961e></span>rgba(255,255,255,.1)</td><td data-v-be77961e>Neutral selected fill (sidebar rows, list pickers) — translucent, never accent-tinted</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>--color-hover</td><td class="val" data-v-be77961e><span class="swatch" style="background:rgba(0,0,0,.03);" data-v-be77961e></span>rgba(0,0,0,.03)</td><td class="val" data-v-be77961e><span class="swatch" style="background:rgba(255,255,255,.05);" data-v-be77961e></span>rgba(255,255,255,.05)</td><td data-v-be77961e>Row hover wash — lighter than the selected fill (hover < selected); translucent, sits on any surface. The global hover rule: transparent-base controls overlay this f1 wash (hover never darkens — never sunken); filled controls use their own hover token (accent-hover, send-bg-hover)</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>--color-inline-code-bg</td><td class="val" data-v-be77961e><span class="swatch" style="background:rgba(0,0,0,.03);" data-v-be77961e></span>rgba(0,0,0,.03)</td><td class="val" data-v-be77961e><span class="swatch" style="background:rgba(255,255,255,.1);" data-v-be77961e></span>rgba(255,255,255,.1)</td><td data-v-be77961e>Inline-code chip fill — fills.f1 / fills.f2; dark lifts off any dark surface (sunken == bg there)</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>--color-media-alpha-bg-1</td><td class="val" data-v-be77961e><span class="swatch" style="background:#858585;" data-v-be77961e></span>≈#858585</td><td class="val" data-v-be77961e><span class="swatch" style="background:#76797e;" data-v-be77961e></span>≈#76797e</td><td data-v-be77961e>Checkerboard square A of the <code data-v-be77961e><img></code> alpha canvas — color-mix of <code data-v-be77961e>--color-bg</code>/<code data-v-be77961e>--color-text</code> (52/48); applied via <code data-v-be77961e>--media-alpha-canvas</code> (16px period)</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>--color-media-alpha-bg-2</td><td class="val" data-v-be77961e><span class="swatch" style="background:#6b6b6b;" data-v-be77961e></span>≈#6b6b6b</td><td class="val" data-v-be77961e><span class="swatch" style="background:#8c8f93;" data-v-be77961e></span>≈#8c8f93</td><td data-v-be77961e>Checkerboard square B (42/58) — both squares stay ≥3:1 against white and black; opaque images cover the canvas</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>--color-sidebar-bg</td><td class="val" data-v-be77961e><span class="swatch" style="background:#f9fbfc;" data-v-be77961e></span>#f9fbfc</td><td class="val" data-v-be77961e><span class="swatch" style="background:#0d0d0d;" data-v-be77961e></span>#0d0d0d</td><td data-v-be77961e>Sidebar surface — one step off <code data-v-be77961e>--color-bg</code> (just under white in light, one step BELOW the page in dark) so the session column reads as its own plane and never brighter than the reading surface</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>--color-scrim</td><td class="val" data-v-be77961e><span class="swatch" style="background:rgba(0,0,0,.4);" data-v-be77961e></span>rgba(0,0,0,.4)</td><td class="val" data-v-be77961e><span class="swatch" style="background:rgba(0,0,0,.6);" data-v-be77961e></span>rgba(0,0,0,.6)</td><td data-v-be77961e>Modal scrim — the dark veil behind dialogs/lightboxes (mask.base; legacy hardcoded overlays can migrate here)</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>--color-scrim-strong</td><td class="val" data-v-be77961e><span class="swatch" style="background:rgba(0,0,0,.6);" data-v-be77961e></span>rgba(0,0,0,.6)</td><td class="val" data-v-be77961e><span class="swatch" style="background:rgba(0,0,0,.75);" data-v-be77961e></span>rgba(0,0,0,.75)</td><td data-v-be77961e>Stronger scrim for full-screen media previews (mask.strong — the PhotoSwipe image preview backdrop)</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>--color-text-on-scrim</td><td class="val" data-v-be77961e><span class="swatch" style="background:#fff;" data-v-be77961e></span>#ffffff</td><td class="val" data-v-be77961e><span class="swatch" style="background:#fff;" data-v-be77961e></span>same</td><td data-v-be77961e>Text drawn on the scrim (captions over the media lightbox)</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>--color-accent</td><td class="val" data-v-be77961e><span class="swatch" style="background:#1783ff;" data-v-be77961e></span>#1783ff</td><td class="val" data-v-be77961e><span class="swatch" style="background:#1a88ff;" data-v-be77961e></span>#1a88ff</td><td data-v-be77961e>Primary action / link / focus</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>--color-success</td><td class="val" data-v-be77961e><span class="swatch" style="background:#0e7a38;" data-v-be77961e></span>#0e7a38</td><td class="val" data-v-be77961e><span class="swatch" style="background:#3fb950;" data-v-be77961e></span>#3fb950</td><td data-v-be77961e>Success / pass</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>--color-warning</td><td class="val" data-v-be77961e><span class="swatch" style="background:#a9610a;" data-v-be77961e></span>#a9610a</td><td class="val" data-v-be77961e><span class="swatch" style="background:#d29922;" data-v-be77961e></span>#d29922</td><td data-v-be77961e>Warning / pending</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>--color-danger</td><td class="val" data-v-be77961e><span class="swatch" style="background:#c0392b;" data-v-be77961e></span>#c0392b</td><td class="val" data-v-be77961e><span class="swatch" style="background:#f85149;" data-v-be77961e></span>#f85149</td><td data-v-be77961e>Danger / error / abort</td></tr></tbody></table><h4 class="mini" data-v-be77961e>Palette</h4><p data-v-be77961e>The palette <b data-v-be77961e>is</b> the production kimi.com palette (design tokens <code data-v-be77961e>tokens.json</code>): neutral-gray surfaces, an alpha-based label / fill / separator ramp (<code data-v-be77961e>labels.*</code> / <code data-v-be77961e>fills.*</code> / <code data-v-be77961e>separator.s1</code>), the KMBlue accent, and a true neutral dark ladder (<code data-v-be77961e>#121212 → #1f1f1f → #292929</code>; the deep chrome plane and sidebar derive one step below at <code data-v-be77961e>#0d0d0d</code> — the palette has nothing darker than primary).</p><p data-v-be77961e>The ONE deliberate exception is the <b data-v-be77961e>status hues</b>: success / warning / danger / done keep the app's own WCAG-tuned ramp (≥4.5:1 on the neutral surfaces) — the production status colours (positiveGreen <code data-v-be77961e>#16c456</code>, orange <code data-v-be77961e>#ff9500</code>, danger red <code data-v-be77961e>#ff3849</code>) are too bright against it. Diff add/del bands happen to coincide (both use the production 25% fills in light, 14% in dark).</p><h4 class="mini" data-v-be77961e>Surface usage</h4><p data-v-be77961e>The surface layers each have a role — choose by "field overlay / raised layer / content well / default flat layer / sunken layer / page background / deep chrome", and avoid treating <code data-v-be77961e>--p-surface-raised</code> as a universal background. In dark, elevation = lighter: floating layers sit above the content, content wells sit above the page, and chrome planes (sidebar, panel headers) sit below it — never the reverse. One consequence: on the page itself, never use <code data-v-be77961e>--color-surface-sunken</code> for a content carrier — it equals <code data-v-be77961e>--color-bg</code> in dark and the fill vanishes; use <code data-v-be77961e>--color-well</code>. Sunken stays correct INSIDE surface / raised cards, where it is a genuine recess. Field controls (selects, steppers) on a raised card use <code data-v-be77961e>--color-surface-overlay</code>, the top fill rung; floating layers keep <code data-v-be77961e>--color-surface-raised</code> — their elevation is shadow + hairline, not a lighter fill.</p><table class="dt" data-v-be77961e><thead data-v-be77961e><tr data-v-be77961e><th data-v-be77961e>Token</th><th data-v-be77961e>Light</th><th data-v-be77961e>Dark</th><th data-v-be77961e>Usage</th></tr></thead><tbody data-v-be77961e><tr data-v-be77961e><td class="tk" data-v-be77961e>--p-surface-overlay</td><td class="val" data-v-be77961e><span class="swatch" style="background:#fff;" data-v-be77961e></span>#ffffff</td><td class="val" data-v-be77961e><span class="swatch" style="background:#22272e;" data-v-be77961e></span>#22272e</td><td data-v-be77961e>Field controls on raised cards — select, stepper (top fill rung; light = white)</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>--p-surface-raised</td><td class="val" data-v-be77961e><span class="swatch" style="background:#fff;" data-v-be77961e></span>#ffffff</td><td class="val" data-v-be77961e><span class="swatch" style="background:#1c2128;" data-v-be77961e></span>#1c2128</td><td data-v-be77961e>Raised card / dialog / input (raised layer)</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>--p-well</td><td class="val" data-v-be77961e><span class="swatch" style="background:#f3f5f8;" data-v-be77961e></span>#f3f5f8</td><td class="val" data-v-be77961e><span class="swatch" style="background:#13181e;" data-v-be77961e></span>#13181e</td><td data-v-be77961e>Code block / tool output / list carrier directly on the page (content well — light: recessed, dark: one rung above the page)</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>--p-surface</td><td class="val" data-v-be77961e><span class="swatch" style="background:#fafbfc;" data-v-be77961e></span>#fafbfc</td><td class="val" data-v-be77961e><span class="swatch" style="background:#13181e;" data-v-be77961e></span>#13181e</td><td data-v-be77961e>Panel / sidebar / card head (default flat layer)</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>--p-surface-sunken</td><td class="val" data-v-be77961e><span class="swatch" style="background:#f3f5f8;" data-v-be77961e></span>#f3f5f8</td><td class="val" data-v-be77961e><span class="swatch" style="background:#0d1117;" data-v-be77961e></span>#0d1117</td><td data-v-be77961e>Recessed area INSIDE a surface / raised card — never a content carrier on the page (sunken layer)</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>--p-bg</td><td class="val" data-v-be77961e><span class="swatch" style="background:#fff;" data-v-be77961e></span>#ffffff</td><td class="val" data-v-be77961e><span class="swatch" style="background:#0d1117;" data-v-be77961e></span>#0d1117</td><td data-v-be77961e>Page background</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>--p-surface-deep</td><td class="val" data-v-be77961e><span class="swatch" style="background:#fafbfc;" data-v-be77961e></span>#fafbfc</td><td class="val" data-v-be77961e><span class="swatch" style="background:#0a0d12;" data-v-be77961e></span>#0a0d12</td><td data-v-be77961e>Panel header / diff gutter (deep chrome layer — below the page in dark)</td></tr></tbody></table><h4 class="mini" data-v-be77961e>Borders & hairlines</h4><p data-v-be77961e>Three line tokens, three jobs: <code data-v-be77961e>--color-line</code> is the default structural separator, <code data-v-be77961e>--color-subtle</code> the tertiary separator that must stay quieter (diff-gutter column rules, quiet dividers inside wells), and <code data-v-be77961e>--color-line-strong</code> the edge of interactive controls (inputs, selects, secondary buttons). Width is one: <b data-v-be77961e>0.5px</b> — every stroke is the same hairline, on static structural edges (card rims, plane seams, header dividers), interactive control rims and floating layers alike. Separation comes from luminance first — planes one rung apart already read as distinct in dark, so their shared edge stays a 0.5px hairline rather than a heavier border; same-rung neighbours (list rows, card head / body) are exactly where a hairline is required. In dark, drop shadows fade on near-black surfaces, so a floating layer's edge IS its hairline — never ship a shadow-only floating surface. (Legacy <code data-v-be77961e>--line</code> / <code data-v-be77961e>--line2</code> alias <code data-v-be77961e>--color-line</code> / <code data-v-be77961e>--color-subtle</code> for one cycle; new work references the v2 names.)</p><h4 class="mini" data-v-be77961e>Focus ring</h4><p data-v-be77961e>All focusable controls (button, input, link, menu item, switch, checkbox) use the focus-ring token uniformly; do not hand-write a <code data-v-be77961e>box-shadow</code> focus ring.</p><table class="dt" data-v-be77961e><thead data-v-be77961e><tr data-v-be77961e><th data-v-be77961e>Token</th><th data-v-be77961e>Value</th><th data-v-be77961e>Usage</th></tr></thead><tbody data-v-be77961e><tr data-v-be77961e><td class="tk" data-v-be77961e>--p-focus-ring</td><td class="val" data-v-be77961e>0 0 0 3px var(--p-accent-soft)</td><td data-v-be77961e>Default focus ring (link, menu item, switch, checkbox)</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>--p-focus-ring-strong</td><td class="val" data-v-be77961e>0 0 0 3px var(--p-accent-soft), 0 0 0 1px var(--p-accent)</td><td data-v-be77961e>Strong focus ring (button, primary action)</td></tr></tbody></table><h4 class="mini" data-v-be77961e>Text selection</h4><p data-v-be77961e>The text-selection color uses <code data-v-be77961e>--p-selection</code> uniformly (light <code data-v-be77961e>rgba(23,131,255,.18)</code> / dark <code data-v-be77961e>rgba(88,166,255,.32)</code>), applied by the global <code data-v-be77961e>::selection</code> rule; do not set a separate highlight background.</p><h4 class="mini" data-v-be77961e>Disabled state</h4><p data-v-be77961e>All disabled controls use <code data-v-be77961e>opacity:.5</code> + <code data-v-be77961e>cursor:not-allowed</code> uniformly; do not separately grey out or recolor.</p><h3 class="sub" data-v-be77961e>Font families</h3><p data-v-be77961e>Kimi Web uses two font tokens: <b data-v-be77961e>--font-ui</b> (UI and body, with Schibsted Grotesk for Latin and Noto Sans SC for Simplified Chinese) and <b data-v-be77961e>--font-mono</b> (code and monospace). Components always reference the variables; do not hard-code font names.</p><h4 class="mini" data-v-be77961e>--font-ui · UI & body (Schibsted Grotesk + Noto Sans SC)</h4><p data-v-be77961e>Body and UI use self-hosted Schibsted Grotesk for Latin text and self-hosted Noto Sans SC Variable for Simplified Chinese. Platform fonts remain as fallbacks:</p><div class="code" data-v-be77961e><div class="code-bar" data-v-be77961e><span class="d" data-v-be77961e></span><span class="d" data-v-be77961e></span><span class="d" data-v-be77961e></span><span class="fn" data-v-be77961e>--font-ui</span></div><pre data-v-be77961e>--font-ui: "Schibsted Grotesk Variable", "Helvetica Neue", Arial,
|
|
2
|
-
"Noto Sans SC Variable", "Noto Sans SC", "PingFang SC",
|
|
3
|
-
"Microsoft YaHei",
|
|
4
|
-
-apple-system, BlinkMacSystemFont, "Segoe UI",
|
|
5
|
-
Roboto, Ubuntu, sans-serif,
|
|
6
|
-
"Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji";</pre></div><ul class="clean" data-v-be77961e><li data-v-be77961e>Schibsted Grotesk first: self-hosted Latin UI and body text, with normal and italic variable faces.</li><li data-v-be77961e>Western fallbacks next: Helvetica Neue / Arial for environments where Schibsted Grotesk cannot load.</li><li data-v-be77961e>Noto Sans SC Variable next: bundled Simplified Chinese glyphs with a weight range of 100–900.</li><li data-v-be77961e>System UI fallbacks last: PingFang SC / Microsoft YaHei, platform UI fonts, and emoji fonts.</li></ul><h4 class="mini" data-v-be77961e>--font-mono · Code & monospace</h4><p data-v-be77961e>Code, line numbers, diffs, and Bash commands use JetBrains Mono (a self-hosted variable font), falling back to the system monospace. Other tool labels and summaries use the UI font:</p><div class="code" data-v-be77961e><div class="code-bar" data-v-be77961e><span class="d" data-v-be77961e></span><span class="d" data-v-be77961e></span><span class="d" data-v-be77961e></span><span class="fn" data-v-be77961e>--font-mono</span></div><pre data-v-be77961e>--font-mono: "JetBrains Mono Variable", "JetBrains Mono",
|
|
7
|
-
ui-monospace, "SF Mono", Menlo, Consolas, monospace;</pre></div><h4 class="mini" data-v-be77961e>Loading strategy</h4><table class="dt" data-v-be77961e><thead data-v-be77961e><tr data-v-be77961e><th data-v-be77961e>Font</th><th data-v-be77961e>Source</th><th data-v-be77961e>Bundled</th><th data-v-be77961e>Usage</th></tr></thead><tbody data-v-be77961e><tr data-v-be77961e><td class="tk" data-v-be77961e>JetBrains Mono</td><td class="val" data-v-be77961e>@fontsource-variable/jetbrains-mono</td><td class="val" data-v-be77961e>✓ self-hosted</td><td data-v-be77961e>monospace / code (--font-mono)</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>Schibsted Grotesk</td><td class="val" data-v-be77961e>prepare-fonts → web-ui/assets/fonts</td><td class="val" data-v-be77961e>✓ generated + bundled</td><td data-v-be77961e>UI / body / display (--font-ui, --font-display), wght 400-900, normal + italic</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>Noto Sans SC</td><td class="val" data-v-be77961e>prepare-fonts → web-ui/assets/fonts</td><td class="val" data-v-be77961e>✓ generated + bundled</td><td data-v-be77961e>Simplified Chinese UI / body, wght 100–900</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>System UI / CJK fonts</td><td class="val" data-v-be77961e>operating system</td><td class="val" data-v-be77961e>—</td><td data-v-be77961e>late fallback for UI / body</td></tr></tbody></table><div class="callout good" data-v-be77961e><span class="ico" data-v-be77961e>✓</span><div data-v-be77961e> Schibsted Grotesk, Noto Sans SC, and JetBrains Mono are self-hosted. They make no external network requests and work offline; platform fonts remain as fallbacks. </div></div><h4 class="mini" data-v-be77961e>Usage rules</h4><ul class="clean check" data-v-be77961e><li data-v-be77961e>Components always use <code data-v-be77961e>var(--font-ui)</code> / <code data-v-be77961e>var(--font-mono)</code>; do not hard-code font names like <code data-v-be77961e>'Schibsted Grotesk'</code> / <code data-v-be77961e>'JetBrains Mono'</code>.</li><li data-v-be77961e>Body / UI use <code data-v-be77961e>--font-ui</code> (Schibsted Grotesk for Latin, Noto Sans SC for Simplified Chinese); code / monospace use <code data-v-be77961e>--font-mono</code> (JetBrains Mono).</li><li data-v-be77961e>Schibsted Grotesk is loaded from complete variable faces, including normal and italic styles; <code data-v-be77961e>font-optical-sizing: auto</code> is enabled globally.</li><li data-v-be77961e>Noto Sans SC is loaded from one complete weight-variable WOFF2 asset. Platform CJK fonts stay late in the fallback chain.</li></ul><h3 class="sub" data-v-be77961e>Type scale & weight</h3><p data-v-be77961e>The user font-size preference is one of four named steps (<code data-v-be77961e>small / medium / large / xlarge</code>, Medium default) written to <code data-v-be77961e>data-font-scale</code> on <code data-v-be77961e><html></code>; the step name is persisted, never a px value. The step only moves <code data-v-be77961e>--base-font</code>; every size token derives additively (<code data-v-be77961e>default + shift</code>), and line heights are locked to integer px via <code data-v-be77961e>round(size × ratio, 1px)</code> — never a unitless ratio.</p><p data-v-be77961e>Two token groups share the shift but keep their own ratios: <b data-v-be77961e>--ui-*</b> for chrome (tight, 1.40–1.50) and <b data-v-be77961e>--md-*</b> for Markdown content + the composer (loose, 1.56–1.63; body is anchored to the UI body size — the spec's +2px offset was dropped as a product decision — while keeping its own looser line-height ratios). T0/T1 cap at 24/22px on the top steps (built into the tokens via <code data-v-be77961e>min()</code> — do not remove). Use the <code data-v-be77961e>.text-ui-*</code> / <code data-v-be77961e>.text-md-*</code> utility classes; legacy aliases <code data-v-be77961e>--ui-font-size</code> (→ <code data-v-be77961e>--ui-b2</code>), <code data-v-be77961e>--content-font-size</code> (→ <code data-v-be77961e>--md-b1</code>) and the whole 6-level <code data-v-be77961e>--text-*</code> ramp (xs→c1, sm→b2−1px, base→b2, lg→t2, xl→t1, 2xl→t0) keep older components on the ramp. Panel titles sit at the base step (<code data-v-be77961e>--ui-b2</code>); dropdown menu items sit one rung below (<code data-v-be77961e>--text-sm</code> = b2 − 1px) — both still follow the user's font scale.</p><div class="panel panel-pad" style="margin:16px 0;" data-v-be77961e><div class="type-row" data-v-be77961e><div class="type-sample" style="font-size:var(--ui-t1);font-weight:500;" data-v-be77961e>Section Title</div><div class="type-meta" data-v-be77961e>--ui-t1 · title (cap 22)</div></div><div class="type-row" data-v-be77961e><div class="type-sample" style="font-size:var(--ui-t2);font-weight:500;" data-v-be77961e>Card title</div><div class="type-meta" data-v-be77961e>--ui-t2 · subtitle</div></div><div class="type-row" data-v-be77961e><div class="type-sample" style="font-size:var(--ui-b1);font-weight:500;" data-v-be77961e>UI emphasis</div><div class="type-meta" data-v-be77961e>--ui-b1 · body strong</div></div><div class="type-row" data-v-be77961e><div class="type-sample" style="font-size:var(--ui-b2);" data-v-be77961e>UI control / button / form</div><div class="type-meta" data-v-be77961e>--ui-b2 · body</div></div><div class="type-row" data-v-be77961e><div class="type-sample" style="font-size:var(--ui-c1);" data-v-be77961e>Helper text / table</div><div class="type-meta" data-v-be77961e>--ui-c1 · caption</div></div><div class="type-row" data-v-be77961e><div class="type-sample" style="font-size:var(--ui-c2);" data-v-be77961e>Badge / timestamp</div><div class="type-meta" data-v-be77961e>--ui-c2 · non-critical only</div></div><div class="type-row" data-v-be77961e><div class="type-sample" style="font-size:var(--md-h1);font-weight:600;" data-v-be77961e>Markdown H1</div><div class="type-meta" data-v-be77961e>--md-h1</div></div><div class="type-row" data-v-be77961e><div class="type-sample" style="font-size:var(--md-b1);" data-v-be77961e>Chat body / message bubbles / composer</div><div class="type-meta" data-v-be77961e>--md-b1 · prose body</div></div><div class="type-row" data-v-be77961e><div class="type-sample" style="font-size:var(--md-b2);" data-v-be77961e>Quote / table</div><div class="type-meta" data-v-be77961e>--md-b2 · secondary</div></div><div class="type-row" data-v-be77961e><div class="type-sample" style="font-size:var(--md-b3);font-family:var(--font-mono);" data-v-be77961e>Code block / inline code</div><div class="type-meta" data-v-be77961e>--md-b3 · weak / code</div></div></div><p data-v-be77961e>The fixed product type tokens still define scale-independent defaults: transcript prose enables <code data-v-be77961e>text-autospace: normal</code> for mixed CJK and Latin text. Drop stray <code data-v-be77961e>font-weight: 650 / 750</code>; converge on 400 / 500 (regular / emphasis), with a dedicated 600 weight for sidebar section labels.</p><table class="dt" data-v-be77961e><thead data-v-be77961e><tr data-v-be77961e><th data-v-be77961e>Token</th><th data-v-be77961e>Value</th><th data-v-be77961e>Usage</th></tr></thead><tbody data-v-be77961e><tr data-v-be77961e><td class="tk" data-v-be77961e>--font-ui</td><td class="val" data-v-be77961e>"Schibsted Grotesk Variable", …, "Noto Sans SC Variable", …</td><td data-v-be77961e>UI & body (Schibsted Grotesk + Noto Sans SC)</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>--font-kbd</td><td class="val" data-v-be77961e>"Schibsted Grotesk Variable", system-ui, sans-serif</td><td data-v-be77961e>keyboard shortcut keycaps</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>--font-mono</td><td class="val" data-v-be77961e>JetBrains Mono…</td><td data-v-be77961e>code, Bash commands, line numbers, diffs</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>data-font-scale</td><td class="val" data-v-be77961e>small / medium / large / xlarge</td><td data-v-be77961e>user preference on <html>; sets --base-font (12–18px), Medium = 14px default</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>--ui-t0…--ui-c2</td><td class="val" data-v-be77961e>default + --ui-shift, t0/t1 capped via min()</td><td data-v-be77961e>chrome type ramp (title / subtitle / body / caption); .text-ui-* classes</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>--md-h1…--md-b3</td><td class="val" data-v-be77961e>default + --md-shift</td><td data-v-be77961e>Markdown ramp (headings / body / secondary / code); .text-md-* classes</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>--ui-font-size / --content-font-size</td><td class="val" data-v-be77961e>var(--ui-b2) / var(--md-b1)</td><td data-v-be77961e>legacy aliases kept on the ramp</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>--code-font-size</td><td class="val" data-v-be77961e>calc(var(--content-font-size) - 2px)</td><td data-v-be77961e>standalone code surfaces (diff view, file preview, tool cards) — one step below body, 12px @ Medium; prose-embedded code stays on the --md-* ramp</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>--text-xs / sm / base / lg / xl / 2xl</td><td class="val" data-v-be77961e>c1 / b2−1 / b2 / t2 / t1 / t0</td><td data-v-be77961e>legacy ramp, aliased into the scale</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>--leading-tight/normal/prose/relaxed</td><td class="val" data-v-be77961e>1.25 / 1.5 / 1.6 / 1.7</td><td data-v-be77961e>headings / UI / chat prose / long text</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>--weight-regular/option-label/medium/ui-strong</td><td class="val" data-v-be77961e>400 / 475 / 500 / 525</td><td data-v-be77961e>body / settings labels / emphasis / compact UI emphasis</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>--weight-section-label</td><td class="val" data-v-be77961e>600</td><td data-v-be77961e>sidebar section labels</td></tr></tbody></table><h4 class="mini" data-v-be77961e>Icon size</h4><p data-v-be77961e>Icons use three size tokens uniformly. The global <code data-v-be77961e>.p-ic</code> default is 16px (<code data-v-be77961e>--p-ic-md</code>); components pick as needed, and random pixel sizes are forbidden.</p><table class="dt" data-v-be77961e><thead data-v-be77961e><tr data-v-be77961e><th data-v-be77961e>Token</th><th data-v-be77961e>Value</th><th data-v-be77961e>Usage</th></tr></thead><tbody data-v-be77961e><tr data-v-be77961e><td class="tk" data-v-be77961e>--p-ic-sm</td><td class="val" data-v-be77961e>14px</td><td data-v-be77961e>small button, badge, menu item, inline link icon</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>--p-ic-md</td><td class="val" data-v-be77961e>16px</td><td data-v-be77961e>default (button, icon button, toolbar)</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>--p-ic-lg</td><td class="val" data-v-be77961e>20px</td><td data-v-be77961e>Toast status icon, empty-state illustration</td></tr></tbody></table><h4 class="mini" data-v-be77961e>Icon</h4><p data-v-be77961e>Icons always come from the centralized registry <code data-v-be77961e>lib/icons.ts</code>: in templates use the <code data-v-be77961e><Icon name size /></code> component (<code data-v-be77961e>components/ui/Icon.vue</code>); for <code data-v-be77961e>v-html</code> contexts (such as a tool glyph) use <code data-v-be77961e>iconSvg(name, size)</code>. <b data-v-be77961e>Do not hand-write <code data-v-be77961e><svg></code></b> — the <code data-v-be77961e>scripts/check-style.mjs</code> <code data-v-be77961e>icon-from-registry</code> rule flags stray SVGs. Every glyph shares the 24×24 source grid and <code data-v-be77961e>currentColor</code> (colour follows text); size uses the three tokens below, and only icons imported in <code data-v-be77961e>lib/icons.ts</code> are bundled by <a href="https://github.com/unplugin/unplugin-icons" data-v-be77961e>unplugin-icons</a> at build time. Three collections feed the registry, in this order of preference: <b data-v-be77961e><code data-v-be77961e>~icons/kimi/*</code></b> — Kimi Design System icons (24×24 outlined, 1.8px stroke), local SVGs under <code data-v-be77961e>src/icons/kimi/</code> registered as a custom collection in the Vite config, used whenever a Kimi glyph exists for the intent; <b data-v-be77961e><code data-v-be77961e>~icons/tabler/*</code></b> — Tabler Icons (MIT), for the few gaps it uniquely covers (today: the right-panel toggle); and <b data-v-be77961e><code data-v-be77961e>~icons/ri/*</code></b> — <a href="https://remixicon.com/" data-v-be77961e>Remix Icon</a> (Apache-2.0), for the remaining intents the Kimi set does not cover yet. A few glyphs are filed under their intent rather than the upstream asset name (see the <code data-v-be77961e>lib/icons.ts</code> header). When an icon is missing, prefer a glyph from the Kimi icon set: copy the SVG into <code data-v-be77961e>src/icons/kimi/</code> (kebab-case name, monochrome <code data-v-be77961e>currentColor</code>) and register it — two static imports (component + <code data-v-be77961e>?raw</code> string) plus one entry in <code data-v-be77961e>ICONS</code>; reach for Remix only when no Kimi glyph fits, and never draw paths in a component.</p><h4 class="mini" data-v-be77961e>Size scale</h4>`,49)),a("div",D,[a("div",W,[(o(),i("svg",O,[...e[1]||(e[1]=[a("path",{fill:"currentColor",d:"M11 11V5h2v6h6v2h-6v6h-2v-6H5v-2z"},null,-1)])])),e[2]||(e[2]=s("sm · 14",-1))]),a("div",R,[(o(),i("svg",U,[...e[3]||(e[3]=[a("path",{fill:"currentColor",d:"M11 11V5h2v6h6v2h-6v6h-2v-6H5v-2z"},null,-1)])])),e[4]||(e[4]=s("md · 16",-1))]),a("div",N,[(o(),i("svg",F,[...e[5]||(e[5]=[a("path",{fill:"currentColor",d:"M11 11V5h2v6h6v2h-6v6h-2v-6H5v-2z"},null,-1)])])),e[6]||(e[6]=s("lg · 20",-1))])]),e[8]||(e[8]=a("h4",{class:"mini"},"Icon library",-1)),e[9]||(e[9]=a("p",null,[s("Currently registered icons, grouped by purpose. The display order and grouping are defined by "),a("code",null,"ICON_GROUPS"),s(" in "),a("code",null,"lib/icons.ts"),s(" (a hand-maintained array covering the same icon names), and this catalog is rendered directly from that array so the registry and the document never drift.")],-1)),a("div",P,[(o(!0),i(p,null,w(b(B),([n,l])=>(o(),i(p,{key:n},[a("div",j,y(n),1),(o(!0),i(p,null,w(l,d=>(o(),i("div",{key:d,class:"icon-cell"},[c(b(h),{name:d},null,8,["name"]),a("span",K,y(d),1)]))),128))],64))),128))]),e[10]||(e[10]=t('<p data-v-be77961e>Do not use emoji as functional icons. The Kimi brand mark (the robot mascot logo) is a brand asset and is not part of this icon system.</p><p data-v-be77961e>A few <b data-v-be77961e>special graphics</b> are not in the registry; each has a dedicated component maintained in one place, and must not be copied by hand: <code data-v-be77961e><ContextRing :pct /></code> (the Composer context progress ring, data-driven), <code data-v-be77961e><AuthStateIcon kind /></code> (the success / expired / error colored illustrations in the login flow), <code data-v-be77961e><Spinner /></code> (loading state). Status dots (such as in the Provider list) always use CSS dots (<code data-v-be77961e>border-radius:50%</code>), not SVG. The <code data-v-be77961e>scripts/check-style.mjs</code> <code data-v-be77961e>icon-from-registry</code> rule exempts the above and the brand mark; all other hand-written <code data-v-be77961e><svg></code> is flagged.</p><h3 class="sub" data-v-be77961e>Spacing</h3><p data-v-be77961e>A 4px base grid. All spacing, gaps, and padding inside and outside components come from this scale — no arbitrary pixels.</p><div class="panel panel-pad" style="margin:16px 0;" data-v-be77961e><div class="space-row" data-v-be77961e><div class="space-bar" style="width:4px;" data-v-be77961e></div><div class="space-meta" data-v-be77961e>--space-1 · 4</div><div class="space-use" data-v-be77961e>icon gap, badge padding</div></div><div class="space-row" data-v-be77961e><div class="space-bar" style="width:8px;" data-v-be77961e></div><div class="space-meta" data-v-be77961e>--space-2 · 8</div><div class="space-use" data-v-be77961e>control gap, small padding</div></div><div class="space-row" data-v-be77961e><div class="space-bar" style="width:12px;" data-v-be77961e></div><div class="space-meta" data-v-be77961e>--space-3 · 12</div><div class="space-use" data-v-be77961e>button padding, form-item gap</div></div><div class="space-row" data-v-be77961e><div class="space-bar" style="width:16px;" data-v-be77961e></div><div class="space-meta" data-v-be77961e>--space-4 · 16</div><div class="space-use" data-v-be77961e>card padding, grid gap</div></div><div class="space-row" data-v-be77961e><div class="space-bar" style="width:20px;" data-v-be77961e></div><div class="space-meta" data-v-be77961e>--space-5 · 20</div><div class="space-use" data-v-be77961e>dialog padding</div></div><div class="space-row" data-v-be77961e><div class="space-bar" style="width:24px;" data-v-be77961e></div><div class="space-meta" data-v-be77961e>--space-6 · 24</div><div class="space-use" data-v-be77961e>section gap</div></div><div class="space-row" data-v-be77961e><div class="space-bar" style="width:32px;" data-v-be77961e></div><div class="space-meta" data-v-be77961e>--space-8 · 32</div><div class="space-use" data-v-be77961e>large section gap</div></div></div><h4 class="mini" data-v-be77961e>Dense list (sidebar / file tree)</h4><p data-v-be77961e>High-density navigation lists like the sidebar share one rhythm, all on the 4px grid: <b data-v-be77961e>in-row vertical padding</b> <code data-v-be77961e>--space-1</code> (4px), <b data-v-be77961e>no margin between rows</b> (the hover pill provides the separation); <b data-v-be77961e>section gap</b> (between logo / search / action buttons / group title / list) uniformly <code data-v-be77961e>--space-2</code> (8px); <b data-v-be77961e>between groups</b> <code data-v-be77961e>--space-2</code>; the brand header is slightly looser at the top (<code data-v-be77961e>--space-3</code>). When building similar lists, reuse this scale — do not hand-write 1/6/7/10px.</p><h3 class="sub" data-v-be77961e>Radius</h3><p data-v-be77961e>Merge the existing 14 values <b data-v-be77961e>into the nearest</b> of 7 scale steps. Rule: the component type determines the radius, not the author's feel. The Composer shell is the sole product-specific exception: its 32px radius pairs with <code data-v-be77961e>superellipse(1.5)</code> so the flatter curve stays visually concentric with its controls.</p><div class="radius-grid" data-v-be77961e><div class="radius-item" data-v-be77961e><div class="radius-box" style="border-radius:4px;" data-v-be77961e></div><span class="rl" data-v-be77961e>xs · 4</span></div><div class="radius-item" data-v-be77961e><div class="radius-box" style="border-radius:6px;" data-v-be77961e></div><span class="rl" data-v-be77961e>sm · 6</span></div><div class="radius-item" data-v-be77961e><div class="radius-box" style="border-radius:8px;" data-v-be77961e></div><span class="rl" data-v-be77961e>md · 8</span></div><div class="radius-item" data-v-be77961e><div class="radius-box" style="border-radius:12px;" data-v-be77961e></div><span class="rl" data-v-be77961e>lg · 12</span></div><div class="radius-item" data-v-be77961e><div class="radius-box" style="border-radius:16px;" data-v-be77961e></div><span class="rl" data-v-be77961e>xl · 16</span></div><div class="radius-item" data-v-be77961e><div class="radius-box" style="border-radius:20px;" data-v-be77961e></div><span class="rl" data-v-be77961e>2xl · 20</span></div><div class="radius-item" data-v-be77961e><div class="radius-box" style="border-radius:32px;corner-shape:superellipse(1.5);" data-v-be77961e></div><span class="rl" data-v-be77961e>composer · 32 / 1.5</span></div><div class="radius-item" data-v-be77961e><div class="radius-box" style="border-radius:999px;" data-v-be77961e></div><span class="rl" data-v-be77961e>full · 999</span></div></div><table class="dt" data-v-be77961e><thead data-v-be77961e><tr data-v-be77961e><th data-v-be77961e>Token</th><th data-v-be77961e>Value</th><th data-v-be77961e>Usage</th><th data-v-be77961e>Merged from</th></tr></thead><tbody data-v-be77961e><tr data-v-be77961e><td class="tk" data-v-be77961e>--radius-xs</td><td class="val" data-v-be77961e>4px</td><td data-v-be77961e>small badge, inline tag</td><td class="val" data-v-be77961e>2/3/4px →</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>--radius-sm</td><td class="val" data-v-be77961e>6px</td><td data-v-be77961e>small button, icon button, menu item</td><td class="val" data-v-be77961e>5/6px →</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>--radius-md</td><td class="val" data-v-be77961e>8px</td><td data-v-be77961e>button, input, badge, card</td><td class="val" data-v-be77961e>7/8/9px →</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>--radius-lg</td><td class="val" data-v-be77961e>12px</td><td data-v-be77961e>menu, toast, bubble, floating card</td><td class="val" data-v-be77961e>10/12px →</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>--radius-xl</td><td class="val" data-v-be77961e>16px</td><td data-v-be77961e>container baseline: dialogs, settings cards, sheets, work panel</td><td class="val" data-v-be77961e>13/16px →</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>--radius-2xl</td><td class="val" data-v-be77961e>20px</td><td data-v-be77961e>workspace attachment card bottom (<code data-v-be77961e>0 0 2xl 2xl</code>) tucked under the composer</td><td class="val" data-v-be77961e>18/20px →</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>--radius-composer</td><td class="val" data-v-be77961e>32px</td><td data-v-be77961e>Composer shell, with <code data-v-be77961e>--corner-shape-composer</code></td><td class="val" data-v-be77961e>product-specific</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>--radius-full</td><td class="val" data-v-be77961e>999px</td><td data-v-be77961e>pill badge, avatar, send button</td><td class="val" data-v-be77961e>999px / 50%</td></tr></tbody></table><h3 class="sub" data-v-be77961e>Elevation & z-index</h3><p data-v-be77961e>Shadows express only "elevation", never decoration (no colored glow). z-index is unified into a scale, eradicating <code data-v-be77961e>9999</code>-style one-upping.</p><div class="panel panel-pad" style="margin:16px 0;" data-v-be77961e><div class="radius-grid" style="align-items:stretch;" data-v-be77961e><div class="radius-item" data-v-be77961e><div class="radius-box" style="border:none;background:#fff;box-shadow:0 1px 2px rgba(16,24,40,.05),0 1px 3px rgba(16,24,40,.06);" data-v-be77961e></div><span class="rl" data-v-be77961e>sm · dropdown menu / sticky</span></div><div class="radius-item" data-v-be77961e><div class="radius-box" style="border:none;background:#fff;box-shadow:0 4px 12px rgba(16,24,40,.07),0 2px 4px rgba(16,24,40,.05);" data-v-be77961e></div><span class="rl" data-v-be77961e>md · Toast</span></div><div class="radius-item" data-v-be77961e><div class="radius-box" style="border:none;background:#fff;box-shadow:0 12px 32px rgba(16,24,40,.12),0 4px 10px rgba(16,24,40,.08);" data-v-be77961e></div><span class="rl" data-v-be77961e>lg · overlay (reserved)</span></div><div class="radius-item" data-v-be77961e><div class="radius-box" style="border:none;background:#fff;box-shadow:0 24px 64px rgba(16,24,40,.18),0 8px 20px rgba(16,24,40,.10);" data-v-be77961e></div><span class="rl" data-v-be77961e>xl · dialog</span></div></div></div><table class="dt" data-v-be77961e><thead data-v-be77961e><tr data-v-be77961e><th data-v-be77961e>Z-index Token</th><th data-v-be77961e>Value</th><th data-v-be77961e>Usage</th></tr></thead><tbody data-v-be77961e><tr data-v-be77961e><td class="tk" data-v-be77961e>--z-base</td><td class="val" data-v-be77961e>0</td><td data-v-be77961e>normal flow</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>--z-sticky</td><td class="val" data-v-be77961e>100</td><td data-v-be77961e>sticky header / sidebar</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>--z-dropdown</td><td class="val" data-v-be77961e>200</td><td data-v-be77961e>dropdown menu / tooltip</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>--z-overlay</td><td class="val" data-v-be77961e>300</td><td data-v-be77961e>overlay / bottom Sheet</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>--z-modal</td><td class="val" data-v-be77961e>400</td><td data-v-be77961e>dialog — sibling overlays tie-break by DOM order, so the global confirm (ConfirmDialogHost) mounts on demand to always land last / on top</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>--z-modal-dropdown</td><td class="val" data-v-be77961e>500</td><td data-v-be77961e>menus / popovers that open above a modal dialog (teleported to <body>, e.g. the settings SecondaryModelPicker cascade)</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>--z-toast</td><td class="val" data-v-be77961e>600</td><td data-v-be77961e>toast</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>--z-max</td><td class="val" data-v-be77961e>9999</td><td data-v-be77961e>reserved: only this tier for extreme fallback</td></tr></tbody></table><h3 class="sub" data-v-be77961e>Motion</h3><table class="dt" data-v-be77961e><thead data-v-be77961e><tr data-v-be77961e><th data-v-be77961e>Token</th><th data-v-be77961e>Value</th><th data-v-be77961e>Usage</th></tr></thead><tbody data-v-be77961e><tr data-v-be77961e><td class="tk" data-v-be77961e>--ease-out</td><td class="val" data-v-be77961e>cubic-bezier(0.16, 1, 0.3, 1)</td><td data-v-be77961e>enter, hover, expand</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>--ease-in-out</td><td class="val" data-v-be77961e>cubic-bezier(0.4, 0, 0.2, 1)</td><td data-v-be77961e>panel width, layout changes</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>--duration-fast</td><td class="val" data-v-be77961e>120ms</td><td data-v-be77961e>press, focus</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>--duration-base</td><td class="val" data-v-be77961e>160ms</td><td data-v-be77961e>hover, show/hide</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>--duration-slow</td><td class="val" data-v-be77961e>260ms</td><td data-v-be77961e>dialog, Sheet, layout</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>--duration-hover-intent</td><td class="val" data-v-be77961e>250ms</td><td data-v-be77961e>hover-intent reveal gate (TOC rail)</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>--anim-rive-spin</td><td class="val" data-v-be77961e>416.7ms</td><td data-v-be77961e>new-chat / folder-plus icon: plus spin on hover</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>--anim-leftbar</td><td class="val" data-v-be77961e>533.3ms</td><td data-v-be77961e>sidebar toggle icon: arrow fly-in on hover</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>--anim-leftbar-shrink</td><td class="val" data-v-be77961e>200ms</td><td data-v-be77961e>sidebar toggle icon: divider shrink on hover</td></tr></tbody></table><p data-v-be77961e>The <code data-v-be77961e>--anim-*</code> lengths are track timings ported verbatim from the designer's Rive exports, so they sit outside the <code data-v-be77961e>--duration-*</code> ramp on purpose — retiming the ramp must not distort them. Their interpolation stays <code data-v-be77961e>linear</code> because the easing is already baked into the dense keyframe stops; a token easing would double-apply. Three hover tracks use them today: the sidebar toggle shrinks its divider to half height while an arrow flies in and settles (the expand variant mirrors the track from the left), and the new-chat / folder-plus pluses do one bouncy spin. Each track is keyed to an id inside its own glyph (<code data-v-be77961e>#bar-divider</code>, <code data-v-be77961e>#bar-arrow</code> / <code data-v-be77961e>#bar-arrow-expand</code>, <code data-v-be77961e>#p1</code>, <code data-v-be77961e>#af-p1</code>) so every instance of the icon animates, and all revert on mouse-out. They still fall under the global reduced-motion switch below.</p><h4 class="mini" data-v-be77961e>Reduced motion</h4><div class="callout info" data-v-be77961e><span class="ico" data-v-be77961e>i</span><div data-v-be77961e> Under <code data-v-be77961e>@media (prefers-reduced-motion: reduce)</code>, all animation and transition durations drop to about <code data-v-be77961e>0.001ms</code> (effectively off), and the chat working indicator's mascot renders its static fallback instead of the Rive loop. Components should not check this individually; it is handled uniformly in the global styles. The switch clears durations, not <code data-v-be77961e>transition-delay</code>: a hover-intent gate (the conversation TOC's 250ms reveal) decides <i data-v-be77961e>whether</i> hidden content appears, and clearing it would make pointer fly-bys strobe content for reduced-motion users. </div></div><h3 class="sub" data-v-be77961e>Layout & breakpoints</h3><p data-v-be77961e>Layout sizes and responsive breakpoints are tokenized too: sidebar width, content reading-column width, and two global breakpoints. Components should not hard-code pixels.</p><table class="dt" data-v-be77961e><thead data-v-be77961e><tr data-v-be77961e><th data-v-be77961e>Token</th><th data-v-be77961e>Value</th><th data-v-be77961e>Usage</th></tr></thead><tbody data-v-be77961e><tr data-v-be77961e><td class="tk" data-v-be77961e>--p-sidebar-w</td><td class="val" data-v-be77961e>264px</td><td data-v-be77961e>left session sidebar width</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>--p-content-max</td><td class="val" data-v-be77961e>760px</td><td data-v-be77961e>chat reading-column max width (regular chat prose)</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>--p-content-wide</td><td class="val" data-v-be77961e>920px</td><td data-v-be77961e>wide content (settings / panel)</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>--p-table-max</td><td class="val" data-v-be77961e>1040px</td><td data-v-be77961e>desktop wide-table max width (see §04)</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>--p-table-cell-max</td><td class="val" data-v-be77961e>700px</td><td data-v-be77961e>max width of a single table column; longer cell content wraps (see §04)</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>--p-bp-sm</td><td class="val" data-v-be77961e>640px</td><td data-v-be77961e>mobile / desktop boundary</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>--p-bp-md</td><td class="val" data-v-be77961e>980px</td><td data-v-be77961e>narrow / wide screen boundary</td></tr></tbody></table><div class="callout info" data-v-be77961e><span class="ico" data-v-be77961e>i</span><div data-v-be77961e> At ≤640px: dialogs become bottom Sheets, the sidebar collapses into an expandable drawer, and Composer toolbar controls are allowed to wrap. </div></div>',24))]),a("section",G,[e[27]||(e[27]=t(`<div class="sec-head" data-v-be77961e><span class="sec-num" data-v-be77961e>03</span><h2 class="sec-title" data-v-be77961e>Primitives</h2></div><p class="sec-desc" data-v-be77961e> Component primitives are the "smallest correct units" of the site UI. Each primitive exposes variants along only two dimensions — <code data-v-be77961e>variant</code> / <code data-v-be77961e>size</code> — with appearance driven by tokens, so it naturally supports light / dark mode and customizable theme colors. </p><div class="callout info" data-v-be77961e><span class="ico" data-v-be77961e>i</span><div data-v-be77961e> For every interactive primitive, the <b data-v-be77961e>keyboard behavior, focus, and ARIA contract are in §08 Accessibility</b>. New primitives must ship with a keyboard model — mouse-only interaction is not enough. </div></div><h3 class="sub" data-v-be77961e>Component selection guide</h3><table class="dt" data-v-be77961e><thead data-v-be77961e><tr data-v-be77961e><th data-v-be77961e>Scenario</th><th data-v-be77961e>Use</th></tr></thead><tbody data-v-be77961e><tr data-v-be77961e><td data-v-be77961e>Primary action (submit / confirm)</td><td data-v-be77961e><code data-v-be77961e>Button variant=primary</code></td></tr><tr data-v-be77961e><td data-v-be77961e>Secondary action / cancel</td><td data-v-be77961e><code data-v-be77961e>Button secondary</code> / <code data-v-be77961e>ghost</code></td></tr><tr data-v-be77961e><td data-v-be77961e>Destructive action (delete / abort)</td><td data-v-be77961e><code data-v-be77961e>Button danger</code> / <code data-v-be77961e>danger-soft</code></td></tr><tr data-v-be77961e><td data-v-be77961e>Status marker</td><td data-v-be77961e><code data-v-be77961e>Badge</code></td></tr><tr data-v-be77961e><td data-v-be77961e>Toolbar filter / model switch</td><td data-v-be77961e><code data-v-be77961e>Pill</code></td></tr><tr data-v-be77961e><td data-v-be77961e>2–5 mutually exclusive options</td><td data-v-be77961e><code data-v-be77961e>SegmentedControl</code></td></tr><tr data-v-be77961e><td data-v-be77961e>Top tabs</td><td data-v-be77961e><code data-v-be77961e>Tabs</code></td></tr><tr data-v-be77961e><td data-v-be77961e>Switch / multi-select</td><td data-v-be77961e><code data-v-be77961e>Switch</code> / <code data-v-be77961e>Checkbox</code></td></tr><tr data-v-be77961e><td data-v-be77961e>Scrollable regions with overlay controls</td><td data-v-be77961e><code data-v-be77961e>ScrollArea</code></td></tr><tr data-v-be77961e><td data-v-be77961e>Floating content card / list action menu</td><td data-v-be77961e><code data-v-be77961e>Card</code> / <code data-v-be77961e>Menu</code></td></tr><tr data-v-be77961e><td data-v-be77961e>Inline notice / global toast</td><td data-v-be77961e><code data-v-be77961e>Banner</code> / <code data-v-be77961e>Toast</code></td></tr><tr data-v-be77961e><td data-v-be77961e>Dialog / confirmation · bottom panel (mobile)</td><td data-v-be77961e><code data-v-be77961e>Dialog</code> / <code data-v-be77961e>Sheet</code></td></tr></tbody></table><h3 class="sub" data-v-be77961e>Button</h3><p data-v-be77961e>4 semantic variants × 3 sizes. The primary action <code data-v-be77961e>primary</code> takes its color from the current theme color (§05 can switch between the blue and black families). Radius uses <code data-v-be77961e>--radius-md</code> uniformly (small size <code data-v-be77961e>--radius-sm</code>), weight 600, with a visible focus ring.</p><div class="stage-wrap" data-v-be77961e><div class="stage-bar" data-v-be77961e><span class="st" data-v-be77961e>Variant matrix <span class="tag spec" data-v-be77961e>light</span></span><span class="sactions" data-v-be77961e><span class="tab on" data-v-be77961e>preview</span></span></div><div class="stage p col" data-v-be77961e><span class="stage-label" data-v-be77961e>medium · default</span><div class="demo-row" data-v-be77961e><button class="p-btn primary" data-v-be77961e>Primary action</button><button class="p-btn secondary" data-v-be77961e>Secondary action</button><button class="p-btn ghost" data-v-be77961e>Ghost button</button><button class="p-btn danger-soft" data-v-be77961e>Destructive (soft)</button><button class="p-btn danger" data-v-be77961e>Destructive action</button></div><span class="stage-label" data-v-be77961e>small</span><div class="demo-row" data-v-be77961e><button class="p-btn primary sm" data-v-be77961e>Confirm</button><button class="p-btn secondary sm" data-v-be77961e>Cancel</button><button class="p-btn ghost sm" data-v-be77961e>More</button></div><span class="stage-label" data-v-be77961e>With icon / state</span><div class="demo-row" data-v-be77961e><button class="p-btn primary" data-v-be77961e><svg class="p-ic" viewBox="0 0 24 24" fill="currentColor" data-v-be77961e><path fill="currentColor" d="M11 11V5h2v6h6v2h-6v6h-2v-6H5v-2z" data-v-be77961e></path></svg>New chat</button><button class="p-btn secondary" data-v-be77961e><svg class="p-ic" viewBox="0 0 24 24" fill="currentColor" data-v-be77961e><path fill="currentColor" d="m10 15.17l9.192-9.191l1.414 1.414L10 17.999l-6.364-6.364l1.414-1.414z" data-v-be77961e></path></svg>Copied</button><button class="p-btn primary disabled" data-v-be77961e>Loading…</button></div></div></div><div class="stage-wrap" data-v-be77961e><div class="stage-bar" data-v-be77961e><span class="st" data-v-be77961e>Dark skin <span class="tag spec" data-v-be77961e>dark</span></span></div><div class="stage dark p col" data-p="dark" data-v-be77961e><div class="demo-row" data-v-be77961e><button class="p-btn primary" data-v-be77961e>Primary action</button><button class="p-btn secondary" data-v-be77961e>Secondary action</button><button class="p-btn ghost" data-v-be77961e>Ghost button</button><button class="p-btn danger" data-v-be77961e>Destructive action</button></div></div></div><h4 class="mini" data-v-be77961e>API</h4><div class="code" data-v-be77961e><div class="code-bar" data-v-be77961e><span class="d" data-v-be77961e></span><span class="d" data-v-be77961e></span><span class="d" data-v-be77961e></span><span class="fn" data-v-be77961e>Button.vue · usage</span></div><pre data-v-be77961e><span class="k" data-v-be77961e><Button</span> <span class="p" data-v-be77961e>variant</span>=<span class="s" data-v-be77961e>"primary"</span> <span class="p" data-v-be77961e>size</span>=<span class="s" data-v-be77961e>"md"</span> <span class="p" data-v-be77961e>:loading</span>=<span class="s" data-v-be77961e>"submitting"</span><span class="k" data-v-be77961e>></span>Save<span class="k" data-v-be77961e></Button></span>
|
|
8
|
-
<span class="c" data-v-be77961e>// variant: primary | secondary | ghost | danger | danger-soft</span>
|
|
9
|
-
<span class="c" data-v-be77961e>// size: sm | md | lg</span></pre></div><div class="stage-wrap" data-v-be77961e><div class="stage-bar" data-v-be77961e><span class="st" data-v-be77961e>States</span></div><div class="stage p" data-v-be77961e><div class="demo-row" data-v-be77961e><button class="p-btn primary" disabled style="opacity:.5;cursor:not-allowed;" data-v-be77961e>Disabled primary</button><button class="p-btn primary" data-v-be77961e><svg class="p-spinner sm" viewBox="0 0 24 24" data-v-be77961e><circle class="track" cx="12" cy="12" r="9" data-v-be77961e></circle><circle class="arc" cx="12" cy="12" r="9" data-v-be77961e></circle></svg>Submitting</button><button class="p-btn danger" disabled style="opacity:.5;cursor:not-allowed;" data-v-be77961e>Disabled danger</button></div></div></div><h3 class="sub" data-v-be77961e>IconButton</h3><p data-v-be77961e>Unified into three sizes — 26 / 32 / 44px — with the neutral <code data-v-be77961e>--color-hover</code> wash on hover and a visible focus ring. Replaces the ad-hoc icon + click areas scattered across components today.</p><div class="stage-wrap" data-v-be77961e><div class="stage-bar" data-v-be77961e><span class="st" data-v-be77961e>IconButton</span></div><div class="stage p" data-v-be77961e><button class="p-icon-btn" data-v-be77961e><svg class="p-ic" viewBox="0 0 24 24" fill="currentColor" data-v-be77961e><path fill="currentColor" d="M11 11V5h2v6h6v2h-6v6h-2v-6H5v-2z" data-v-be77961e></path></svg></button><button class="p-icon-btn" data-v-be77961e><svg class="p-ic" viewBox="0 0 24 24" fill="currentColor" data-v-be77961e><path fill="currentColor" d="M3 4h18v2H3zm0 7h18v2H3zm0 7h18v2H3z" data-v-be77961e></path></svg></button><button class="p-icon-btn" data-v-be77961e><svg class="p-ic" viewBox="0 0 24 24" fill="currentColor" data-v-be77961e><path fill="currentColor" d="m12 10.587l4.95-4.95l1.414 1.414l-4.95 4.95l4.95 4.95l-1.415 1.414l-4.95-4.95l-4.949 4.95l-1.414-1.415l4.95-4.95l-4.95-4.95L7.05 5.638z" data-v-be77961e></path></svg></button><button class="p-icon-btn sm" data-v-be77961e><svg class="p-ic" viewBox="0 0 24 24" fill="currentColor" data-v-be77961e><path fill="currentColor" d="m18.031 16.617l4.283 4.282l-1.415 1.415l-4.282-4.283A8.96 8.96 0 0 1 11 20c-4.968 0-9-4.032-9-9s4.032-9 9-9s9 4.032 9 9a8.96 8.96 0 0 1-1.969 5.617m-2.006-.742A6.98 6.98 0 0 0 18 11c0-3.867-3.133-7-7-7s-7 3.133-7 7s3.133 7 7 7a6.98 6.98 0 0 0 4.875-1.975z" data-v-be77961e></path></svg></button><button class="p-icon-btn sm" data-v-be77961e><svg class="p-ic" viewBox="0 0 24 24" fill="currentColor" data-v-be77961e><path fill="currentColor" d="m10 15.17l9.192-9.191l1.414 1.414L10 17.999l-6.364-6.364l1.414-1.414z" data-v-be77961e></path></svg></button></div></div><div class="callout info" data-v-be77961e><span class="ico" data-v-be77961e>i</span><div data-v-be77961e> The desktop IconButton comes in <code data-v-be77961e>sm</code> 26 / <code data-v-be77961e>md</code> 32; on touch devices the tap target should be ≥ 44px, so use <code data-v-be77961e>lg</code> 44px, satisfying the §01 accessibility principle (the mobile three-piece set uses <code data-v-be77961e>lg</code>). </div></div><h3 class="sub" data-v-be77961e>Badge · Chip · Pill</h3><p data-v-be77961e>Collapsed into two kinds: <b data-v-be77961e>Badge</b> (status badge, with an optional status dot) and <b data-v-be77961e>Pill</b> (the clickable pill in the composer toolbar). Radius, font size, and padding are all unified.</p><div class="stage-wrap" data-v-be77961e><div class="stage-bar" data-v-be77961e><span class="st" data-v-be77961e>Badge · status badge</span></div><div class="stage p col" data-v-be77961e><span class="stage-label" data-v-be77961e>Semantic variants</span><div class="demo-row" data-v-be77961e><span class="p-badge neutral" data-v-be77961e><span class="bd" data-v-be77961e></span>pending</span><span class="p-badge info" data-v-be77961e><span class="bd" data-v-be77961e></span>running</span><span class="p-badge success" data-v-be77961e><span class="bd" data-v-be77961e></span>completed</span><span class="p-badge warning" data-v-be77961e><span class="bd" data-v-be77961e></span>needs confirmation</span><span class="p-badge danger" data-v-be77961e><span class="bd" data-v-be77961e></span>failed</span><span class="p-badge solid" data-v-be77961e>KIMI</span></div><span class="stage-label" data-v-be77961e>With icon / small size</span><div class="demo-row" data-v-be77961e><span class="p-badge info" data-v-be77961e><svg class="p-ic" viewBox="0 0 24 24" fill="currentColor" data-v-be77961e><path fill="currentColor" d="M4 3h16a1 1 0 0 1 1 1v16a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1m1 2v14h14V5z" data-v-be77961e></path></svg>plan</span><span class="p-badge success sm" data-v-be77961e><span class="bd" data-v-be77961e></span>passed</span><span class="p-badge neutral sm" data-v-be77961e>read-only</span></div></div></div>`,19)),a("div",_,[e[14]||(e[14]=a("div",{class:"stage-bar"},[a("span",{class:"st"},"Pill · toolbar pill (composer)")],-1)),a("div",J,[e[12]||(e[12]=t('<span class="p-pill" data-v-be77961e><svg class="p-ic" viewBox="0 0 24 24" fill="currentColor" data-v-be77961e><path fill="currentColor" d="M8 4h13v2H8zM4.5 6.5a1.5 1.5 0 1 1 0-3a1.5 1.5 0 0 1 0 3m0 7a1.5 1.5 0 1 1 0-3a1.5 1.5 0 0 1 0 3m0 6.9a1.5 1.5 0 1 1 0-3a1.5 1.5 0 0 1 0 3M8 11h13v2H8zm0 7h13v2H8z" data-v-be77961e></path></svg><span class="pp-strong" data-v-be77961e>kimi-k2</span><span class="pp-sub" data-v-be77961e>· thinking</span><svg class="p-ic" viewBox="0 0 24 24" fill="currentColor" data-v-be77961e><path fill="currentColor" d="m12 13.171l4.95-4.95l1.414 1.415L12 16L5.636 9.636L7.05 8.222z" data-v-be77961e></path></svg></span>',1)),a("span",Q,[c(b(h),{name:"shield-question",size:"sm"}),e[11]||(e[11]=s("yolo",-1))]),e[13]||(e[13]=a("span",{class:"p-pill"},[a("svg",{class:"p-ic",viewBox:"0 0 24 24",fill:"currentColor"},[a("path",{fill:"currentColor",d:"M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10s-4.477 10-10 10m0-2a8 8 0 1 0 0-16a8 8 0 0 0 0 16m1-8h4v2h-6V7h2z"})]),s("12k / 200k")],-1))])]),e[28]||(e[28]=t(`<h3 class="sub" data-v-be77961e>Kbd · keyboard shortcut</h3><p data-v-be77961e><b data-v-be77961e>Kbd</b> renders a shortcut as keycaps — one block per key, never inline text like <code data-v-be77961e>(⌘K)</code>. Caps are 18px tall (Badge sm rhythm): transparent ground with a 0.5px hairline edge, 11px <code data-v-be77961e>--font-kbd</code> (Inter + system-ui), text colour inherited from the row that carries it — the cap has no fill or colour of its own, so it follows its context (bright inside the accent-ringed recording box, quiet in a hint row). Typical placement: pushed to the row's trailing edge, opposite the label (e.g. the sidebar search row), and inside dialog navigation hints.</p><div class="stage-wrap" data-v-be77961e><div class="stage-bar" data-v-be77961e><span class="st" data-v-be77961e>Kbd · keycaps</span></div><div class="stage p" data-v-be77961e><span class="p-kbd" data-v-be77961e><kbd data-v-be77961e>⌘</kbd><kbd data-v-be77961e>K</kbd></span><span class="p-kbd" data-v-be77961e><kbd data-v-be77961e>Ctrl</kbd><kbd data-v-be77961e>K</kbd></span><span class="p-kbd" data-v-be77961e><kbd data-v-be77961e>⌘</kbd><kbd data-v-be77961e>⇧</kbd><kbd data-v-be77961e>P</kbd></span></div></div><h3 class="sub" data-v-be77961e>Card / Surface</h3><p data-v-be77961e>All cards across the site share <b data-v-be77961e>one structure</b> — <code data-v-be77961e>head / body / foot</code> — and come in two tiers by visual weight:</p><ul class="clean" data-v-be77961e><li data-v-be77961e><b data-v-be77961e>Operation card</b> —— composite "process" content such as the Swarm overview. (Individual tool calls are NOT cards anymore: they render as quiet borderless lines, see §04.) Flat shell: <code data-v-be77961e>0.5px</code> hairline, <code data-v-be77961e>--radius-md</code>, no shadow. The head is compact mono with no fill, low weight by default, not competing with the conversation.</li><li data-v-be77961e><b data-v-be77961e>Attention card</b> —— content that needs a user decision, such as Question / Approval. A floating neutral card: white raised surface, <code data-v-be77961e>--radius-lg</code>, a faint popover shadow (<code data-v-be77961e>--shadow-menu</code>), a plain dark title head, and a hairline footer whose actions read in number-key order (chips on the buttons) leading to one solid primary action. No semantic color band.</li></ul><div class="stage-wrap" data-v-be77961e><div class="stage-bar" data-v-be77961e><span class="st" data-v-be77961e>Operation card · compact mono head (no fill)</span></div><div class="stage p col" data-v-be77961e><div class="p-card" style="max-width:460px;" data-v-be77961e><div class="p-card-head" data-v-be77961e><span class="p-card-title" data-v-be77961e>read_file</span><span class="p-badge info sm" style="margin-left:auto;" data-v-be77961e>session.ts</span></div><div class="p-card-body" data-v-be77961e>The head uses mono + a neutral background to emphasize its "code / process" nature; the body uses sans for readability. Flat, radius-md, same shape as the Swarm composite card.</div></div></div></div><div class="stage-wrap" data-v-be77961e><div class="stage-bar" data-v-be77961e><span class="st" data-v-be77961e>Attention card · floating neutral surface (no color band)</span></div><div class="stage p col" data-v-be77961e><div class="p-action" style="max-width:460px;" data-v-be77961e><div class="p-action-head" data-v-be77961e><span class="p-action-title" data-v-be77961e>A decision needs your confirmation</span></div><div class="p-action-body" data-v-be77961e>A floating neutral card — no color band. The raised surface, large radius and soft shadow lift it above the transcript; the head is a plain dark title, and the hairline footer lines up quiet text buttons leading to one solid primary action.</div><div class="p-action-foot" data-v-be77961e><button class="p-btn ghost sm" data-v-be77961e>Dismiss</button><button class="p-btn primary sm" data-v-be77961e>Confirm</button></div></div></div></div><div class="stage-wrap" data-v-be77961e><div class="stage-bar" data-v-be77961e><span class="st" data-v-be77961e>Activity run · a summary row expands into the folded lines</span></div><div class="stage p col" data-v-be77961e><div class="p-tool-group open" style="max-width:460px;" data-v-be77961e><div class="p-tool-group-head" data-v-be77961e><svg class="tg-ic" viewBox="0 0 24 24" fill="currentColor" data-v-be77961e><path fill="currentColor" d="M9 2.003V2h10.998C20.55 2 21 2.455 21 2.992v18.016a.993.993 0 0 1-.993.992H3.993A1 1 0 0 1 3 20.993V8zM5.83 8H9V4.83zM11 4v5a1 1 0 0 1-1 1H5v10h14V4z" data-v-be77961e></path></svg><span class="tg-title" data-v-be77961e>Read 2 files</span></div><div class="p-tool-row" data-v-be77961e><span class="tr-name" data-v-be77961e>Read</span><span class="tr-file" data-v-be77961e>session.ts</span><span class="tr-faint" data-v-be77961e>src/auth</span><span class="tr-chip" data-v-be77961e>34 lines</span><span class="tr-ok" data-v-be77961e>✓</span></div><div class="p-tool-row" data-v-be77961e><span class="tr-name" data-v-be77961e>Read</span><span class="tr-file" data-v-be77961e>middleware.ts</span><span class="tr-faint" data-v-be77961e>src/auth</span><span class="tr-chip" data-v-be77961e>58 lines</span><span class="tr-ok" data-v-be77961e>✓</span></div></div></div></div><ul class="clean check" data-v-be77961e><li data-v-be77961e><b data-v-be77961e>One structure, two shells</b>: every card is <code data-v-be77961e>head / body / foot</code>; operation cards are flat + 0.5px hairline + radius-md with no shadow, while the attention card is the single exception — raised surface, radius-lg and a soft shadow, because it floats above the transcript in place of the composer.</li><li data-v-be77961e><b data-v-be77961e>Differences are intentional</b>: operation cards keep a compact mono head; attention cards get a plain dark title head and footer actions.</li><li data-v-be77961e><b data-v-be77961e>Grouping</b>: consecutive activity (thinking + tool calls of any kind, cards included) folds into ONE activity-run row — a smart summary sentence that expands into the items in order; only text and successful media tools (inline media is the turn's output) stay out and break the run (see §04).</li><li data-v-be77961e><b data-v-be77961e>Turn fold</b>: once an assistant turn settles, everything before its final text block (thinking, activity runs, interim text, standalone cards) folds into ONE bare "Worked Ns" row — no glyph, a faint one-line label + rotating chevron sharing the activity-run head's padding and hover language; while the turn streams the row stays hidden and the body forced open, and on settle the row appears and folds itself back. The span is the turn's elapsed time (daemon duration once settled, server message stamps for history; approval/question waits included by design), reading the generic "Work details" without any stamp. The final text — and anything after it, so trailing media / cards stay on screen — never folds; a text-only turn renders no row at all (see §04).</li><li data-v-be77961e><b data-v-be77961e>Status dots</b>: running (pulsing blue) / done (green) / failed (red), sharing one color vocabulary (see §04 tool calls).</li></ul><h3 class="sub" data-v-be77961e>Input / Select / Textarea</h3><p data-v-be77961e>Unified 38px height (32px small), <code data-v-be77961e>--radius-md</code> radius, <code data-v-be77961e>--color-surface-overlay</code> background, and a unified blue focus ring (<code data-v-be77961e>0 0 0 3px accent-soft</code>). Select is a custom combobox and listbox, not a native <code data-v-be77961e><select></code>; opening it centres the selected option in the scrollable menu. Open Select roots enter the dropdown layer; containing settings groups temporarily release clipping and join that layer so later sections cannot cover the menu.</p><div class="stage-wrap" data-v-be77961e><div class="stage-bar" data-v-be77961e><span class="st" data-v-be77961e>Form primitives</span></div><div class="stage p col" data-v-be77961e><div class="demo-row" style="align-items:flex-start;" data-v-be77961e><div class="p-field demo-grow" data-v-be77961e><label class="p-label" data-v-be77961e>Workspace name</label><input class="p-input" placeholder="e.g. frontend" data-v-be77961e><span class="p-hint" data-v-be77961e>Only letters, numbers, and hyphens are allowed.</span></div><div class="p-field demo-grow" data-v-be77961e><label class="p-label" data-v-be77961e>Model provider</label><button class="p-select" type="button" data-v-be77961e>Anthropic</button></div></div><div class="p-field" data-v-be77961e><label class="p-label" data-v-be77961e>System prompt</label><textarea class="p-textarea" placeholder="Describe this Agent's role and boundaries…" data-v-be77961e></textarea></div></div></div><div class="stage-wrap" data-v-be77961e><div class="stage-bar" data-v-be77961e><span class="st" data-v-be77961e>States</span></div><div class="stage p col" data-v-be77961e><div class="demo-row" style="align-items:flex-start;" data-v-be77961e><div class="p-field demo-grow" data-v-be77961e><label class="p-label" data-v-be77961e>Workspace name</label><input class="p-input" value="my workspace!" style="border-color:var(--p-danger);" data-v-be77961e><span class="p-field-error" data-v-be77961e>Please enter a valid workspace name</span></div><div class="p-field demo-grow" data-v-be77961e><label class="p-label" data-v-be77961e>Display name</label><input class="p-input" value="frontend" data-v-be77961e><span class="p-hint" data-v-be77961e>Normal state · validation passed</span></div></div></div></div><h3 class="sub" data-v-be77961e>Code / Diff</h3><p data-v-be77961e><b data-v-be77961e>Diff controls</b>: The non-selectable branch summary starts with a 14px branch icon, aligns to the panel header's 12px inset, uses 12px labels, and ends with a 0.5px hairline. List and tree choices use the 14px <code data-v-be77961e>list</code> and <code data-v-be77961e>tree-view</code> registry icons. Flat-list and tree-view paths use the UI font at 12px. Tree roots share the flat list's 14px content inset, then each depth advances by 12px and adds a grey indentation rule.</p><p data-v-be77961e><b data-v-be77961e>Diff empty state</b>: Centre the clean-workspace message in the available panel height and lead with a quiet 32px status icon.</p><p data-v-be77961e><b data-v-be77961e>Diff detail body</b>: the right-side diff detail reuses <code data-v-be77961e>HighlightedCode</code> unframed (the panel owns the edge and scroll) — shiki highlighting with the language inferred from the file path, an old/new line-number gutter, hunk headers as a muted band, at the shared code size <code data-v-be77961e>--code-font-size</code> (12px at Medium, one step below body text). The file preview's code body (text / JSON / HTML and Markdown source) renders through the same component with a per-row number gutter plus search-hit / jump-target row states.</p><p data-v-be77961e>Inline code, code blocks, and diff contents use the monospace font (<code data-v-be77961e>--p-font-mono</code>); diff change counts and branch summaries use the UI font. Code blocks have a filename title bar and a copy button; the action edge uses a compact 6px inset. Diffs use <code data-v-be77961e>+</code> / <code data-v-be77961e>-</code> row colors to express additions and deletions — additions use a success light background, deletions use a danger light background, with no gradients.</p><div class="stage-wrap" data-v-be77961e><div class="stage-bar" data-v-be77961e><span class="st" data-v-be77961e>Code / Diff</span></div><div class="stage p col" data-v-be77961e><span class="stage-label" data-v-be77961e>inline code</span><div data-v-be77961e>The server uses <code class="p-code-inline" data-v-be77961e>jwt.verify(token)</code> to verify the signature, returning 401 on failure.</div><span class="stage-label" data-v-be77961e>code block</span><div class="p-code-block" data-v-be77961e><div class="p-code-block-head" data-v-be77961e><span data-v-be77961e>session.ts</span><button class="p-icon-btn sm" aria-label="Copy" data-v-be77961e><svg class="p-ic" viewBox="0 0 24 24" fill="currentColor" data-v-be77961e><path fill="currentColor" d="M7 6V3a1 1 0 0 1 1-1h12a1 1 0 0 1 1 1v14a1 1 0 0 1-1 1h-3v3c0 .552-.45 1-1.007 1H4.007A1 1 0 0 1 3 21l.003-14c0-.552.45-1 1.006-1zM5.002 8L5 20h10V8zM9 6h8v10h2V4H9z" data-v-be77961e></path></svg></button></div><pre data-v-be77961e>import { verify } from './jwt';
|
|
10
|
-
|
|
11
|
-
export function auth(token: string) {
|
|
12
|
-
return verify(token, process.env.JWT_SECRET!);
|
|
13
|
-
}</pre></div><span class="stage-label" data-v-be77961e>diff</span><div class="p-diff" data-v-be77961e><div class="p-diff-head" data-v-be77961e>session.ts · +3 -1</div><div class="p-diff-row" data-v-be77961e><span class="pm" data-v-be77961e></span><span class="p-diff-code" data-v-be77961e>import { verify } from './jwt';</span></div><div class="p-diff-row del" data-v-be77961e><span class="pm" data-v-be77961e>-</span><span class="p-diff-code" data-v-be77961e>const secret = 'dev-secret';</span></div><div class="p-diff-row add" data-v-be77961e><span class="pm" data-v-be77961e>+</span><span class="p-diff-code" data-v-be77961e>const secret = process.env.JWT_SECRET!;</span></div><div class="p-diff-row" data-v-be77961e><span class="pm" data-v-be77961e></span><span class="p-diff-code" data-v-be77961e>return verify(token, secret);</span></div></div></div></div><h3 class="sub" data-v-be77961e>Dialog</h3><p data-v-be77961e>One dialog primitive replaces 6 hand-written implementations: unified <code data-v-be77961e>--radius-xl</code> radius, <code data-v-be77961e>--shadow-xl</code> shadow, 20px head padding, right-aligned footer actions, and an IconButton close button.</p><div class="stage-wrap" data-v-be77961e><div class="stage-bar" data-v-be77961e><span class="st" data-v-be77961e>Dialog primitive</span></div><div class="stage p col" style="align-items:center;" data-v-be77961e><div class="p-dialog" data-v-be77961e><div class="p-dialog-head" data-v-be77961e><div data-v-be77961e><div class="p-dialog-title" data-v-be77961e>New chat</div><div class="p-dialog-desc" data-v-be77961e>Create an independent Agent chat in the current workspace.</div></div><button class="p-icon-btn sm" data-v-be77961e><svg class="p-ic" viewBox="0 0 24 24" fill="currentColor" data-v-be77961e><path fill="currentColor" d="m12 10.587l4.95-4.95l1.414 1.414l-4.95 4.95l4.95 4.95l-1.415 1.414l-4.95-4.95l-4.949 4.95l-1.414-1.415l4.95-4.95l-4.95-4.95L7.05 5.638z" data-v-be77961e></path></svg></button></div><div class="p-dialog-body" data-v-be77961e><div class="p-field" data-v-be77961e><label class="p-label" data-v-be77961e>Chat title (optional)</label><input class="p-input" placeholder="Generated automatically" data-v-be77961e></div></div><div class="p-dialog-foot" data-v-be77961e><button class="p-btn secondary" data-v-be77961e>Cancel</button><button class="p-btn primary" data-v-be77961e>Create</button></div></div></div></div><div class="callout info" data-v-be77961e><span class="ico" data-v-be77961e>i</span><div data-v-be77961e><b data-v-be77961e>Size & height</b>: Dialog offers three widths — <code data-v-be77961e>md</code> 440 / <code data-v-be77961e>lg</code> 640 / <code data-v-be77961e>xl</code> 760 (<code data-v-be77961e>--p-content-max</code>) — chosen by content weight. Height comes in two kinds: <code data-v-be77961e>auto</code> (default, grows with content up to <code data-v-be77961e>max-height</code>) and <code data-v-be77961e>fixed</code> (constant height <code data-v-be77961e>min(680px, 100vh - 64px)</code>, with overflow scrolled inside the body). <b data-v-be77961e>Content / multi-tab dialogs</b> (settings, model picker, provider manager, folder browser) always use <code data-v-be77961e>fixed</code> so the frame size stays constant and doesn't jump when switching tabs or content length; short confirmation dialogs keep <code data-v-be77961e>auto</code>. Selectable controls inside Settings use 0.5px hairlines. Its navigation stays transparent on the grouped canvas — separated from the content region by the 0.5px hairline (horizontal in the stacked mobile layout) — and uses 12px labels at weight 525 with 16px registry icons; the selected tab paints the same neutral <code data-v-be77961e>--color-hover</code> wash as hover, with the label simply brightening to <code data-v-be77961e>--color-text</code> — the Kimi app settings nav's recipe (<code data-v-be77961e>.ss-nav-item--active</code> → <code data-v-be77961e>Fills-F1</code>, no accent tint, no weight change); section captions use 16px UI text in <code data-v-be77961e>--color-text</code>. Every setting row has a plain-language description; option labels use <code data-v-be77961e>--color-text</code> at weight 475 with a 1px gap before that description. Chinese descriptions use “思考” and “计划模式” rather than the English terms; “skills” stays lowercase when it appears within a sentence. Every settings section puts its rows inside one rounded group with 0.5px dividers; the content region paints the flat <code data-v-be77961e>--color-surface</code> so each group (<code data-v-be77961e>--color-surface-raised</code>) reads one rung above it — never a sunken pit, which would sink the dialog's content below its chrome in dark. The font-size stepper is a compact 32px UI-font control with 12px values and custom minus and plus buttons. Its 52px desktop row centres the control with equal space above and below. Archived workspace headings reuse the sidebar’s <code data-v-be77961e>folder-closed</code> registry icon, and Restore actions lead with the <code data-v-be77961e>undo</code> icon. Archive counts use weight 500; timestamps and workspace paths use the UI font. </div></div><p data-v-be77961e><b data-v-be77961e>Dialog backdrop</b>: Use a restrained 28% neutral overlay so the workspace remains legible without competing with the modal.</p><p data-v-be77961e><b data-v-be77961e>Settings regions</b>: The settings title and close action belong to the right content region. The navigation is a separate full-height region that starts at the dialog's top edge, not content beneath a dialog-wide header.</p><p data-v-be77961e><b data-v-be77961e>Archived sessions</b>: Start with the localized page title. Do not add a repeated English kicker above it.</p><p data-v-be77961e><b data-v-be77961e>Settings interaction</b>: Notification labels and descriptions are not selectable; their switches remain fully interactive.</p><p data-v-be77961e><b data-v-be77961e>Conversation chrome</b>: Header labels are not selectable; the rename input remains selectable and editable. Branch names start with a 14px branch icon. The overflow trigger is a compact 24px control with a 14px icon. Below a 720px header container, hide the workspace prefix and give the conversation title the available width. On macOS desktop the header doubles as the window-drag region and interactive controls opt out with no-drag; while one of its menus or a dock work panel is open every window-drag strip (chat header, sidebar header, panel header) drops the drag region so an outside press anywhere reaches the page and dismisses the overlay (window dragging is simply paused).</p><p data-v-be77961e><b data-v-be77961e>Session search</b>: follows the §09 flush picker anatomy — a boxed Input under the head, and a result list that fills the body's available height and owns vertical scrolling.</p><p data-v-be77961e><b data-v-be77961e>Model picker</b>: follows the §09 flush picker anatomy; the provider filter remains horizontally scrollable without showing a persistent scrollbar. Only the model list scrolls; the shortcut bar remains pinned at the bottom.</p><h3 class="sub" data-v-be77961e>Toast</h3><p data-v-be77961e>Unified information architecture: status icon + title + description. The status color appears only on the icon, avoiding large colored areas that create visual noise. For an <b data-v-be77961e>undoable action</b> there is a second, lighter form — the <b data-v-be77961e>Action toast</b> (<code data-v-be77961e>ActionToast.vue</code>): a pill floating top-center just below the 48px header, carrying a one-line sentence whose actions are plain inline <code data-v-be77961e><button></code>s (styled accent by the component), plus close. Self-timed (default 8s, hover pauses); the parent re-keys to reset and wraps it in a <code data-v-be77961e><Transition></code>. First used by session archive (Undo / Settings); warnings keep the bottom-right <code data-v-be77961e>Toast</code> stack.</p><div class="stage-wrap" data-v-be77961e><div class="stage-bar" data-v-be77961e><span class="st" data-v-be77961e>Toast</span></div><div class="stage p col" data-v-be77961e><div class="p-toast success" data-v-be77961e><span class="ti" data-v-be77961e><svg class="p-ic" viewBox="0 0 24 24" fill="currentColor" data-v-be77961e><path fill="currentColor" d="m10 15.17l9.192-9.191l1.414 1.414L10 17.999l-6.364-6.364l1.414-1.414z" data-v-be77961e></path></svg></span><div data-v-be77961e><div class="tt" data-v-be77961e>Connected to server</div><div class="td" data-v-be77961e>The local server is responding normally; you can start a new chat.</div></div></div><div class="p-toast warning" data-v-be77961e><span class="ti" data-v-be77961e><svg class="p-ic" viewBox="0 0 24 24" fill="currentColor" data-v-be77961e><path fill="currentColor" d="m12.866 3l9.526 16.5a1 1 0 0 1-.866 1.5H2.474a1 1 0 0 1-.866-1.5L11.134 3a1 1 0 0 1 1.732 0m-8.66 16h15.588L12 5.5zM11 16h2v2h-2zm0-7h2v5h-2z" data-v-be77961e></path></svg></span><div data-v-be77961e><div class="tt" data-v-be77961e>Context usage 82%</div><div class="td" data-v-be77961e>Consider running /compact to free up space.</div></div></div></div></div><div class="stage-wrap" data-v-be77961e><div class="stage-bar" data-v-be77961e><span class="st" data-v-be77961e>Action toast</span></div><div class="stage p col" data-v-be77961e><div class="p-action-toast" data-v-be77961e><button class="lk" data-v-be77961e>Undo</button><span data-v-be77961e>or view archived chats in</span><button class="lk" data-v-be77961e>Settings</button><svg class="p-ic x" viewBox="0 0 24 24" fill="currentColor" data-v-be77961e><path d="M17.9542 4.77253C18.3056 4.42106 18.8761 4.42106 19.2276 4.77253C19.579 5.12401 19.579 5.69452 19.2276 6.04597L13.2735 12.0001L19.2276 17.9542C19.5791 18.3056 19.5791 18.8761 19.2276 19.2276C18.8761 19.5791 18.3056 19.5791 17.9542 19.2276L12.0001 13.2735L6.04595 19.2276C5.69451 19.5791 5.12399 19.579 4.77252 19.2276C4.42104 18.8761 4.42104 18.3056 4.77252 17.9542L10.7266 12.0001L4.77252 6.04597C4.42104 5.6945 4.42104 5.124 4.77252 4.77253C5.12399 4.42107 5.69448 4.42106 6.04595 4.77253L12.0001 10.7266L17.9542 4.77253Z" data-v-be77961e></path></svg></div></div></div><h3 class="sub" data-v-be77961e>Spinner</h3><p data-v-be77961e>Loaders fall into two categories by scenario — <b data-v-be77961e>do not mix them</b>:</p><ul class="clean" data-v-be77961e><li data-v-be77961e><b data-v-be77961e>Spinner (plain · SVG ring)</b> —— the default loader. Used for button loading, app startup (GlobalLoading), and general inline waits — "everything else".</li><li data-v-be77961e><b data-v-be77961e>WorkingIndicator (小蓝 mascot · brand signature)</b> —— used <b data-v-be77961e>only</b> for the chat working state after a prompt is sent (the sending placeholder in ChatPane, the send → first-token loading in SideChatPanel). The label follows the phase: "Requesting…" until the assistant's reply starts, then "Working…".</li></ul><h4 class="mini" data-v-be77961e>Spinner · plain loader (default)</h4>`,39)),a("div",Y,[e[18]||(e[18]=a("div",{class:"stage-bar"},[a("span",{class:"st"},"Spinner · common scenarios")],-1)),a("div",Z,[a("div",X,[e[17]||(e[17]=t('<svg class="p-spinner" viewBox="0 0 24 24" data-v-be77961e><circle class="track" cx="12" cy="12" r="9" data-v-be77961e></circle><circle class="arc" cx="12" cy="12" r="9" data-v-be77961e></circle></svg><span class="p-thinking" data-v-be77961e><svg class="p-spinner sm" viewBox="0 0 24 24" data-v-be77961e><circle class="track" cx="12" cy="12" r="9" data-v-be77961e></circle><circle class="arc" cx="12" cy="12" r="9" data-v-be77961e></circle></svg>Loading…</span>',2)),a("button",$,[(o(),i("svg",ee,[...e[15]||(e[15]=[a("circle",{class:"track",cx:"12",cy:"12",r:"9"},null,-1),a("circle",{class:"arc",cx:"12",cy:"12",r:"9"},null,-1)])])),e[16]||(e[16]=s("Submitting",-1))])])])]),e[29]||(e[29]=a("h4",{class:"mini"},"WorkingIndicator · 小蓝 mascot (only the chat working state)",-1)),a("div",ae,[e[20]||(e[20]=a("div",{class:"stage-bar"},[a("span",{class:"st"},[s("WorkingIndicator · chat working state only "),a("span",{class:"tag spec"},"signature")])],-1)),a("div",te,[e[19]||(e[19]=a("span",{class:"stage-label"},"Usage · only while the chat has an unfinished prompt",-1)),a("div",se,[c(k,{label:"Requesting…"}),c(k,{label:"Working…"})])])]),e[30]||(e[30]=t('<div class="callout info" data-v-be77961e><span class="ico" data-v-be77961e>i</span><div data-v-be77961e>The chat working state is rendered uniformly by <code data-v-be77961e>WorkingIndicator</code> — the 小蓝 mascot (<code data-v-be77961e>KimiMascot</code>, the kimi.com avatar Rive asset, with a static SVG fallback under reduced motion or when the runtime fails) plus a phase label. All other loading states use the plain Spinner.</div></div><h3 class="sub" data-v-be77961e>Link</h3><p data-v-be77961e>Inline text link: the default is the accent color with no underline; on hover it shows an underline and darkens. File links inside inline code use a 1.5px underline offset so the line stays clear of the chip background. The <code data-v-be77961e>.muted</code> variant uses the secondary text color. Used for in-text jumps, external links, "view all", and other lightweight actions.</p><div class="stage-wrap" data-v-be77961e><div class="stage-bar" data-v-be77961e><span class="st" data-v-be77961e>Link · inline link</span></div><div class="stage p col" data-v-be77961e><div class="demo-row" style="font-size:var(--p-font-size-base);color:var(--p-text);" data-v-be77961e><span data-v-be77961e>Read the full <a class="p-link" href="#" data-v-be77961e>design token docs</a> before building.</span><a class="p-link" href="#" data-v-be77961e>View on GitHub<svg class="p-ic" viewBox="0 0 24 24" fill="currentColor" data-v-be77961e><path fill="currentColor" d="M10 6v2H5v11h11v-5h2v6a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1zm11-3v8h-2V6.413l-7.793 7.794l-1.414-1.414L17.585 5H13V3z" data-v-be77961e></path></svg></a><a class="p-link muted" href="#" data-v-be77961e>View history</a></div></div></div><h3 class="sub" data-v-be77961e>Menu / Dropdown</h3><p data-v-be77961e>Desktop menus use a 3.5px panel inset. Standard items use 5px × 9px padding and a 7px icon gap. Their three-layer neutral shadow stays below 4% opacity.</p><p data-v-be77961e>Dropdown menu panel: frosted glass — the translucent <code data-v-be77961e>--color-menu-bg</code> fill over a blurred, saturated page backdrop (<code data-v-be77961e>--p-menu-backdrop</code>) — plus hairline + light shadow (<code data-v-be77961e>--shadow-menu</code>, a three-layer neutral ramp). This is the one place glassmorphism is the design language rather than an exception (§06); every floating menu surface (Menu.vue, the Select listbox, composer dropdowns, slash/mention popups) uses the token pair, never ad-hoc blur values. Menu items support icons, the current (active) state, the danger state, and the disabled state, with separators grouping items. All menu actions use 13px labels at weight 475 with 16px leading icons; both share a 16px line box for vertical alignment. Menu timestamps use the UI font. On touch / mobile, use <code data-v-be77961e>lg</code> (≥44px row height) while keeping the same type size. A dropdown menu pops in from its trigger corner — fade plus a slight 0.97 scale over <code data-v-be77961e>--duration-base</code> (exit <code data-v-be77961e>--duration-fast</code>), the composer model dropdown's motion language; the transform origin and the nudge direction follow the anchoring, including the upward flip near the viewport edge.</p><p data-v-be77961e>Row states: hover uses the mode-aware <code data-v-be77961e>--color-hover</code> wash (it lightens under dark, never darkens); a leading icon sits one rung below the label (<code data-v-be77961e>--muted</code>), and on hover both label and icon step up to <code data-v-be77961e>--color-text-strong</code>, the max foreground tier. Selection keeps the accent pair (<code data-v-be77961e>--color-accent-soft</code> / <code data-v-be77961e>--color-accent-hover</code>); danger keeps its own colour.</p><div class="stage-wrap" data-v-be77961e><div class="stage-bar" data-v-be77961e><span class="st" data-v-be77961e>Menu · dropdown menu</span></div><div class="stage p col" style="align-items:flex-start;" data-v-be77961e><div class="p-menu" data-v-be77961e><div class="p-menu-item" data-v-be77961e><svg class="p-ic" viewBox="0 0 24 24" fill="currentColor" data-v-be77961e><path fill="currentColor" d="M9 2.003V2h10.998C20.55 2 21 2.455 21 2.992v18.016a.993.993 0 0 1-.993.992H3.993A1 1 0 0 1 3 20.993V8zM5.83 8H9V4.83zM11 4v5a1 1 0 0 1-1 1H5v10h14V4z" data-v-be77961e></path></svg>Open file</div><div class="p-menu-item active" data-v-be77961e><svg class="p-ic" viewBox="0 0 24 24" fill="currentColor" data-v-be77961e><path fill="currentColor" d="m10 15.17l9.192-9.191l1.414 1.414L10 17.999l-6.364-6.364l1.414-1.414z" data-v-be77961e></path></svg>Selected item</div><div class="p-menu-item disabled" data-v-be77961e><svg class="p-ic" viewBox="0 0 24 24" fill="currentColor" data-v-be77961e><path fill="currentColor" d="M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10s-4.477 10-10 10m0-2a8 8 0 1 0 0-16a8 8 0 0 0 0 16M8.523 7.109l8.368 8.368a6 6 0 0 1-1.414 1.414L7.109 8.523A6 6 0 0 1 8.523 7.11" data-v-be77961e></path></svg>Disabled item</div><div class="p-menu-sep" data-v-be77961e></div><div class="p-menu-item danger" data-v-be77961e><svg class="p-ic" viewBox="0 0 24 24" fill="currentColor" data-v-be77961e><path fill="currentColor" d="m12 10.587l4.95-4.95l1.414 1.414l-4.95 4.95l4.95 4.95l-1.415 1.414l-4.95-4.95l-4.949 4.95l-1.414-1.415l4.95-4.95l-4.95-4.95L7.05 5.638z" data-v-be77961e></path></svg>Delete chat</div></div></div></div><h3 class="sub" data-v-be77961e>SegmentedControl</h3><p data-v-be77961e>Mutually exclusive short option groups, commonly used for 2–5 option switches such as "light / dark / follow system" or the four font-scale steps. Options may include a 14px registry icon or a colour swatch. A single raised indicator with a soft shadow (no border — the edge stays clean) slides and resizes between options using the standard motion tokens. Three sizes: <code data-v-be77961e>md</code> (default, settings pages), <code data-v-be77961e>sm</code> (compact rows), and <code data-v-be77961e>xs</code> (dense menus such as the composer model dropdown — 20px items, 12px labels).</p><div class="stage-wrap" data-v-be77961e><div class="stage-bar" data-v-be77961e><span class="st" data-v-be77961e>SegmentedControl</span></div><div class="stage p col" data-v-be77961e><div class="p-seg" data-v-be77961e><span class="p-seg-item on" data-v-be77961e>Light</span><span class="p-seg-item" data-v-be77961e>Dark</span><span class="p-seg-item" data-v-be77961e>Follow system</span></div></div></div><h3 class="sub" data-v-be77961e>SecondaryModelPicker</h3><p data-v-be77961e>Linked model + thinking-effort picker (settings → Agent → Subagents, experimental; <code data-v-be77961e>components/settings/SecondaryModelPicker.vue</code>, shared by both ends). Use it whenever two choices are only valid as a pair — here an effort is meaningless without its model, and every model declares a different supported set. It is a cascading variant of the §03 Select: the trigger is the Select trigger verbatim (value renders <code data-v-be77961e>model · effort</code>, the unset state uses the placeholder tint), and the dropdown opens as a SINGLE-LEVEL model list (grouped by provider) on the floating menu surface (<code data-v-be77961e>--color-menu-bg</code> / <code data-v-be77961e>--p-menu-backdrop</code> / <code data-v-be77961e>--shadow-lg</code>). The menu <b data-v-be77961e>teleports to <code data-v-be77961e><body></code> with <code data-v-be77961e>position: fixed</code></b> — it opens on top of the settings modal (on the <code data-v-be77961e>--z-modal-dropdown</code> rung), and only a body-level surface escapes the dialog's scrolling-body clip; it re-anchors to the trigger on any outside scroll and closes on window resize (the UserMenu teleport's full recipe). Hovering or clicking a model row flies its effort submenu out to the RIGHT of the row — same menu surface, anchored to the row's live position, flipping to the left only near the viewport edge per the §03 anchoring rules — with a 250ms hover-intent grace (the UserMenu flyout's recipe) so the diagonal path into the submenu doesn't collapse it. Every model row carries a trailing <code data-v-be77961e>chevron-right</code> affordance; clicking an effort confirms the pair and closes — one atomic write, never two staggered patches. Flyout options follow the composer's thinking-level model (<code data-v-be77961e>segmentsFor</code>): effort models get <code data-v-be77961e>off</code> + their declared levels (always-thinking ones get no off), boolean-thinking models get <code data-v-be77961e>on</code>/<code data-v-be77961e>off</code>, unsupported models get <code data-v-be77961e>off</code> alone; while no effort is set at all, a "Model default" entry leads (it writes the model alone — POST /config merges and cannot clear a stored effort, so the entry disappears once one is set). A configured effort the model no longer declares is appended as an extra flyout option so the current pair stays visible and re-selectable. Keyboard mirrors the Select contract (focus stays on the trigger, Esc <code data-v-be77961e>preventDefault</code>s so the hosting dialog does not close): ↑/↓ move within the active level (the flyout follows model moves), → opens the flyout, ← collapses it, Enter confirms, Home/End jump. ARIA: combobox trigger → <code data-v-be77961e>dialog</code> menu holding a model <code data-v-be77961e>listbox</code> plus the effort <code data-v-be77961e>listbox</code> flyout with <code data-v-be77961e>option</code> rows. The menu itself flips upward when the trigger sits near the viewport bottom.</p><h3 class="sub" data-v-be77961e>Tabs</h3><p data-v-be77961e>Tabs with a bottom hairline, used for grouping and switching sibling content. The current tab is marked with accent text + an accent underline.</p><div class="stage-wrap" data-v-be77961e><div class="stage-bar" data-v-be77961e><span class="st" data-v-be77961e>Tabs</span></div><div class="stage p col" data-v-be77961e><div class="p-tabs" data-v-be77961e><span class="p-tab on" data-v-be77961e>General</span><span class="p-tab" data-v-be77961e>Agent</span><span class="p-tab" data-v-be77961e>Advanced</span></div></div></div><h3 class="sub" data-v-be77961e>Switch</h3><p data-v-be77961e>A two-state switch for settings that take effect immediately. The 36×20 track has a 0.5px hairline and full radius; its 16px knob uses 1.5px internal offsets so the visible inset remains 2px and symmetric after accounting for the border. On hover, the knob eases to an 18px rounded rectangle towards the track centre. When on, the track turns accent and the knob slides right.</p><div class="stage-wrap" data-v-be77961e><div class="stage-bar" data-v-be77961e><span class="st" data-v-be77961e>Switch</span></div><div class="stage p" data-v-be77961e><span class="p-switch on" data-v-be77961e></span><span class="p-switch" data-v-be77961e></span></div></div><h3 class="sub" data-v-be77961e>Checkbox</h3><p data-v-be77961e>A 17×17 checkbox. When checked it fills with the accent color and shows a white tick (inline SVG). Often paired with a text label.</p><div class="stage-wrap" data-v-be77961e><div class="stage-bar" data-v-be77961e><span class="st" data-v-be77961e>Checkbox</span></div><div class="stage p" data-v-be77961e><span class="p-check on" data-v-be77961e><svg class="p-ic" viewBox="0 0 24 24" fill="currentColor" data-v-be77961e><path fill="currentColor" d="m10 15.17l9.192-9.191l1.414 1.414L10 17.999l-6.364-6.364l1.414-1.414z" data-v-be77961e></path></svg></span><span class="p-check" data-v-be77961e></span><label style="display:inline-flex;align-items:center;gap:8px;color:var(--p-text);font-size:var(--p-font-size-base);cursor:pointer;" data-v-be77961e><span class="p-check on" data-v-be77961e><svg class="p-ic" viewBox="0 0 24 24" fill="currentColor" data-v-be77961e><path fill="currentColor" d="m10 15.17l9.192-9.191l1.414 1.414L10 17.999l-6.364-6.364l1.414-1.414z" data-v-be77961e></path></svg></span>Enable auto-save</label></div></div><h3 class="sub" data-v-be77961e>Avatar</h3><p data-v-be77961e>A 32px default avatar with md radius; <code data-v-be77961e>.sm</code> is 24px. Can hold an initial or an icon; falls back to this placeholder when there is no image.</p><div class="stage-wrap" data-v-be77961e><div class="stage-bar" data-v-be77961e><span class="st" data-v-be77961e>Avatar</span></div><div class="stage p" data-v-be77961e><span class="p-avatar" data-v-be77961e>K</span><span class="p-avatar" data-v-be77961e><svg class="p-ic" viewBox="0 0 24 24" fill="currentColor" data-v-be77961e><path fill="currentColor" d="M4 22a8 8 0 1 1 16 0h-2a6 6 0 0 0-12 0zm8-9c-3.315 0-6-2.685-6-6s2.685-6 6-6s6 2.685 6 6s-2.685 6-6 6m0-2c2.21 0 4-1.79 4-4s-1.79-4-4-4s-4 1.79-4 4s1.79 4 4 4" data-v-be77961e></path></svg></span><span class="p-avatar sm" data-v-be77961e>K</span></div></div><h3 class="sub" data-v-be77961e>EmptyState</h3><p data-v-be77961e>A centered placeholder for empty lists / panels: a 48px faint icon + title + hint, avoiding blank pages.</p><div class="stage-wrap" data-v-be77961e><div class="stage-bar" data-v-be77961e><span class="st" data-v-be77961e>EmptyState</span></div><div class="stage p col" data-v-be77961e><div class="p-empty" style="width:100%;border:0.5px dashed var(--p-line);border-radius:var(--p-r-lg);" data-v-be77961e><svg class="em-ic" viewBox="0 0 24 24" fill="currentColor" data-v-be77961e><path fill="currentColor" d="M6.455 19L2 22.5V4a1 1 0 0 1 1-1h18a1 1 0 0 1 1 1v14a1 1 0 0 1-1 1zm-.692-2H20V5H4v13.385zM8 10h8v2H8z" data-v-be77961e></path></svg><div class="em-title" data-v-be77961e>No chats yet</div><div class="em-hint" data-v-be77961e>Click "New chat" to start a conversation with Kimi</div></div></div></div><h3 class="sub" data-v-be77961e>Divider</h3><p data-v-be77961e>A 0.5px hairline divider (<code data-v-be77961e>--p-line</code>); <code data-v-be77961e>.p-divider-v</code> is the vertical divider, used between inline elements.</p><div class="stage-wrap" data-v-be77961e><div class="stage-bar" data-v-be77961e><span class="st" data-v-be77961e>Divider</span></div><div class="stage p col" data-v-be77961e><div style="width:100%;font-size:var(--p-font-size-sm);color:var(--p-text);" data-v-be77961e>Content above</div><hr class="p-divider" data-v-be77961e><div style="width:100%;font-size:var(--p-font-size-sm);color:var(--p-text);" data-v-be77961e>Content below</div><div style="display:flex;align-items:center;gap:10px;height:24px;font-size:var(--p-font-size-sm);color:var(--p-text);" data-v-be77961e><span data-v-be77961e>kimi-k2</span><span class="p-divider-v" data-v-be77961e></span><span data-v-be77961e>thinking</span></div></div></div><h3 class="sub" data-v-be77961e>Tooltip</h3><p data-v-be77961e>A CSS-only hover hint, wrapped in <code data-v-be77961e>.p-tip</code>. Inverted background (<code data-v-be77961e>--p-text</code> / <code data-v-be77961e>--p-bg</code>), single line, no wrapping — carries only short notes.</p><div class="stage-wrap" data-v-be77961e><div class="stage-bar" data-v-be77961e><span class="st" data-v-be77961e>Tooltip (hover the button)</span></div><div class="stage p" data-v-be77961e><span class="p-tip" data-v-be77961e><button class="p-icon-btn" data-v-be77961e><svg class="p-ic" viewBox="0 0 24 24" fill="currentColor" data-v-be77961e><path fill="currentColor" d="M11 11V5h2v6h6v2h-6v6h-2v-6H5v-2z" data-v-be77961e></path></svg></button><span class="p-tooltip" data-v-be77961e>New chat</span></span></div></div><h3 class="sub" data-v-be77961e>Banner</h3><p data-v-be77961e>An inline notice bar placed at the top of a content area. Three states — <code data-v-be77961e>.info</code> / <code data-v-be77961e>.warning</code> / <code data-v-be77961e>.danger</code> — each with a matching 18px icon.</p><div class="stage-wrap" data-v-be77961e><div class="stage-bar" data-v-be77961e><span class="st" data-v-be77961e>Banner</span></div><div class="stage p col" data-v-be77961e><div class="p-banner info" data-v-be77961e><svg class="bn-ic" viewBox="0 0 24 24" fill="currentColor" data-v-be77961e><path fill="currentColor" d="M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10s-4.477 10-10 10m0-2a8 8 0 1 0 0-16a8 8 0 0 0 0 16M11 7h2v2h-2zm0 4h2v6h-2z" data-v-be77961e></path></svg>Connected to server</div><div class="p-banner warning" data-v-be77961e><svg class="bn-ic" viewBox="0 0 24 24" fill="currentColor" data-v-be77961e><path fill="currentColor" d="m12.866 3l9.526 16.5a1 1 0 0 1-.866 1.5H2.474a1 1 0 0 1-.866-1.5L11.134 3a1 1 0 0 1 1.732 0m-8.66 16h15.588L12 5.5zM11 16h2v2h-2zm0-7h2v5h-2z" data-v-be77961e></path></svg>Currently in yolo mode; tool calls will run automatically</div></div></div><h3 class="sub" data-v-be77961e>Sheet / BottomSheet</h3><p data-v-be77961e>A mobile bottom slide-up panel: xl top radius + drag handle, xl shadow. At ≤640px, dialogs become bottom-anchored Sheets.</p><div class="stage-wrap" data-v-be77961e><div class="stage-bar" data-v-be77961e><span class="st" data-v-be77961e>BottomSheet</span></div><div class="stage p col" style="align-items:center;" data-v-be77961e><div class="p-sheet" style="width:100%;max-width:360px;" data-v-be77961e><div class="p-sheet-handle" data-v-be77961e></div><div style="font-size:var(--p-font-size-base);font-weight:700;color:var(--p-text);margin-bottom:8px;" data-v-be77961e>Choose a model</div><div class="p-menu-item" style="padding:8px 10px;" data-v-be77961e>kimi-k2 · thinking</div><div class="p-menu-item" style="padding:8px 10px;" data-v-be77961e>kimi-k2 · instant</div></div></div></div><h3 class="sub" data-v-be77961e>Skeleton</h3><p data-v-be77961e>A placeholder for loading content, using a breathing opacity animation (no gradients), following the <code data-v-be77961e>no-gradient-text</code> rule. Composed into titles / text lines / avatars.</p><div class="stage-wrap" data-v-be77961e><div class="stage-bar" data-v-be77961e><span class="st" data-v-be77961e>Skeleton</span></div><div class="stage p col" data-v-be77961e><div style="display:flex;flex-direction:column;gap:10px;width:100%;max-width:360px;" data-v-be77961e><div class="p-skeleton" style="height:16px;width:55%;" data-v-be77961e></div><div class="p-skeleton" style="height:12px;width:100%;" data-v-be77961e></div><div class="p-skeleton" style="height:12px;width:82%;" data-v-be77961e></div><div class="p-skeleton" style="height:32px;width:32px;border-radius:var(--p-r-full);" data-v-be77961e></div></div></div></div><h3 class="sub" data-v-be77961e>Command Bar</h3><p data-v-be77961e>An inline combination of "primary action + command text + copy", sitting between a button and a code block — used for install / onboarding / one-click execution. The primary action reuses <code data-v-be77961e>Button primary</code>; the command area uses a mono light-grey background.</p><div class="stage-wrap" data-v-be77961e><div class="stage-bar" data-v-be77961e><span class="st" data-v-be77961e>Command Bar</span></div><div class="stage p col" data-v-be77961e><div class="p-cmdbar" style="max-width:620px;" data-v-be77961e><button class="p-btn primary" data-v-be77961e>Install Kimi Web ▾</button><span class="p-cmd" data-v-be77961e><span class="cmd-text" data-v-be77961e>curl -fsSL https://code.kimi.com/install.sh | bash</span><button class="cmd-copy" data-v-be77961e><svg class="p-ic" viewBox="0 0 24 24" fill="currentColor" data-v-be77961e><path fill="currentColor" d="M7 6V3a1 1 0 0 1 1-1h12a1 1 0 0 1 1 1v14a1 1 0 0 1-1 1h-3v3c0 .552-.45 1-1.007 1H4.007A1 1 0 0 1 3 21l.003-14c0-.552.45-1 1.006-1zM5.002 8L5 20h10V8zM9 6h8v10h2V4H9z" data-v-be77961e></path></svg></button></span></div></div></div><h3 class="sub" data-v-be77961e>TopBar</h3><p data-v-be77961e>The application top bar. Solid by default; the <code data-v-be77961e>.frost</code> variant is translucent + background blur, used <b data-v-be77961e>only for sticky navigation bars</b>. Together with the floating menu surfaces (Menu / Dropdown), it is one of the two exceptions to the <code data-v-be77961e>no-glassmorphism</code> rule (see §06).</p><div class="stage-wrap" data-v-be77961e><div class="stage-bar" data-v-be77961e><span class="st" data-v-be77961e>TopBar · solid / frosted glass</span></div><div class="stage p col" style="gap:14px;background:radial-gradient(circle at 18% 30%,rgba(23,131,255,.16),transparent 42%),radial-gradient(circle at 82% 75%,rgba(20,23,28,.10),transparent 46%),var(--p-surface-sunken);" data-v-be77961e><div class="p-topbar" style="width:100%;max-width:580px;" data-v-be77961e><span class="tb-title" data-v-be77961e>Solid TopBar</span><span class="tb-actions" data-v-be77961e><button class="p-icon-btn sm" data-v-be77961e><svg class="p-ic" viewBox="0 0 24 24" fill="currentColor" data-v-be77961e><path fill="currentColor" d="M3 4h18v2H3zm0 7h18v2H3zm0 7h18v2H3z" data-v-be77961e></path></svg></button></span></div><div class="p-topbar frost" style="width:100%;max-width:580px;" data-v-be77961e><span class="tb-title" data-v-be77961e>Frosted-glass TopBar · .frost</span><span class="tb-actions" data-v-be77961e><button class="p-icon-btn sm" data-v-be77961e><svg class="p-ic" viewBox="0 0 24 24" fill="currentColor" data-v-be77961e><path fill="currentColor" d="M3 4h18v2H3zm0 7h18v2H3zm0 7h18v2H3z" data-v-be77961e></path></svg></button></span></div></div></div><h3 class="sub" data-v-be77961e>Find Bar · transcript search</h3><p data-v-be77961e>The in-transcript find bar (Cmd/Ctrl+F), implemented by <code data-v-be77961e>components/chat/TranscriptSearch.vue</code>. A floating card pinned to the transcript's top-right (<code data-v-be77961e>top: --panel-head-h + --space-3</code>, <code data-v-be77961e>right: --space-3</code> — equal inset on both axes), <code data-v-be77961e>--z-sticky</code>, raised surface + 0.5px hairline + <code data-v-be77961e>--shadow-menu</code>. <b data-v-be77961e>One radius for both states</b>: <code data-v-be77961e>--radius-2xl</code> is a full capsule at the collapsed height and a card once the footer expands — never animate between two radii.</p><table class="dt" data-v-be77961e><thead data-v-be77961e><tr data-v-be77961e><th data-v-be77961e>Part</th><th data-v-be77961e>Rule</th></tr></thead><tbody data-v-be77961e><tr data-v-be77961e><td class="tk" data-v-be77961e>Input row</td><td data-v-be77961e>Search icon (muted) + <b data-v-be77961e>bare input</b> — the list-style bare-input exception family (sidebar search row, inline rename), NOT the boxed Input primitive; the 38px bordered control would break the pill. Circular close <code data-v-be77961e>IconButton sm</code> (concentric with the capsule end); a 0.5px hairline separator before it. Height comes from the grid: 32px control (<code data-v-be77961e>--space-8</code>) + 2× <code data-v-be77961e>--space-1</code> padding = 40px — at which <code data-v-be77961e>--radius-2xl</code> is exactly the half-height capsule.</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>Footer (results)</td><td data-v-be77961e>Expands via the 0fr→1fr grid fold (<code data-v-be77961e>--duration-slow</code>), hairline top separator, prev/next <code data-v-be77961e>IconButton sm</code> left, right-aligned muted count (<code data-v-be77961e>N/M results</code> · <code data-v-be77961e>--ui-font-size-sm</code>). Only exists once a query has settled — while typing or empty, the bar stays a bare pill.</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>States</td><td data-v-be77961e>collapsed (empty query) / searching (<code data-v-be77961e>Spinner sm</code> in the input row during the ~800ms debounce) / results / no-results (count reads "No results", nav disabled). Disabled is uniformly <code data-v-be77961e>opacity:.5</code>.</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>Focus</td><td data-v-be77961e>Composer-style: a neutral hairline overlay (<code data-v-be77961e>::after</code> + <code data-v-be77961e>--color-composer-focus-line</code>) fading in on <code data-v-be77961e>:focus-within</code>. No accent ring.</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>Match ink</td><td data-v-be77961e>CSS Custom Highlight API — the bar mutates no transcript DOM. All matches: <code data-v-be77961e>--color-search-match</code> (yellow); current: <code data-v-be77961e>--color-search-match-current</code> + a 2px <code data-v-be77961e>--color-warning</code> outline ring (a positioned overlay — highlight pseudos can't paint box outlines). Tokens live in <code data-v-be77961e>web-ui/style.css</code> with light/dark pairs.</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>Keyboard</td><td data-v-be77961e>Cmd/Ctrl+F opens + focuses (repeat = re-focus + select-all; hardcoded, reserved in the desktop keymap), Enter / Shift+Enter steps matches (wrapping), Esc closes from ANY control inside (container-level, so it never reaches the conversation's Esc-abort).</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>Matching semantics</td><td data-v-be77961e>Rendered transcript DOM only (unloaded older pages are out of scope), capped at 1000 matches (count reads <code data-v-be77961e>N/1000+</code>). Matches span inline nodes within one block, never cross block breaks; <code data-v-be77961e>inert</code> and <code data-v-be77961e>display:none</code> content is excluded. Stepping scrolls the match's own rect into view, not its parent element.</td></tr></tbody></table><h3 class="sub" data-v-be77961e>SectionLabel</h3><p data-v-be77961e>A small group title for sidebar lists, used to section the content below (such as <code data-v-be77961e>Workspaces</code> in the sidebar). Spec: 13px / 700 / uppercase / letter-spacing <code data-v-be77961e>.08em</code>, color <code data-v-be77961e>--color-fg-faint</code>; left-aligned to the row's starting padding (<code data-v-be77961e>--sb-pad-x</code>), keeping the same indent as the group rows below. For scripts without case (such as Chinese), <code data-v-be77961e>text-transform:uppercase</code> simply has no effect — no special handling needed.</p>',55)),a("div",de,[e[26]||(e[26]=a("div",{class:"stage-bar"},[a("span",{class:"st"},"Sidebar · group title")],-1)),a("div",oe,[e[25]||(e[25]=a("div",{class:"p-section-label",style:{padding:"12px 16px 4px"}},"Workspaces",-1)),a("div",ie,[(o(),i("svg",ne,[...e[21]||(e[21]=[a("path",{fill:"currentColor",d:"M4 5v14h16V7h-8.414l-2-2zm8.414 0H21a1 1 0 0 1 1 1v14a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h7.414z"},null,-1)])])),e[22]||(e[22]=s(" kimi-code-web ",-1))]),a("div",le,[(o(),i("svg",re,[...e[23]||(e[23]=[a("path",{fill:"currentColor",d:"M4 5v14h16V7h-8.414l-2-2zm8.414 0H21a1 1 0 0 1 1 1v14a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h7.414z"},null,-1)])])),e[24]||(e[24]=s(" playground ",-1))])])])]),a("section",ce,[e[39]||(e[39]=t('<div class="sec-head" data-v-be77961e><span class="sec-num" data-v-be77961e>04</span><h2 class="sec-title" data-v-be77961e>Chat Interface Overhaul</h2></div><p class="sec-desc" data-v-be77961e> The message stream is the core of Kimi Web. Tool calls render as <b data-v-be77961e>quiet activity lines</b> — one borderless line per call, bespoke per tool kind, auto-grouped, expanding on demand — while Question / Approval elevate to a <b data-v-be77961e>floating neutral surface</b> because they need a decision, and the Swarm composite keeps a card; the Composer collapses into a single rounded container. </p><h3 class="sub" data-v-be77961e>Unified message stream</h3><p data-v-be77961e>User-message bubbles follow the kimiwork production recipe (<code data-v-be77961e>MessageItem .user-bubble</code>): a neutral <code data-v-be77961e>--color-user-bubble-bg</code> fill (BubbleGray — <code data-v-be77961e>#f5f5f5</code> light / <code data-v-be77961e>#292929</code> dark), uniform <code data-v-be77961e>--radius-lg</code> corners, no border, no shadow.</p><p data-v-be77961e>Message timestamps use 12px UI text at weight 500, matching the compact metadata scale without switching to a monospace face.</p><p data-v-be77961e>The user-message metadata row sits one 8px spacing step below the bubble, so its actions and timestamp read as supporting information rather than part of the bubble edge.</p><p data-v-be77961e>Overlong user messages clamp at 10 measured lines, the tail dissolving through an alpha mask rather than a tint overlay (the translucent accent fill would double-composite); a floating pill toggle centred on the fade expands in place and collapses back, and the collapse pins the toggle itself so the reading position survives. Skill / plugin command args clamp through the same wrapper, beside the card head. Like the transcript's other disclosure controls (thinking row, turn fold, tool lines), the toggle is a bare native button carrying <code data-v-be77961e>aria-expanded</code> — chat-surface disclosure controls do not use the §03 Button primitive.</p><p data-v-be77961e>The floating jump-to-latest control uses 12px UI text at weight 525, led by the full down-arrow icon rather than a disclosure caret.</p><p data-v-be77961e>Thinking is an inline, borderless disclosure row in the message stream — never a side panel. The k15 bulb (the <code data-v-be77961e>thinking</code> registry icon) leads the row in every state; while streaming the "Thinking…" label breathes (opacity only, never a gradient shimmer) and whole elapsed seconds tick beside it, afterwards the label settles to "Thinking process" with the final span as <code data-v-be77961e>· Ns</code> (renderer-measured, live sessions only — history shows no seconds). Collapsed by default, it expands in place with the standard grid-rows animation and a 90° chevron rotation, and it folds itself back once the stream moves past it, even if the user expanded mid-stream. The header only animates its text colour on hover (standard duration and easing tokens), no card shell.</p><div class="stage-wrap" data-v-be77961e><div class="stage-bar" data-v-be77961e><span class="st" data-v-be77961e>Conversation · 760px reading column</span></div><div class="stage p col" style="align-items:center;background:#fff;" data-v-be77961e><div class="demo-chat" data-v-be77961e><div class="p-bubble-user" data-v-be77961e>Please change the login endpoint to JWT and add the corresponding unit tests.</div><span class="p-thinking" data-v-be77961e><span style="font-size:15px;line-height:1;" data-v-be77961e>🌔</span>Analyzing the auth module…</span><div class="p-tool-group open" data-v-be77961e><div class="p-tool-group-head" data-v-be77961e><svg class="tg-ic" viewBox="0 0 24 24" fill="currentColor" data-v-be77961e><path fill="currentColor" d="M9 2.003V2h10.998C20.55 2 21 2.455 21 2.992v18.016a.993.993 0 0 1-.993.992H3.993A1 1 0 0 1 3 20.993V8zM5.83 8H9V4.83zM11 4v5a1 1 0 0 1-1 1H5v10h14V4z" data-v-be77961e></path></svg><span class="tg-title" data-v-be77961e>Read 2 files</span><svg class="tg-car" viewBox="0 0 24 24" fill="currentColor" data-v-be77961e><path fill="currentColor" d="m13.172 12l-4.95-4.95l1.414-1.413L16 12l-6.364 6.364l-1.414-1.415z" data-v-be77961e></path></svg></div><div class="p-tool-row expanded" data-v-be77961e><svg class="tr-ic" viewBox="0 0 24 24" fill="currentColor" data-v-be77961e><path fill="currentColor" d="M9 2.003V2h10.998C20.55 2 21 2.455 21 2.992v18.016a.993.993 0 0 1-.993.992H3.993A1 1 0 0 1 3 20.993V8zM5.83 8H9V4.83zM11 4v5a1 1 0 0 1-1 1H5v10h14V4z" data-v-be77961e></path></svg><span class="tr-name" data-v-be77961e>Read</span><span class="tr-file" data-v-be77961e>session.ts</span><span class="tr-faint" data-v-be77961e>src/auth · :12-45</span><svg class="tr-car" viewBox="0 0 24 24" fill="currentColor" data-v-be77961e><path fill="currentColor" d="m13.172 12l-4.95-4.95l1.414-1.413L16 12l-6.364 6.364l-1.414-1.415z" data-v-be77961e></path></svg><span class="tr-chip" data-v-be77961e>34 lines</span><span class="tr-ok" data-v-be77961e>✓</span></div><div class="p-tool-detail" data-v-be77961e><div class="p-code" data-v-be77961e>12 export function verify(token: string) {<br data-v-be77961e>13 return jwt.verify(token, getSecret());<br data-v-be77961e>14 }</div></div><div class="p-tool-row" data-v-be77961e><svg class="tr-ic" viewBox="0 0 24 24" fill="currentColor" data-v-be77961e><path fill="currentColor" d="M9 2.003V2h10.998C20.55 2 21 2.455 21 2.992v18.016a.993.993 0 0 1-.993.992H3.993A1 1 0 0 1 3 20.993V8zM5.83 8H9V4.83zM11 4v5a1 1 0 0 1-1 1H5v10h14V4z" data-v-be77961e></path></svg><span class="tr-name" data-v-be77961e>Read</span><span class="tr-file" data-v-be77961e>middleware.ts</span><span class="tr-faint" data-v-be77961e>src/auth</span><svg class="tr-car" viewBox="0 0 24 24" fill="currentColor" data-v-be77961e><path fill="currentColor" d="m13.172 12l-4.95-4.95l1.414-1.413L16 12l-6.364 6.364l-1.414-1.415z" data-v-be77961e></path></svg><span class="tr-chip" data-v-be77961e>58 lines</span><span class="tr-ok" data-v-be77961e>✓</span></div></div><div class="p-tool-row" data-v-be77961e><svg class="tr-ic" viewBox="0 0 24 24" fill="currentColor" data-v-be77961e><path fill="currentColor" d="M15.728 9.686l-1.414-1.414L5 17.586V19h1.414l9.314-9.314zm1.414-1.414l1.414 1.414l1.414-1.414l-1.414-1.414l-1.414 1.414zM4 21h16v-2H4v2z" data-v-be77961e></path></svg><span class="tr-name" data-v-be77961e>Edit</span><span class="tr-file" data-v-be77961e>middleware.ts</span><span class="tr-faint" data-v-be77961e>src/auth</span><svg class="tr-car" viewBox="0 0 24 24" fill="currentColor" data-v-be77961e><path fill="currentColor" d="m13.172 12l-4.95-4.95l1.414-1.413L16 12l-6.364 6.364l-1.414-1.415z" data-v-be77961e></path></svg><span class="tr-add" data-v-be77961e>+12</span><span class="tr-del" data-v-be77961e>−4</span><span class="tr-bar" aria-hidden="true" data-v-be77961e><span style="flex:12;background:var(--p-success);" data-v-be77961e></span><span style="flex:4;background:var(--p-danger);" data-v-be77961e></span></span><span class="tr-ok" data-v-be77961e>✓</span></div><div class="p-tool-row" data-v-be77961e><svg class="tr-ic" viewBox="0 0 24 24" fill="currentColor" data-v-be77961e><path fill="currentColor" d="m18.031 16.617l4.283 4.282l-1.415 1.415l-4.282-4.283A8.96 8.96 0 0 1 11 20c-4.968 0-9-4.032-9-9s4.032-9 9-9s9 4.032 9 9a8.96 8.96 0 0 1-1.969 5.617m-2.006-.742A6.98 6.98 0 0 0 18 11c0-3.867-3.133-7-7-7s-7 3.133-7 7s3.133 7 7 7a6.98 6.98 0 0 0 4.875-1.975z" data-v-be77961e></path></svg><span class="tr-name" data-v-be77961e>Search</span><span class="tr-mono" data-v-be77961e>"jwt.verify"</span><span class="tr-faint" data-v-be77961e>src/auth</span><svg class="tr-car" viewBox="0 0 24 24" fill="currentColor" data-v-be77961e><path fill="currentColor" d="m13.172 12l-4.95-4.95l1.414-1.413L16 12l-6.364 6.364l-1.414-1.415z" data-v-be77961e></path></svg><span class="tr-chip" data-v-be77961e>4 results</span><span class="tr-ok" data-v-be77961e>✓</span></div><div class="p-msg" data-v-be77961e><p data-v-be77961e>I looked at the structure of <code data-v-be77961e>src/auth</code>; it is currently based on a session cookie. The scope of the change is below — once you confirm, I'll start.</p></div><div class="p-action" data-v-be77961e><div class="p-action-head" data-v-be77961e><span class="p-action-title" data-v-be77961e>A decision needs your confirmation</span></div><div class="p-action-body" data-v-be77961e>How long should the JWT expiry be? Default 7 days, refresh token 30 days.</div><div class="p-action-foot" data-v-be77961e><button class="p-btn ghost sm" data-v-be77961e>Customize</button><button class="p-btn primary sm" data-v-be77961e>Use default</button></div></div><div class="p-action" data-v-be77961e><div class="p-action-head" data-v-be77961e><span class="p-action-title" data-v-be77961e>Write permission required</span></div><div class="p-action-body" data-v-be77961e>About to modify <code data-v-be77961e>src/auth/middleware.ts</code>, 42 lines changed. Allow?</div><div class="p-action-foot" data-v-be77961e><button class="p-btn primary sm" data-v-be77961e>Allow this time</button><button class="p-btn ghost sm" data-v-be77961e>Always allow</button><button class="p-btn ghost sm" data-v-be77961e>Deny</button></div></div><div class="p-todo" data-v-be77961e><div class="p-todo-row done" data-v-be77961e><span class="p-todo-check" data-v-be77961e><svg viewBox="0 0 24 24" fill="currentColor" data-v-be77961e><path d="m10 15.17l9.192-9.191l1.414 1.414L10 17.999l-6.364-6.364l1.414-1.414z" data-v-be77961e></path></svg></span>Replace session with JWT signing</div><div class="p-todo-row active" data-v-be77961e><span class="p-todo-check" data-v-be77961e><svg viewBox="0 0 24 24" fill="currentColor" data-v-be77961e><circle cx="12" cy="12" r="3.5" data-v-be77961e></circle></svg></span>Refactor the auth middleware</div><div class="p-todo-row" data-v-be77961e><span class="p-todo-check" data-v-be77961e><svg viewBox="0 0 24 24" fill="currentColor" data-v-be77961e><circle cx="12" cy="12" r="3.5" data-v-be77961e></circle></svg></span>Add unit tests</div></div></div></div></div><p data-v-be77961e><b data-v-be77961e>Wide markdown tables (desktop):</b> regular chat prose stays within the 760px reading column (<code data-v-be77961e>--p-content-max</code>), and tables stay there too by default — an overflowing table scrolls horizontally inside its own wrapper, so the page and the chat area never scroll sideways. A clipped table shows a gradient fade at its truncated right edge, and hovering the table reveals a small widen button at its top-right corner; clicking it lets the table grow naturally with its content up to 1040px (<code data-v-be77961e>--p-table-max</code>), centred within the conversation pane, and clicking again restores the default width. At the default width a single column is capped at 36% of the pane; once widened the cap relaxes to 700px (<code data-v-be77961e>--p-table-cell-max</code>), so long cell content wraps inside the cell instead of stretching the table. The conversation outline (TOC) keeps its usual position just outside the reading column; when a widened table grows past it and scrolls under the rail, the TOC is hidden temporarily and returns as soon as the table leaves, without touching the user's TOC setting. On mobile a table never breaks out of the reading column.</p><h3 class="sub" data-v-be77961e>Tool calls: quiet activity lines, bespoke per tool</h3><p data-v-be77961e>High-frequency calls like <code data-v-be77961e>read</code> / <code data-v-be77961e>bash</code> / <code data-v-be77961e>grep</code> are "operational noise" — boxed, collapsible cards quickly drown out the conversation. Tool calls therefore render as <b data-v-be77961e>one quiet borderless line</b> in the message stream — never a card — and each tool kind composes that line for its own content, so the stream reads like an activity log rather than a pile of widgets. The three visual-weight tiers:</p><div class="stage-wrap" data-v-be77961e><div class="stage-bar" data-v-be77961e><span class="st" data-v-be77961e>Three visual-weight tiers</span></div><div class="stage p col" data-v-be77961e><span class="stage-label" data-v-be77961e>① Tool line · lightest (default) — bespoke content per tool, no card chrome</span><div class="p-tool-row" style="align-self:stretch;" data-v-be77961e><svg class="tr-ic" viewBox="0 0 24 24" fill="currentColor" data-v-be77961e><path fill="currentColor" d="M3 3h18a1 1 0 0 1 1 1v16a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1zm1 2v14h16V5H4zm3 3h5v2H7V8zm0 4h8v2H7v-2z" data-v-be77961e></path></svg><span class="tr-name" data-v-be77961e>Run</span><span class="tr-mono" data-v-be77961e>pnpm run build && pnpm lint</span><svg class="tr-car" viewBox="0 0 24 24" fill="currentColor" data-v-be77961e><path fill="currentColor" d="m13.172 12l-4.95-4.95l1.414-1.413L16 12l-6.364 6.364l-1.414-1.415z" data-v-be77961e></path></svg><span class="tr-chip" data-v-be77961e>0.8s</span><span class="tr-ok" data-v-be77961e>✓</span></div><span class="stage-label" data-v-be77961e>② Activity run · medium (consecutive quiet activity — thinking + tool lines — folds to one smart-summary row)</span><div class="p-tool-group" data-v-be77961e><div class="p-tool-group-head" data-v-be77961e><svg class="tg-ic" viewBox="0 0 24 24" fill="currentColor" data-v-be77961e><path fill="currentColor" d="M9 2.003V2h10.998C20.55 2 21 2.455 21 2.992v18.016a.993.993 0 0 1-.993.992H3.993A1 1 0 0 1 3 20.993V8zM5.83 8H9V4.83zM11 4v5a1 1 0 0 1-1 1H5v10h14V4z" data-v-be77961e></path></svg><span class="tg-title" data-v-be77961e>Read 3 files</span><svg class="tg-car" viewBox="0 0 24 24" fill="currentColor" data-v-be77961e><path fill="currentColor" d="m13.172 12l-4.95-4.95l1.414-1.413L16 12l-6.364 6.364l-1.414-1.415z" data-v-be77961e></path></svg></div></div><span class="stage-label" data-v-be77961e>③ Sub Agent identity card · one per delegation — task title + agent type; the whole card opens the side panel (no in-stream expansion, never grouped)</span><div class="p-agent-card" data-v-be77961e><span class="pa-ic" data-v-be77961e><svg viewBox="0 0 24 24" fill="currentColor" data-v-be77961e><path fill="currentColor" d="M13.5 2c0 .444-.193.843-.5 1.118V5h5a3 3 0 0 1 3 3v10a3 3 0 0 1-3 3H6a3 3 0 0 1-3-3V8a3 3 0 0 1 3-3h5V3.118A1.5 1.5 0 1 1 13.5 2M6 7a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V8a1 1 0 0 0-1-1zm-4 3H0v6h2zm20 0h2v6h-2zM9 14.5a1.5 1.5 0 1 0 0-3a1.5 1.5 0 0 0 0 3m6 0a1.5 1.5 0 1 0 0-3a1.5 1.5 0 0 0 0 3" data-v-be77961e></path></svg></span><span class="pa-main" data-v-be77961e><span class="pa-task" data-v-be77961e>分析双引擎架构</span><span class="pa-type" data-v-be77961e>Explore</span></span><span class="pa-ok" data-v-be77961e>✓</span><span class="pa-go" data-v-be77961e>→</span></div><span class="stage-label" data-v-be77961e>④ Decision card · heavy (only question / approval, needs user input)</span><div class="p-action" data-v-be77961e><div class="p-action-head" data-v-be77961e><span class="p-action-title" data-v-be77961e>Write permission required</span></div><div class="p-action-body" data-v-be77961e>About to modify <code data-v-be77961e>src/auth/middleware.ts</code>, 42 lines changed.</div></div></div></div><ul class="clean check" data-v-be77961e><li data-v-be77961e>A tool call renders as <b data-v-be77961e>one quiet borderless line</b> (~24px, the thinking row's rhythm): leading glyph, tool-specific content, trailing meta + status. There is no card chrome and no hover wash — the chevron hugging the line's text (thinking-row style, never pushed to the far edge) is the only disclosure affordance, a real <code data-v-be77961e><button></code> carrying <code data-v-be77961e>aria-expanded</code> (keyboard path); the head itself is a plain click target (mouse path), so trailing slots may hold genuine buttons of their own (e.g. Agent's "open detail").</li><li data-v-be77961e><b data-v-be77961e>One type scale for the whole stream</b>: thinking rows, fold summary rows and tool lines all set 13px UI text; in-line mono and trailing meta run one step down at 12px (a monospace x-height reads larger, so 12px sits level next to 13px). Hierarchy comes from colour, never from size jumps or bold — everything on the line is regular weight: the only dark object is the file-name button (<code data-v-be77961e>--color-text</code> — the one interactive place to go); the action label (Run / Read / Edit…), the mono command / pattern and secondary context all sit at <code data-v-be77961e>--color-text-muted</code>; auxiliary elements (glyphs, chevrons, trailing meta) stay <code data-v-be77961e>--color-text-faint</code>. The stream thus reads in three quiet tiers: prose in text, tool lines in muted, thinking / captions in faint. Line content is centre-aligned so mono-only rows (Bash) sit level with the icon and chevron. Truncating line content (the CSS-ellipsis spans) sets <code data-v-be77961e>--leading-tight</code> rather than the row's <code data-v-be77961e>line-height: 1</code> — a 1em line box is shorter than the font's ascent + descent, so <code data-v-be77961e>overflow: hidden</code> would clip descenders (j / p / g / y); mono runs take the font's own <code data-v-be77961e>normal</code> leading instead, since JetBrains Mono's ≈1.32em metrics exceed <code data-v-be77961e>--leading-tight</code>. The 16px chevron still drives the ~24px row height.</li><li data-v-be77961e><b data-v-be77961e>Every tool kind composes its own line, leading with the tool's localized action label</b> (Run / Read / Edit / Write / Search / Find / Fetch…): Bash pairs its label with the full command in mono (CSS-truncated) plus a duration chip; Read / Edit / Write follow the label with the file name as a real button (opens the file preview) followed by the directory, a <code data-v-be77961e>:line-range</code> or a <code data-v-be77961e>+N −M</code> stat with a mini segmented bar; Grep shows the pattern in mono plus a match count; Glob / Ls list paths; Todo carries the active task with a done/total progress bar; goal tools show a coloured status pill; ExitPlanMode expands into a read-only plan receipt with its persisted review outcome. Unrecognized tools fall back to glyph + localized label + argument summary.</li><li data-v-be77961e><b data-v-be77961e>The settled question is the one exception to the quiet line</b>: once AskUserQuestion settles with a recognized answer, it becomes a small <b data-v-be77961e>receipt card</b> — the question card's echo (raised surface, hairline edge, lg radius, <code data-v-be77961e>--shadow-xs</code>, flush with the stream's left edge, ≤560px). The card echoes only the picks, checked with the live QuestionCard's CSS glyph language one step down (14px); passed-over options are not echoed. Dismissed (or zero-answer) collapses to a slim italic one-line card; while running, and for unrecognized output (background launch / error), it stays the plain quiet disclosure line with the raw output.</li><li data-v-be77961e>Clicking a line <b data-v-be77961e>expands it in place</b>; the detail hangs below at the line's own left edge (no inset), so it reads as part of the stream rather than as a separate card. Details are one of: the mono output panel (content-well surface, hairline edge, 12-line scroll cap), the inline diff, or clickable match / file lists (<code data-v-be77961e>path:line</code> opens the preview at that line). Code-bearing details — the Read content, the Edit diff, the Write content — are <b data-v-be77961e>syntax-highlighted by file type</b> (github-light / github-dark, following the colour scheme), with the Read output's real line numbers as the gutter; highlighting mounts lazily on first expand and degrades to plain text for unknown languages or oversized content.</li><li data-v-be77961e>Rows sit <b data-v-be77961e>flush with the message stream's left edge</b> (same alignment as prose and the thinking row): no inset, no hover wash, and the glyph rides the thinking row's 4px icon-to-text rhythm with no padded slot. Expanded rows inside a group stack directly on the shared rhythm — no dividers.</li><li data-v-be77961e>Consecutive activity — thinking segments and tool calls of ANY kind, quiet lines and richer cards alike — <b data-v-be77961e>folds into ONE activity-run row</b>: a smart summary sentence that aggregates the run per tool kind in first-appearance order (<code data-v-be77961e>Read 2 files · Ran 5 commands (1 failed) · 26s</code>), the failure clause hanging on its kind in danger red, the total span faint at the tail — one line, ellipsis-truncated, the full sentence in the title tooltip. Thinking items fold into the run but are not narrated in the sentence. The row shares the thinking row's language (borderless faint text row, text-colour hover only, one whole-row button with a rotating chevron) but rides a roomier 8px vertical padding — 30px against the quiet lines' 22px, so the turn-level summary keeps its presence between prose paragraphs; while the turn streams through the run the row stays expanded and the summary turns live (current action + cumulative per-kind stats + ticking whole seconds), and once every item settles it folds itself back — even if the user expanded it mid-run (the thinking block's vocabulary); a settled → running transition (the stream appending to the same run) reopens it. The glyph carries the state: the current step's own icon breathing while running, green ✓ / red ✕ once settled. A run needs <b data-v-be77961e>≥ 2 steps</b> — a lone step renders standalone as the block it always was. <b data-v-be77961e>Text never folds</b> (it breaks the run), and neither do successful media tools (no card — inline media is the turn's output); everything else folds, cards included: Todo / Goal progress narration, the sub-agent identity card, Question / Swarm cards and unrecognized kinds (skills, MCP tools) all join the run — the stay-expanded-while-live rule keeps a card visible exactly while it is active. The expanded run is the items flat in order (thinking rows + tool rows), each with its own in-row details intact — the lines keep their own 4px row rhythm but breathe 8px apart, with a small inset below the head.</li><li data-v-be77961e><b data-v-be77961e>Above the activity run sits the turn fold</b> (<code data-v-be77961e>TurnFold.vue</code>): when an assistant turn settles, every block before the LAST text block — thinking segments, activity runs, interim text paragraphs, Todo / Goal / sub-agent cards — folds into a single bare row reading <code data-v-be77961e>Worked 4m57s</code> (whole seconds, no glyph, no summary sentence), expanding into the folded blocks in order, each with its own rendering intact. The span is the turn's ELAPSED time (<code data-v-be77961e>turnWorkMs</code>): it ticks from the stamped start while the turn is open — approval/question waits included by design, so no park bookkeeping exists — then reads the daemon's own <code data-v-be77961e>durationMs</code> once settled (the server message stamps for history turns); the wall clock only feeds the live tick, so throttled tabs, session switches and remounts cannot corrupt the settled value. Without any stamp the row falls back to the generic <code data-v-be77961e>Work details</code>. Streaming turns show no row and a forced-open body — the live transcript is untouched, the fold lands only when the stream moves past the turn (or the turn parks). The split never hides the turn's output: the final text block and any trailing blocks (inline media, standalone cards) stay visible, and a text-only turn folds nothing. Fold state is a plain component ref — nothing persists, switching sessions resets to folded. Inside the right-side sub-agent transcript, disclosure bodies open instantly while their chevrons retain the standard rotation: animating the height of a full historical stream would relayout the entire panel on every animation frame.</li><li data-v-be77961e><b data-v-be77961e>A sub-agent delegation is an identity card</b> — never a quiet line: the card carries the TASK as its title and the agent type as a quiet meta line, while the orchestrator's full prompt stays out of the stream on purpose. The whole card is one action (the quiet shell vocabulary: raised surface, hairline edge, large radius, no shadow): click to open the subagent's live progress in the side panel — there is no in-stream expansion.</li><li data-v-be77961e>Status keeps the shared vocabulary: running (pulsing accent dot) / done (green ✓) / failed (red ✗), at the line's right edge. <b data-v-be77961e>Only two types keep a full card</b>: <code data-v-be77961e>Question</code> and <code data-v-be77961e>Approval</code> — they genuinely need the user's attention. The Swarm composite keeps one quiet card (raised surface, 0.5px hairline, large radius) for its phase overview + member accordion.</li><li data-v-be77961e><b data-v-be77961e>A task notification is a status card, not a quiet line</b> (<code data-v-be77961e>NotificationCard.vue</code>): the hidden <code data-v-be77961e><notification></code> injections (background-task / sub-agent settlement) render where they landed in the turn — a 28px status chip + title/sub head tinted with the toast status token pairs (completed → success, failed / timed_out / lost → danger, killed → warning, else neutral surface), expanding in place to the fields, the body, an output-file row (copy path) and the raw payload. ≥2 CONSECUTIVE notifications merge into one neutral group card (count + per-item status dots + compact rows, each expanding on its own). Notifications break the activity run but are never turn boundaries, and they <b data-v-be77961e>never fold</b> — a notification is an event worth noticing, not process noise, so it punches out of the turn fold and renders right after the fold row, in order.</li><li data-v-be77961e><b data-v-be77961e>A goal-continuation turn carries a provenance row</b>: the hidden <code data-v-be77961e>goal_continuation</code> trigger (goal mode's self-driven next turn — a turn boundary, unlike task notifications) never renders its machine prompt; instead the assistant turn it opens shows one faint 12px line flush with the stream's left edge — the <code data-v-be77961e>target</code> glyph shared with the Goal tool (this turn belongs to the goal) + a localized label — ABOVE the turn's content and OUTSIDE the turn fold, so the row survives as the turn's provenance after settling. The marker lands with the trigger (before the first assistant block), and while the newest exchange is a goal-continuation turn the undo affordances (edit-and-resend, Esc undo) are suppressed — rewinding would drop the hidden trigger while refilling the older user text.</li><li data-v-be77961e><b data-v-be77961e>A settled turn's file changes are one summary card</b> (<code data-v-be77961e>TurnFilesSummary.vue</code>): between the turn's final text and its footer, a §03 <code data-v-be77961e>Card</code> (hairline border, no shadow — NOT the quiet tool line, the artifacts are worth a discrete object) lists every file the turn's Edit / Write calls touched. The head reads "N files changed" with the aggregate <code data-v-be77961e>+A −D</code> and the mini diffbar; the aggregate hides whenever any row's stats are incomplete (a Write or an underivable edit makes the total a lower bound, never presented as exact). Each row is one clickable workspace-relative path (short and self-locating; a file outside the cwd stays absolute) with its per-file <code data-v-be77961e>+A −D</code> at the right edge. The row's action keys on the tool kind, and the stats tell it apart: a <b data-v-be77961e>Write</b> has no per-file count (its diff is underivable) and opens the whole file in the preview; an <b data-v-be77961e>Edit / MultiEdit</b> carries its <code data-v-be77961e>+A −D</code> and opens that file's <b data-v-be77961e>turn diff</b> in the right-side detail layer (<code data-v-be77961e>TurnDiffPanel.vue</code> — the turn's own X→Y change, not the git diff), whose header keeps an open-file action. The first three files show inline; the rest collapse behind a "N more files" ghost-button row in the card's foot. Where nothing handles the row action (the BTW side chat), the card renders its file rows as plain text instead of links.</li></ul>',15)),a("div",ve,[e[31]||(e[31]=a("div",{class:"stage-bar"},[a("span",{class:"st"},"Turn files summary · a real TurnFilesSummary (fixed sample)")],-1)),a("div",be,[a("div",pe,[c(A,{changes:C,cwd:ye,onOpenDiff:u,onOpenFile:u})])])]),e[40]||(e[40]=t('<div class="stage-wrap" data-v-be77961e><div class="stage-bar" data-v-be77961e><span class="st" data-v-be77961e>Tool Call · quiet lines (expand on demand)</span></div><div class="stage p" data-v-be77961e><div class="p-tool-group open" data-v-be77961e><div class="p-tool-group-head" data-v-be77961e><svg class="tg-ic" viewBox="0 0 24 24" fill="currentColor" data-v-be77961e><path fill="currentColor" d="M9 2.003V2h10.998C20.55 2 21 2.455 21 2.992v18.016a.993.993 0 0 1-.993.992H3.993A1 1 0 0 1 3 20.993V8zM5.83 8H9V4.83zM11 4v5a1 1 0 0 1-1 1H5v10h14V4z" data-v-be77961e></path></svg><span class="tg-title" data-v-be77961e>Read 2 files</span></div><div class="p-tool-row expanded" data-v-be77961e><svg class="tr-ic" viewBox="0 0 24 24" fill="currentColor" data-v-be77961e><path fill="currentColor" d="M9 2.003V2h10.998C20.55 2 21 2.455 21 2.992v18.016a.993.993 0 0 1-.993.992H3.993A1 1 0 0 1 3 20.993V8zM5.83 8H9V4.83zM11 4v5a1 1 0 0 1-1 1H5v10h14V4z" data-v-be77961e></path></svg><span class="tr-name" data-v-be77961e>Read</span><span class="tr-file" data-v-be77961e>session.ts</span><span class="tr-faint" data-v-be77961e>src/auth · :12-45</span><svg class="tr-car" viewBox="0 0 24 24" fill="currentColor" data-v-be77961e><path fill="currentColor" d="m13.172 12l-4.95-4.95l1.414-1.413L16 12l-6.364 6.364l-1.414-1.415z" data-v-be77961e></path></svg><span class="tr-chip" data-v-be77961e>34 lines</span><span class="tr-ok" data-v-be77961e>✓</span></div><div class="p-tool-detail" data-v-be77961e><div class="p-code" style="font-size:11px;padding:7px 9px;" data-v-be77961e>12 export function verify(…</div></div><div class="p-tool-row" data-v-be77961e><svg class="tr-ic" viewBox="0 0 24 24" fill="currentColor" data-v-be77961e><path fill="currentColor" d="M9 2.003V2h10.998C20.55 2 21 2.455 21 2.992v18.016a.993.993 0 0 1-.993.992H3.993A1 1 0 0 1 3 20.993V8zM5.83 8H9V4.83zM11 4v5a1 1 0 0 1-1 1H5v10h14V4z" data-v-be77961e></path></svg><span class="tr-name" data-v-be77961e>Read</span><span class="tr-file" data-v-be77961e>middleware.ts</span><span class="tr-faint" data-v-be77961e>src/auth</span><svg class="tr-car" viewBox="0 0 24 24" fill="currentColor" data-v-be77961e><path fill="currentColor" d="m13.172 12l-4.95-4.95l1.414-1.413L16 12l-6.364 6.364l-1.414-1.415z" data-v-be77961e></path></svg><span class="tr-chip" data-v-be77961e>58 lines</span><span class="tr-ok" data-v-be77961e>✓</span></div></div><div class="p-tool-row" data-v-be77961e><svg class="tr-ic" viewBox="0 0 24 24" fill="currentColor" data-v-be77961e><path fill="currentColor" d="M15.728 9.686l-1.414-1.414L5 17.586V19h1.414l9.314-9.314zm1.414-1.414l1.414 1.414l1.414-1.414l-1.414-1.414l-1.414 1.414zM4 21h16v-2H4v2z" data-v-be77961e></path></svg><span class="tr-name" data-v-be77961e>Edit</span><span class="tr-file" data-v-be77961e>middleware.ts</span><svg class="tr-car" viewBox="0 0 24 24" fill="currentColor" data-v-be77961e><path fill="currentColor" d="m13.172 12l-4.95-4.95l1.414-1.413L16 12l-6.364 6.364l-1.414-1.415z" data-v-be77961e></path></svg><span class="tr-add" data-v-be77961e>+12</span><span class="tr-del" data-v-be77961e>−4</span><span class="tr-bar" aria-hidden="true" data-v-be77961e><span style="flex:12;background:var(--p-success);" data-v-be77961e></span><span style="flex:4;background:var(--p-danger);" data-v-be77961e></span></span><span class="tr-ok" data-v-be77961e>✓</span></div></div></div><h3 class="sub" data-v-be77961e>Decision cards · Question / Approval</h3><p data-v-be77961e>The two attention cards replace the composer in the dock and share one contract: a floating neutral shell (<code data-v-be77961e>--color-surface-raised</code> + hairline + <code data-v-be77961e>--radius-lg</code> + <code data-v-be77961e>--shadow-menu</code>), a plain dark 16px title head, and a hairline footer whose actions read in number-key order with exactly one accent primary. There is no semantic colour band — the floating card itself is the "needs a decision" signal.</p><div class="stage-wrap" data-v-be77961e><div class="stage-bar" data-v-be77961e><span class="st" data-v-be77961e>Plan review · pinned option rows, second-line descriptions</span></div><div class="stage p col" data-v-be77961e><div class="p-action" style="max-width:520px;" data-v-be77961e><div class="p-action-head" data-v-be77961e><span class="p-action-title" data-v-be77961e>按这份 plan 开始实现?</span></div><div class="p-action-body" data-v-be77961e>The plan markdown scrolls in a capped area; the approaches are pinned below it — label on the first line, full description always on the second. The number chip doubles as the keyboard hint.</div><div class="p-opts" data-v-be77961e><div class="p-opt" data-v-be77961e><span class="n" data-v-be77961e>1</span><span class="p-opt-text" data-v-be77961e><span class="l" data-v-be77961e>方案 A:静态徽章</span><span class="d" data-v-be77961e>零依赖、渲染稳定,升级时需手动同步版本号。</span></span></div><div class="p-opt" data-v-be77961e><span class="n" data-v-be77961e>2</span><span class="p-opt-text" data-v-be77961e><span class="l" data-v-be77961e>方案 B:动态徽章</span><span class="d" data-v-be77961e>版本自动同步免维护,但要求仓库公开可访问。</span></span></div></div><div class="p-action-foot" data-v-be77961e><button class="p-btn ghost sm" data-v-be77961e>修改</button><button class="p-btn ghost sm" data-v-be77961e>拒绝并退出</button></div></div></div></div><ul class="clean check" data-v-be77961e><li data-v-be77961e><b data-v-be77961e>Footer contract</b>: actions are left-aligned in number-key order (1·2·3·4), each carrying a number chip — sized by <code data-v-be77961e>--p-chip-num</code> over <code data-v-be77961e>--color-inline-code-bg</code>, the same chip vocabulary as option rows and the multi-step chip; exactly one <code data-v-be77961e>primary</code> action, the rest are <code data-v-be77961e>ghost</code>. Feedback mode swaps the whole footer for submit / cancel.</li><li data-v-be77961e><b data-v-be77961e>Body by kind</b>: Write approvals preview the incoming content with <code data-v-be77961e>HighlightedCode</code> (syntax-highlighted, 24-row cap with scroll); Edit approvals render the before/after hunk as a highlighted line diff. Plan / diff / file kinds get a head expand toggle that lifts the cap so the block fills the card; the card itself never exceeds the pane (only the scroll area shrinks). Once the plan scrolls, a soft shadow fades in at the scroll area's top edge — the sidebar's scroll-linked seam language, so clipped content reads as passing under the card chrome.</li><li data-v-be77961e><b data-v-be77961e>Danger hint</b>: destructive shell commands (rm -rf, sudo, force-push…) show a <code data-v-be77961e>danger-soft</code> filled hint row under the command — detection is a display-layer heuristic on the client.</li><li data-v-be77961e><b data-v-be77961e>Minimized</b>: the card collapses to a thin bar with a mono peek of the subject; the whole bar is the expand click target.</li><li data-v-be77961e><b data-v-be77961e>Question card</b>: the title is the question itself (2-line clamp), with a step chip for multi-question flows and a × dismiss button. Options use CSS radio/checkbox glyphs (accent when selected); the number chip and glyph top-align with the option text, optically centred on the label's first line. The footer follows the same left-aligned action contract (primary first, ghosts after), with the keyboard hint pinned to the right edge; keyboard: ↑↓ moves (Space toggles in multi), digits pick, Enter advances/submits, Esc dismisses.</li></ul><h3 class="sub" data-v-be77961e>Composer</h3><p data-v-be77961e>Unified into a single raised container: <code data-v-be77961e>--radius-composer</code> (32px) with <code data-v-be77961e>--corner-shape-composer: superellipse(1.5)</code> and a stable 0.5px edge. Focus crossfades a low-chroma line-and-accent edge over <code data-v-be77961e>--duration-slow</code> with <code data-v-be77961e>--ease-in-out</code>, while the neutral shadow stays unchanged — there is no added halo and no layout shift. The textarea uses <code data-v-be77961e>text-autospace: normal</code> for mixed CJK and Latin input. Toolbar controls use a quiet 32px full-round geometry with 8px edge inset; the send button remains a standard 32px circle, with its glyph at 28px (<code data-v-be77961e>--composer-send-icon-size</code>, the production kimi.com size; it sits outside the <code data-v-be77961e>--p-ic-*</code> scale on purpose).</p><p data-v-be77961e><b data-v-be77961e>Fill and edge tokens</b>: the card's fill and rest border are their own tokens — <code data-v-be77961e>--color-composer-bg</code> and <code data-v-be77961e>--color-composer-line</code> — running the kimiwork / kimi.com production input recipe (<code data-v-be77961e>.chat-input__shell</code>): fill = <code data-v-be77961e>groupedBackground.secondary</code> (#ffffff light / #1f1f1f dark), rest border = <code data-v-be77961e>separator.s1</code> (13% black / 12% white), focus line = <code data-v-be77961e>fills.f4</code> (25% in both schemes), and <code data-v-be77961e>--shadow-input</code> = <code data-v-be77961e>effect.shadow.inputDefault</code> (<code data-v-be77961e>0 5px 16px -4px rgba(0,0,0,0.07)</code>, kept identical in dark — the hairline carries the edge there). Only colours sit in the tokens; the 32px superellipse shape and the focus-only edge overlay are unchanged.</p><p data-v-be77961e><b data-v-be77961e>Send button tokens</b>: the send circle runs on <code data-v-be77961e>--color-send-bg</code> / <code data-v-be77961e>--color-send-bg-hover</code> / <code data-v-be77961e>--color-send-icon</code> (+ <code data-v-be77961e>*-disabled</code>, <code data-v-be77961e>--opacity-send-disabled</code>, <code data-v-be77961e>--shadow-send[-hover]</code>), following the production recipe (<code data-v-be77961e>.chat-input__send</code>): a neutral <code data-v-be77961e>labels.primary</code> fill (90% black light / 84% white dark, hover #252525 / 84.8%) with the production lift shadow (<code data-v-be77961e>0 7px 16px -13px 38% + 0 1px 2px 7%</code>, one step larger on hover), a <code data-v-be77961e>groupedBackground.secondary</code> glyph, and a disabled state of the same vocabulary — <code data-v-be77961e>fills.f2</code> fill with a <code data-v-be77961e>labels.quaternary</code> glyph at full opacity. The button is disabled exactly when submit would no-op — an empty draft with no ready attachment (image-only sends stay enabled), an upload in flight, or the starting spinner — so disabled is a first-class persistent state, never a fade.</p><p data-v-be77961e><b data-v-be77961e>Layering, anchors, and motion</b>: the dock normally stays at <code data-v-be77961e>--z-sticky</code> so the Latest Messages pill can remain visible above its veil. While any Composer popup is open, the dock temporarily joins <code data-v-be77961e>--z-dropdown</code>, ensuring permission, work-mode, and model menus always paint above that pill. The permission menu's left edge and the model menu's right edge each follow their own trigger pill. All three menus use <code data-v-be77961e>--shadow-menu</code> and the same trigger-corner pop motion as Session Row menus: 0.97 scale with a 2px shift toward the trigger, <code data-v-be77961e>--duration-base</code> on entry, and <code data-v-be77961e>--duration-fast</code> on exit.</p><p data-v-be77961e><b data-v-be77961e>Attachment strip</b>: attachments hang inside the composer card above the textarea as two grouped rows — images/videos as shared <code data-v-be77961e>MediaThumb</code> rounded thumbnails, files as the shared <code data-v-be77961e>AttachmentChip</code> pill — the same pair the sent bubble renders, so a draft looks exactly like the sent message. File-store videos render a static play tile instead of fetching a first frame. The strip caps at two thumbnail rows and scrolls beyond that instead of pushing the input down; while overflowing, a quiet count badge pins to the bottom-left and new attachments auto-scroll into view (to the end of whichever group grew). With two or more attachments, a one-click clear-all pins to the strip's top-right corner as a quiet 22px badge (trash glyph, danger on hover). The composer's pending preview and the bubble's media clicks open the same <code data-v-be77961e>MediaLightbox</code> preview, which owns Escape via the shared dialog stack: images go through PhotoSwipe (<code data-v-be77961e>lib/mediaPreview.ts</code>) and zoom out of the clicked thumbnail (scrim = <code data-v-be77961e>--color-scrim-strong</code>, caption = <code data-v-be77961e>--color-text-on-scrim</code>), videos keep the custom modal.</p>',11)),a("div",he,[e[38]||(e[38]=a("div",{class:"stage-bar"},[a("span",{class:"st"},"Composer")],-1)),a("div",ue,[a("div",ge,[e[36]||(e[36]=a("div",{class:"p-composer-ta ph"},"Message Kimi, / to run a command, @ to reference a file…",-1)),a("div",me,[a("div",fe,[e[33]||(e[33]=a("button",{class:"p-icon-btn"},[a("svg",{class:"p-ic",viewBox:"0 0 24 24",fill:"currentColor"},[a("path",{fill:"currentColor",d:"M11 11V5h2v6h6v2h-6v6h-2v-6H5v-2z"})])],-1)),a("span",we,[c(b(h),{name:"shield-question",size:"sm"}),e[32]||(e[32]=s("yolo",-1))]),e[34]||(e[34]=a("span",{class:"p-pill"},[a("svg",{class:"p-ic",viewBox:"0 0 24 24",fill:"currentColor"},[a("path",{fill:"currentColor",d:"M8 4h13v2H8zM4.5 6.5a1.5 1.5 0 1 1 0-3a1.5 1.5 0 0 1 0 3m0 7a1.5 1.5 0 1 1 0-3a1.5 1.5 0 0 1 0 3m0 6.9a1.5 1.5 0 1 1 0-3a1.5 1.5 0 0 1 0 3M8 11h13v2H8zm0 7h13v2H8z"})]),s("plan")],-1))]),e[35]||(e[35]=t('<div class="p-composer-right" data-v-be77961e><span class="p-pill" data-v-be77961e><span class="pp-strong" data-v-be77961e>kimi-k2</span><span class="pp-sub" data-v-be77961e>· thinking</span><svg class="p-ic" viewBox="0 0 24 24" fill="currentColor" data-v-be77961e><path fill="currentColor" d="m12 13.171l4.95-4.95l1.414 1.415L12 16L5.636 9.636L7.05 8.222z" data-v-be77961e></path></svg></span><button class="p-send" data-v-be77961e><svg class="p-ic" viewBox="0 0 24 24" fill="currentColor" data-v-be77961e><path fill="currentColor" d="M13 7.828V20h-2V7.828l-5.364 5.364l-1.414-1.414L12 4l7.778 7.778l-1.414 1.414z" data-v-be77961e></path></svg></button></div>',1))])]),e[37]||(e[37]=t('<div class="p-composer-strip" data-v-be77961e><svg class="p-ic" viewBox="0 0 24 24" fill="currentColor" data-v-be77961e><path fill="currentColor" d="M4 5a1 1 0 0 1 1-1h5l2 2h7a1 1 0 0 1 1 1v10a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V5z" data-v-be77961e></path></svg>kimi-code-web<svg class="p-ic" viewBox="0 0 24 24" fill="currentColor" data-v-be77961e><path fill="currentColor" d="m12 13.171l4.95-4.95l1.414 1.415L12 16L5.636 9.636L7.05 8.222z" data-v-be77961e></path></svg></div>',1))])]),e[41]||(e[41]=t('<div class="callout info" data-v-be77961e><span class="ico" data-v-be77961e>i</span><div data-v-be77961e><b data-v-be77961e>Site-wide consistency</b>: the composer uses one 32px superellipse shell and one 32px desktop control height. Attachment, permission, modes, compact, and model controls are all full-round and transparent at rest; hover reveals a neutral wash, open/active may use accent-soft, and Send remains the sole persistent filled control — an inverted <code data-v-be77961e>--color-text</code> fill with a <code data-v-be77961e>--color-bg</code> glyph (never the accent), disabled while the input is empty or an upload is in flight. The transparent dock floats over the transcript, while the scrolling content receives bottom padding equal to the live dock height so its final item can still clear the composer. Composer chrome is not selectable; only the message input permits text selection. Each permission mode has its own registry icon — manual <code data-v-be77961e>hand</code>, yolo <code data-v-be77961e>shield-question</code>, auto <code data-v-be77961e>full-access</code> — paired with the label in the pill (collapsing to the accessible icon below a 620px composer container) and leading its dropdown row in the mode's colour, with the current row's check trailing the row's end. The right toolbar is the flexible region: the model pill shrink-wraps its content, then shrinks and truncates internally only when the toolbar runs out of room. The dock's workbar above the composer carries one pill vocabulary — 32px high with <code data-v-be77961e>--space-4</code> inline padding and stadium-shaped (<code data-v-be77961e>--radius-full</code>) corners, a <code data-v-be77961e>--color-surface</code> fill (one rung above the page in both schemes — sunken is degenerate in dark — the same material as the popover it opens), and the system hairline edge (0.5px at <code data-v-be77961e>--color-line-strong</code>, one rung up for presence; no shadow), icon + label + a count or status — for background bash tasks, background sub-agents, todos, and the goal alike; a pill toggles the shared work panel (itself at <code data-v-be77961e>--radius-xl</code> with the same 0.5px <code data-v-be77961e>--color-line-strong</code> edge outside — inner separators stay <code data-v-be77961e>--color-line</code> — and the menu panel's <code data-v-be77961e>--shadow-menu</code>), and the goal's detail (full objective, completion criterion) fills the panel body while its pause / resume / cancel controls ride the panel head (the decision cards' action vocabulary — exactly one accent primary, resume while paused; secondary pause while active; danger-soft cancel) and the meta counts (turns / tokens / time / budget) sit in a hairline footer — never a separate full-width strip. </div></div><p data-v-be77961e><b data-v-be77961e>Workspace attachment card</b>: on the empty session, the workspace picker is a <b data-v-be77961e>separate attachment card</b> tucked under the composer — and the composer card itself stays complete (its own 0.5px border, <code data-v-be77961e>--radius-composer</code> corners with <code data-v-be77961e>--corner-shape-composer</code>, and shadow are never altered). The attachment lives inside the composer's padding box as the card's sibling, so its width always matches; its top <code data-v-be77961e>--space-4</code> slides behind the card (the card is raised to <code data-v-be77961e>--z-sticky</code>), its square top edge stays hidden, and only the rounded bottom (<code data-v-be77961e>0 0 --radius-xl --radius-xl</code>) shows. Background <code data-v-be77961e>--color-hover</code> at 60% via <code data-v-be77961e>color-mix</code> (≈0.03 black in light, self-adapting in dark), no border, no shadow. Inside sits one quiet capsule trigger: transparent, <code data-v-be77961e>--radius-full</code>, 16px leading icon and 12px label at weight 475 in <code data-v-be77961e>--color-text-muted</code>; hover deepens to <code data-v-be77961e>--color-selected</code> and the label turns <code data-v-be77961e>--color-text</code>. The dropdown follows the §03 menu spec and is viewport-aware (flips above when more room, clamps max-height to the scrollport); at <code data-v-be77961e>--z-dropdown</code> it outranks both the card and the fixed click-outside backdrop (<code data-v-be77961e>--z-sticky</code>), which renders outside the composer because the card's <code data-v-be77961e>container-type</code> captures <code data-v-be77961e>position: fixed</code> descendants.</p><h3 class="sub" data-v-be77961e>Responsive</h3><p data-v-be77961e>See §02 <code data-v-be77961e>--p-bp-sm</code> for the breakpoint. This section only gives mobile-adaptation pointers for the chat interface; a full mobile mockup is out of scope for this spec.</p><div class="callout info" data-v-be77961e><span class="ico" data-v-be77961e>i</span><div data-v-be77961e> At ≤640px: dialogs anchor to the bottom as Sheets (xl top radius, top drag handle), the sidebar collapses into an expandable drawer, the Composer toolbar is allowed to wrap, and the chat reading column drops its max-width to fill the screen. </div></div>',5))]),e[43]||(e[43]=t('<section id="themes" data-v-be77961e><div class="sec-head" data-v-be77961e><span class="sec-num" data-v-be77961e>05</span><h2 class="sec-title" data-v-be77961e>Theming</h2></div><p class="sec-desc" data-v-be77961e> Kimi Web uses <b data-v-be77961e>one unified theme</b>: the same components, fonts, radii, shadows, and surfaces — theming only swaps color values. Every semantic color token ships a light value in <code data-v-be77961e>:root</code> and a dark override in the <code data-v-be77961e>data-color-scheme</code> blocks; the semantic status colors (success / warning / danger) are independent palettes, one set each for light / dark. </p><h3 class="sub" data-v-be77961e>Accent</h3><p data-v-be77961e>The app has <b data-v-be77961e>one accent</b>: the brand blue (<code data-v-be77961e>--color-accent</code>, <code data-v-be77961e>#1783ff</code> light / <code data-v-be77961e>#58a6ff</code> dark). Use it sparingly — the accent is reserved for the primary action, focus rings, links, and active marks (current tab, toggles); large fills always come from the neutral surface tokens. Selection that means "where I am" (sidebar rows, list pickers) is deliberately NOT accent-tinted — it uses <code data-v-be77961e>--color-selected</code> so it reads as location, not as an action.</p><h3 class="sub" data-v-be77961e>Light / dark mode</h3><p data-v-be77961e>Each semantic token ships a light value in <code data-v-be77961e>:root</code> and a dark override in the two <code data-v-be77961e>data-color-scheme</code> blocks (explicit choice, or following the OS preference via <code data-v-be77961e>prefers-color-scheme</code>). Switching light / dark simply swaps between these two sets of derived tokens, with zero structural change.</p><div class="callout good" data-v-be77961e><span class="ico" data-v-be77961e>✓</span><div data-v-be77961e><b data-v-be77961e>Benefits of one theme</b>: components, fonts, radii, and surfaces are consistent site-wide; a single accent keeps the brand identity unambiguous; light / dark mode works out of the box; semantic status colors are independently tunable. </div></div></section><section id="rules" data-v-be77961e><div class="sec-head" data-v-be77961e><span class="sec-num" data-v-be77961e>06</span><h2 class="sec-title" data-v-be77961e>Style Rules</h2></div><p class="sec-desc" data-v-be77961e> Anti-pattern rules that all UI code must follow. These rules are also the basis of the check-style detection script, one-to-one with a warning. </p><table class="dt" data-v-be77961e><thead data-v-be77961e><tr data-v-be77961e><th data-v-be77961e>Rule ID</th><th data-v-be77961e>What it detects</th><th data-v-be77961e>Action</th></tr></thead><tbody data-v-be77961e><tr data-v-be77961e><td class="tk" data-v-be77961e>no-gradient-text</td><td data-v-be77961e>gradient text / gradient background</td><td data-v-be77961e><span class="pill red" data-v-be77961e>Forbidden</span></td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>no-glassmorphism</td><td data-v-be77961e><code data-v-be77961e>backdrop-filter: blur</code> (<b data-v-be77961e>TopBar sticky nav bar</b> and <b data-v-be77961e>menu surfaces via <code data-v-be77961e>--p-menu-backdrop</code></b> are the exceptions)</td><td data-v-be77961e><span class="pill amber" data-v-be77961e>TopBar + menus exempt</span></td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>no-color-glow</td><td data-v-be77961e>colored / large-radius box-shadow glow</td><td data-v-be77961e><span class="pill red" data-v-be77961e>Forbidden</span></td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>no-emoji-icon</td><td data-v-be77961e>using emoji as a functional icon (no exceptions). Emoji inside <b data-v-be77961e>user content</b> — session titles, messages — is not chrome and is out of scope (see §07 Session row's emoji icon)</td><td data-v-be77961e><span class="pill red" data-v-be77961e>Forbidden</span></td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>no-hardcoded-hex</td><td data-v-be77961e>unregistered hex color inside a component <code data-v-be77961e><style></code></td><td data-v-be77961e><span class="pill amber" data-v-be77961e>Warning</span></td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>no-hardcoded-font</td><td data-v-be77961e>hard-coded <code data-v-be77961e>font-family</code> in a component (e.g. <code data-v-be77961e>'Inter'</code>) instead of <code data-v-be77961e>var(--font-ui)</code></td><td data-v-be77961e><span class="pill amber" data-v-be77961e>Warning</span></td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>radius-from-scale</td><td data-v-be77961e>radius value not in <code data-v-be77961e>{4,6,8,12,16,20,999}</code></td><td data-v-be77961e><span class="pill amber" data-v-be77961e>Warning</span></td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>z-from-scale</td><td data-v-be77961e>z-index using an unregistered large number</td><td data-v-be77961e><span class="pill amber" data-v-be77961e>Warning</span></td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>weight-from-scale</td><td data-v-be77961e>font-weight not in <code data-v-be77961e>{400,500}</code></td><td data-v-be77961e><span class="pill amber" data-v-be77961e>Warning</span></td></tr></tbody></table><h3 class="sub" data-v-be77961e>State matrix</h3><p data-v-be77961e>Every interactive primitive should define the following states where applicable; missing ones are flagged by the style rules. <code data-v-be77961e>focus-visible</code> always uses <code data-v-be77961e>--p-focus-ring</code> (appears only on keyboard focus, see §08); <code data-v-be77961e>disabled</code> is uniformly <code data-v-be77961e>opacity:.5</code>.</p><table class="dt" data-v-be77961e><thead data-v-be77961e><tr data-v-be77961e><th data-v-be77961e>State</th><th data-v-be77961e>Button</th><th data-v-be77961e>Input</th><th data-v-be77961e>Card</th><th data-v-be77961e>Menu item</th><th data-v-be77961e>Switch</th></tr></thead><tbody data-v-be77961e><tr data-v-be77961e><td class="tk" data-v-be77961e>default</td><td data-v-be77961e>✓</td><td data-v-be77961e>✓</td><td data-v-be77961e>✓</td><td data-v-be77961e>✓</td><td data-v-be77961e>✓</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>hover</td><td data-v-be77961e>✓</td><td data-v-be77961e>✓</td><td data-v-be77961e>✓</td><td data-v-be77961e>✓</td><td data-v-be77961e>—</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>active / pressed</td><td data-v-be77961e>✓</td><td data-v-be77961e>—</td><td data-v-be77961e>—</td><td data-v-be77961e>—</td><td data-v-be77961e>—</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>focus-visible</td><td data-v-be77961e>✓</td><td data-v-be77961e>✓</td><td data-v-be77961e>—</td><td data-v-be77961e>—</td><td data-v-be77961e>✓</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>disabled</td><td data-v-be77961e>✓</td><td data-v-be77961e>✓</td><td data-v-be77961e>—</td><td data-v-be77961e>✓</td><td data-v-be77961e>—</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>loading</td><td data-v-be77961e>✓</td><td data-v-be77961e>—</td><td data-v-be77961e>—</td><td data-v-be77961e>—</td><td data-v-be77961e>—</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>selected / active</td><td data-v-be77961e>—</td><td data-v-be77961e>—</td><td data-v-be77961e>—</td><td data-v-be77961e>✓</td><td data-v-be77961e>✓</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>error</td><td data-v-be77961e>—</td><td data-v-be77961e>✓</td><td data-v-be77961e>—</td><td data-v-be77961e>—</td><td data-v-be77961e>—</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>readonly</td><td data-v-be77961e>—</td><td data-v-be77961e>✓</td><td data-v-be77961e>—</td><td data-v-be77961e>—</td><td data-v-be77961e>—</td></tr></tbody></table><h3 class="sub" data-v-be77961e>Chat working indicator</h3><div class="callout good" data-v-be77961e><span class="ico" data-v-be77961e>✓</span><div data-v-be77961e> The chat working state ("prompt sent, turn unfinished") is a brand signature of Kimi Web, rendered uniformly by the <code data-v-be77961e>WorkingIndicator</code> component: the 小蓝 mascot plus a phase label — "Requesting…" until the assistant's reply starts, "Working…" once it is streaming. All other loading states (including <code data-v-be77961e>ActivityNotice</code>) use the plain <code data-v-be77961e>Spinner</code>. </div></div><h3 class="sub" data-v-be77961e>Glassmorphism exemption</h3><div class="callout good" data-v-be77961e><span class="ico" data-v-be77961e>✓</span><div data-v-be77961e><code data-v-be77961e>backdrop-filter: blur</code> is banned site-wide, with <b data-v-be77961e>two exceptions</b>: the <code data-v-be77961e>.frost</code> variant of <code data-v-be77961e>TopBar</code> — only in the one place of the "sticky navigation bar", used to stay readable over scrolling content — and the floating menu surfaces (Menu.vue, the Select listbox, composer dropdowns, slash/mention popups), which go through the <code data-v-be77961e>--color-menu-bg</code> / <code data-v-be77961e>--p-menu-backdrop</code> token pair so the recipe stays single-sourced. No other component (card, dialog, Toast, panel) may use glassmorphism; violations are flagged under <code data-v-be77961e>no-glassmorphism</code>, and menu blur with ad-hoc values (anything but the token) is flagged too. Persistent panels that stay open over scrolling content (the dock work panel) deliberately stay opaque — a live backdrop blur re-samples the scrolling page every frame and janks in Chromium. </div></div><div class="footer" data-v-be77961e><span data-v-be77961e>Kimi Web Design System · v1.0</span><span data-v-be77961e>The reference when changing the web UI</span></div></section><section id="shell" data-v-be77961e><div class="sec-head" data-v-be77961e><span class="sec-num" data-v-be77961e>07</span><h2 class="sec-title" data-v-be77961e>App Shell & Sidebar</h2></div><p class="sec-desc" data-v-be77961e> The structural spec for the app shell (three-column grid + right preview panel) and the left session sidebar. These are business-agnostic "skeletons" — components, fonts, radii, and surfaces are reused from §02 / §03, but layout and alignment have their own conventions. </p><h3 class="sub" data-v-be77961e>Layout grid</h3><p data-v-be77961e>On web it is a single-row 5-track grid: the sidebar and the right panel each occupy a permanent <code data-v-be77961e>auto</code> track, with the conversation column in the middle; two 0-width tracks are for the ResizeHandles. (The desktop app adds a second row for its terminal panel — desktop-only, see below.)</p><div class="code" data-v-be77961e><div class="code-bar" data-v-be77961e><span class="d" data-v-be77961e></span><span class="d" data-v-be77961e></span><span class="d" data-v-be77961e></span><span class="fn" data-v-be77961e>App.vue · .app</span></div><pre data-v-be77961e>grid-template-columns: auto 0 minmax(0, 1fr) 0 auto;\n /* sidebar ↑ ↑handle ↑conversation ↑handle ↑right panel (auto) */</pre></div><table class="dt" data-v-be77961e><thead data-v-be77961e><tr data-v-be77961e><th data-v-be77961e>Token</th><th data-v-be77961e>Value</th><th data-v-be77961e>Usage</th></tr></thead><tbody data-v-be77961e><tr data-v-be77961e><td class="tk" data-v-be77961e>sidebar width</td><td class="val" data-v-be77961e>270px default (adjustable)</td><td data-v-be77961e>expanded sidebar width, changed by dragging the ResizeHandle; should approach §02's <code data-v-be77961e>--p-sidebar-w</code> (264px)</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>--preview-w</td><td class="val" data-v-be77961e>460px</td><td data-v-be77961e>width of the right preview panel when open</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>--panel-head-h</td><td class="val" data-v-be77961e>48px</td><td data-v-be77961e>unified height for all right panel heads + the conversation column head; both use a 0.5px bottom hairline</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>--p-bp-sm</td><td class="val" data-v-be77961e>640px</td><td data-v-be77961e>≤640 switches to a mobile single column (top bar + conversation), no sidebar / handle / right panel</td></tr></tbody></table><ul class="clean" data-v-be77961e><li data-v-be77961e>The right panel track exists permanently, with its width toggling between <code data-v-be77961e>0 ↔ var(--preview-w)</code> and no transition — animating a grid track would relayout the whole app grid every frame (when open it squeezes the conversation column, rather than switching templates).</li><li data-v-be77961e>The sidebar collapses SYMMETRICALLY to the right panel: its container width animates to 0 while the content keeps its fixed width anchored to the right edge (clipped, sliding out left — no reflow, hairline stays on the clipped content). No rail remains. The collapse control differs by platform: on <b data-v-be77961e>macOS desktop</b> the toggle is a single resident floating IconButton pinned beside the traffic lights (rendered in both states, only the glyph swaps — the sidebar slides underneath it, never moves or flashes); on <b data-v-be77961e>Windows / web</b> the collapse button lives inside the sidebar header (right-aligned), and a floating expand button appears at the top-left only while collapsed. The conversation header uses a 0.5px bottom hairline and pads left in step with the transition while collapsed.</li><li data-v-be77961e>All grid children must have <code data-v-be77961e>min-height:0; min-width:0</code>, so only the inner scroll containers scroll and the page itself does not scroll.</li></ul><h3 class="sub" data-v-be77961e>Sidebar alignment system (<code data-v-be77961e>--sb-*</code>)</h3><p data-v-be77961e>All sidebar rows (group head, session row, New chat, search, and Settings buttons) share 4 custom properties. Their 16px icon slots and <code data-v-be77961e>--sb-gap</code> place every label on the same x-axis as the workspace name.</p><table class="dt" data-v-be77961e><thead data-v-be77961e><tr data-v-be77961e><th data-v-be77961e>Token</th><th data-v-be77961e>Value</th><th data-v-be77961e>Usage</th></tr></thead><tbody data-v-be77961e><tr data-v-be77961e><td class="tk" data-v-be77961e>--sb-inset</td><td class="val" data-v-be77961e>12px</td><td data-v-be77961e>row box (hover/selected pill) inset from the sidebar edges — matches the brand header's 12px padding</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>--sb-pad-x</td><td class="val" data-v-be77961e>20px</td><td data-v-be77961e>content start x (= --sb-inset + 8px row padding)</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>--sb-gutter</td><td class="val" data-v-be77961e>16px</td><td data-v-be77961e>leading icon slot width — matches the workspace folder icon so the session title aligns under the workspace name</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>--sb-gap</td><td class="val" data-v-be77961e>8px</td><td data-v-be77961e>gap between the icon slot and the text</td></tr></tbody></table><div class="callout info" data-v-be77961e><span class="ico" data-v-be77961e>i</span><div data-v-be77961e> The session title's starting x = <code data-v-be77961e>--sb-pad-x + --sb-gutter + --sb-gap</code>. The group head has a folder icon and the session row has a status slot; both icons are the same width and position, so the titles align naturally. </div></div><h3 class="sub" data-v-be77961e>Sidebar structure</h3><p data-v-be77961e>The sidebar from top to bottom: brand header → action group → pinned head (pinned section + "Workspaces" label) → scrolling grouped list (workspace head + session rows) → user-menu footer. New chat and Search are direct sibling controls in the same grid container; the optional new-workspace action shares the first row, while Search spans the next row. A 4px gap keeps Search clear of the scroll boundary. The pinned head sits OUTSIDE the scroll container (the action-group / footer pattern — never <code data-v-be77961e>position: sticky</code>, which would need an opaque plate over the frosted tint), so the pinned sessions and the "Workspaces" label stay put while the workspace groups scroll beneath; the pinned section is collapsible (a chevron on its label, revealed on hover/focus and kept visible while folded; state persisted) so a long pinned set can't eat the sidebar, and it re-expands when a new session is pinned. Both pinned edges use three light near, middle and far fades across 18px, entering over 260ms only while more session content exists beyond that edge — the top seam lives at the pinned head's bottom border. The footer seam is a 0.5px hairline. Controls reuse the §03 primitives as much as possible. The sidebar sits on <code data-v-be77961e>--color-sidebar-bg</code> (one step off <code data-v-be77961e>--color-bg</code>: warm off-white just under white in light, one step BELOW the page in dark — the session column reads as its own plane, and with dark elevation = lighter the chrome never sits brighter than the conversation pane; the hairline still separates it from the pane). Vertical rhythm: the brand header keeps 12px padding (on macOS desktop the left padding grows to 80px to clear the traffic lights); rows inside the action group stack flush (0 gap, same rhythm as the list rows); adjacent groups are separated by 12px. The search glyph has a -0.5px optical correction to align its visual centre with the label. Row hover uses <code data-v-be77961e>--sb-hover</code> (= the global <code data-v-be77961e>--color-hover</code> wash); the selected row uses the lighter <code data-v-be77961e>--sb-selected</code> wash derived from <code data-v-be77961e>--color-selected</code> — On macOS desktop the sidebar is instead <b data-v-be77961e>frosted</b>: the window carries a native <code data-v-be77961e>NSVisualEffectView</code> ('menu' vibrancy, following the in-app scheme via the nativeTheme mirror, its state pinned to <code data-v-be77961e>inactive</code> so the material keeps its flat pressed-down colour — ≈ #282829 dark / #E7E7E7 light — with no active/inactive drift) and the sidebar column drops <code data-v-be77961e>--color-sidebar-bg</code> for a single translucent <code data-v-be77961e>--color-sidebar-tint</code> wash that presses the pinned material one step — ≈ #282829 → ≈ #1e1e1f in dark (<code data-v-be77961e>rgba(0,0,0,0.25)</code>), ≈ #E7E7E7 → ≈ #f1f1f1 in light (<code data-v-be77961e>rgba(255,255,255,0.4)</code>) — with header and footer staying transparent so the tint reads as one uniform pane; the root chain (<code data-v-be77961e>html/body/#app/.app</code>) stays unpainted only under the <code data-v-be77961e>macos-desktop</code> + <code data-v-be77961e>vibrancy</code> flags — the latter is the Settings → Appearance accessibility switch (default on; persisted main-side so the window is created with the right material, and live-applied on toggle): off repaints the root chain and the sidebar falls back to opaque <code data-v-be77961e>--color-sidebar-bg</code>, while the traffic-light layout keeps keying off <code data-v-be77961e>macos-desktop</code> alone — while the conversation pane, chat header and right preview keep their own opaque surfaces. The list's hover-icon clusters (session-row kebab, group-head actions) paint NOTHING there — no plate, no wash, no blur (real backdrop blur does not even render over this window: Chromium's backdrop sampler returns a flat wash above the transparent BrowserWindow + vibrancy view). Instead the row's title/name dissolves before it ever reaches the buttons: a two-stage <code data-v-be77961e>mask-image</code> fade — a subtle 16px dissolve at rest, extending over the cluster zone only while the actions are revealed (row hover / keyboard focus / menu open): 34px on session rows (the pin+kebab cluster overhangs the title by ≈25px), 68px on group heads (the floating cluster is ≈60px wide). The fade is zone-based, so short rows render untouched, and <code data-v-be77961e>text-overflow</code> becomes <code data-v-be77961e>clip</code> so a long tail dissolves instead of dotting.</p><table class="dt" data-v-be77961e><thead data-v-be77961e><tr data-v-be77961e><th data-v-be77961e>Block</th><th data-v-be77961e>Use</th><th data-v-be77961e>Note</th></tr></thead><tbody data-v-be77961e><tr data-v-be77961e><td data-v-be77961e>Brand header</td><td data-v-be77961e>logo + name + collapse IconButton (right-aligned)</td><td data-v-be77961e>on Windows / web the brand is left and the collapse IconButton sm is right-aligned inside the header; the dev-only backend version/address pill uses the UI font, not monospace; the logo is animated (a blinking eye). On macOS desktop the header is a bare drag strip (brand hidden, traffic lights + resident floating toggle over it)</td></tr><tr data-v-be77961e><td data-v-be77961e>New chat</td><td data-v-be77961e>full-width left-aligned button (custom)</td><td data-v-be77961e>500-weight label; same rhythm as the session rows in the list (left-aligned, hover = <code data-v-be77961e>--sb-hover</code>). <b data-v-be77961e>Do not</b> use Button (centered, breaks the rhythm)</td></tr><tr data-v-be77961e><td data-v-be77961e>Search</td><td data-v-be77961e>bare search row (custom)</td><td data-v-be77961e>500-weight label; no border, hover/focus shows the faint <code data-v-be77961e>--color-hover</code> wash; icon + label, with the <code data-v-be77961e>Kbd</code> keycaps (⌘K / Ctrl K) pushed to the trailing edge — label and shortcut are justified apart. <b data-v-be77961e>Do not</b> use Input (the 38px bordered version is too heavy). It is a direct sibling of New chat in the action group</td></tr><tr data-v-be77961e><td data-v-be77961e>Section label</td><td data-v-be77961e><code data-v-be77961e>.p-section-label</code></td><td data-v-be77961e>uppercase muted small titles like "Workspaces", using <code data-v-be77961e>--weight-section-label</code> (600)</td></tr><tr data-v-be77961e><td data-v-be77961e>Pinned head</td><td data-v-be77961e>fixed block above the scroll container (<code data-v-be77961e>.sessions-head</code>): the pinned section (<code data-v-be77961e>PinnedSessionList.vue</code>) + the "Workspaces" section label</td><td data-v-be77961e>stays put while the workspace groups scroll; owns the top scroll-linked seam (hairline + fade, only while scrolled). The pinned section folds via its label chevron (persisted, <code data-v-be77961e>kimi-web.pinned-collapsed</code>) and re-expands only on an explicit pin (never on load backfill); the expanded rows are capped at 40vh with their own scroll so a long pinned set can't push the list or footer out of view</td></tr><tr data-v-be77961e><td data-v-be77961e>Workspace head / session row</td><td data-v-be77961e>see next two sections</td><td data-v-be77961e>share <code data-v-be77961e>--sb-*</code> alignment</td></tr><tr data-v-be77961e><td data-v-be77961e>User-menu footer</td><td data-v-be77961e>account area (<code data-v-be77961e>components/UserMenu.vue</code>) opening an upward §03 menu</td><td data-v-be77961e>pinned row under the session list, separated by a 0.5px <code data-v-be77961e>--line</code> hairline; trigger keeps the same list-style family as New chat (24px round avatar + nickname when signed in, user icon + sign-in hint otherwise). The menu box follows the trigger's left edge and width (ResizeObserver-tracked, so it survives a sidebar resize) and is teleported to body because the column's container-type would capture position:fixed. Rows: plan usage / theme / language are macOS-style hover flyout submenus — the parent row carries the module icon, a faint current value and a fixed chevron-right, and hovering (or moving focus to the parent row, or pressing Enter / Space / → on it) opens a teleported panel anchored to the parent menu's right edge (content-adaptive width floored by the menu's own min-width and capped at the parent menu's width; flips left near the viewport edge) with a 250ms hover-intent close grace; the usage panel shows weekly + 5h rows (percent values with severity colours), while the theme (three schemes) and language (two locales) panels move the check to the picked option without closing the menu — then the upgrade entry below the top plan level, settings (with an always-visible Kbd keycap shortcut hint on desktop) and a confirming sign-out; all menu icons come from the Kimi set</td></tr></tbody></table><div class="callout warn" data-v-be77961e><span class="ico" data-v-be77961e>!</span><div data-v-be77961e><b data-v-be77961e>Why New chat / search / inline rename don't use Button / Input:</b> they are "list-style" controls (full-width, left-aligned, compact, borderless), while Button is centered and Input is a 38px bordered control — forcing them in would break the sidebar's visual density and alignment. This is an intentional custom exception, not an oversight. </div></div><h3 class="sub" data-v-be77961e>Session row</h3><p data-v-be77961e>A session row is an inset rounded pill, structured as: <code data-v-be77961e>status slot → title → time → attention Badge → hover actions (pin / archive)</code>.</p><table class="dt" data-v-be77961e><thead data-v-be77961e><tr data-v-be77961e><th data-v-be77961e>Part</th><th data-v-be77961e>Rule</th></tr></thead><tbody data-v-be77961e><tr data-v-be77961e><td data-v-be77961e>Container</td><td data-v-be77961e><code data-v-be77961e>padding: 8px 8px</code> inside the list's <code data-v-be77961e>--sb-inset</code> gutter, <code data-v-be77961e>radius-sm</code>; <b data-v-be77961e>no fixed/min height</b> — row height is font-driven (title <code data-v-be77961e>line-height: --leading-tight</code>, ≈16px) → ≈32px total, the sidebar-wide row rhythm. The hover actions are absolutely positioned so they never force the row taller (no hover jitter). hover = <code data-v-be77961e>--sb-hover</code> (the global <code data-v-be77961e>--color-hover</code> wash); active = <code data-v-be77961e>--sb-selected</code> (75% of the global selected wash) — neutral, no accent tint, no border, no weight change</td></tr><tr data-v-be77961e><td data-v-be77961e>Status slot (lead)</td><td data-v-be77961e>fixed <code data-v-be77961e>--sb-gutter</code> width; running = <code data-v-be77961e>Spinner</code> sm, otherwise unread = 7px accent dot</td></tr><tr data-v-be77961e><td data-v-be77961e>Title</td><td data-v-be77961e>flex:1 with truncation and <code data-v-be77961e>user-select:none</code>; double-click enters inline rename (compact input, not Input), whose text remains selectable</td></tr><tr data-v-be77961e><td data-v-be77961e>Emoji icon</td><td data-v-be77961e>the session icon is the title's LEADING emoji cluster (web-core <code data-v-be77961e>splitSessionEmoji</code> — no icon field; every client renders the title as-is). The emoji is an ordinary title character — no decoration at rest or on hover (it stays a <code data-v-be77961e><button></code> for a11y), and clicking it opens <code data-v-be77961e>SessionEmojiPicker</code> — a Menu-shelled panel (bare list-style search row → scrollable sections: Recently used persisted in localStorage (cap 8) + the grouped emoji dataset, with remove/random as MenuItems in the footer; a query swaps the sections for keyword-search results), teleported + fixed + <code data-v-be77961e>--z-dropdown</code>, popping from the trigger corner like the right-click menu. The menu's "Set Emoji…" opens the same picker and is the discoverable path. Inline rename edits the whole title — the emoji is an ordinary character in the input</td></tr><tr data-v-be77961e><td data-v-be77961e>Time</td><td data-v-be77961e>mono xs, <code data-v-be77961e>fg-faint</code>; yields to the hover actions on hover</td></tr><tr data-v-be77961e><td data-v-be77961e>Attention Badge</td><td data-v-be77961e><code data-v-be77961e>Badge</code> sm: info (needs answer) / warning (needs approval) / danger (aborted)</td></tr><tr data-v-be77961e><td data-v-be77961e>Hover actions</td><td data-v-be77961e><code data-v-be77961e>IconButton</code> sm × 2 — pin + archive — cross-faded over the time on row hover (no kebab button). Right-clicking the row opens the full menu (copy ID / rename / emoji / fork / export / pin / archive + timestamp) anchored to the cursor, except over the inline rename input, where the native text-editing menu stays</td></tr><tr data-v-be77961e><td data-v-be77961e>Archive</td><td data-v-be77961e>no confirm — the hover archive button / menu item archives immediately, then App.vue shows the §03 <code data-v-be77961e>ActionToast</code> (top-center) with Undo (restores the session) and Settings (opens the archived list)</td></tr></tbody></table><h3 class="sub" data-v-be77961e>Workspace group</h3><p data-v-be77961e>The group head and session rows share <code data-v-be77961e>--sb-*</code>: folder icon (open/closed) → name, with the kebab and "+" revealed on hover.</p><ul class="clean" data-v-be77961e><li data-v-be77961e>The folder icon leads the row (switching icons between open and closed states) with the plain <code data-v-be77961e>--sb-gap</code> before the name — it does not pad out the <code data-v-be77961e>--sb-gutter</code> slot.</li><li data-v-be77961e>The name uses 500 weight with muted color (<code data-v-be77961e>--color-text-muted</code>, one step lighter than session titles), so group heads remain clear without competing with list content. No path subtitle; hovering the name shows the full root path in a <code data-v-be77961e>Tooltip</code>.</li><li data-v-be77961e>The kebab (menu) and "+" (new chat in this workspace) both use <code data-v-be77961e>IconButton</code> sm inside a floating actions layer anchored to the row's right edge — no reserved layout space, so the name uses the full row width when idle. Shown on hover, keyboard focus, or while the menu is open; the layer backs itself with the sidebar surface (container background) plus the row hover wash (an <code data-v-be77961e>::after</code> shown only while the row is hovered), so its color exactly equals the row's current background and the overlapped name tail doesn't bleed through (hidden via <code data-v-be77961e>opacity:0</code>, staying in the tab order). On macOS desktop the layer paints nothing at all — the name's <code data-v-be77961e>mask-image</code> fade (see the sidebar section above) dissolves the tail before it reaches the buttons</li><li data-v-be77961e>The group is collapsible; when collapsed its session list is hidden.</li><li data-v-be77961e>While the active workspace has no session selected (the draft state — e.g. right after adding the workspace, or after New chat), the group head carries the same neutral <code data-v-be77961e>--sb-selected</code> fill as a selected session row (selection reads as "where I am"; the fill wins over hover). Once a session is selected or created, the fill moves to that session row.</li></ul><h3 class="sub" data-v-be77961e>Show more & collapse</h3><p data-v-be77961e>The "expand / collapse" controls at the bottom of each workspace group are compact list controls (same family as search, New chat, inline rename — not Buttons) sharing one row: expand (chevron-down) first, collapse (chevron-up) after a faint middot when both are present. Expanding reveals the next batch of sessions, fetching the next page from the server only when the locally loaded rows can't cover it — the control never exposes whether a reveal came from memory or the network.</p><table class="dt" data-v-be77961e><thead data-v-be77961e><tr data-v-be77961e><th data-v-be77961e>Part</th><th data-v-be77961e>Rule</th></tr></thead><tbody data-v-be77961e><tr data-v-be77961e><td class="tk" data-v-be77961e>Row</td><td data-v-be77961e>a single flex row holding the controls, all content-width — hover washes just the button as a snug pill, never the full row. Font-driven height (≈32px like a session row), <code data-v-be77961e>radius-sm</code>; hover = <code data-v-be77961e>--sb-hover</code> (no text recolor); <code data-v-be77961e>:focus-visible</code> uses <code data-v-be77961e>--p-focus-ring</code></td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>Chevron</td><td data-v-be77961e>sm (down = expand, up = collapse); the row indents by <code data-v-be77961e>--sb-gutter + --sb-gap</code> so the first button's chevron starts exactly at the session-title x, lining the control's leading edge up with the titles above</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>Label</td><td data-v-be77961e><code data-v-be77961e>font-ui</code>, <code data-v-be77961e>text-xs</code>, <code data-v-be77961e>--color-text-muted</code>; truncated</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>Separator</td><td data-v-be77961e>faint middot (<code data-v-be77961e>--color-text-faint</code>) with <code data-v-be77961e>--space-1</code> side margins, rendered only when both controls are present</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>Behavior</td><td data-v-be77961e>each group keeps a display cap starting at the first page; "Show more" steps it up by one batch (5) and fetches the next page only when the loaded rows fall short (busy = "Loading…", disabled); "Show less" resets the cap to the first page (view-layer trim — data is kept, no refetch). "Show more" exists while undisplayed loaded rows remain or the server has more; "Show less" appears once past the first page</td></tr></tbody></table><h3 class="sub" data-v-be77961e>ResizeHandle</h3><p data-v-be77961e>A 4px grab strip layered over the 1px column border (<code data-v-be77961e>margin: 0 -2px</code> makes the whole 4px grabbable) with a centred 2px indicator bar. The bar stays transparent at rest and shows the neutral fills one step up the ramp — f2 on hover, f3 while the drag is live (the sidebar column is translucent on macOS, so f1 read too faint) — never the accent.</p><table class="dt" data-v-be77961e><thead data-v-be77961e><tr data-v-be77961e><th data-v-be77961e>Rule</th><th data-v-be77961e>Value</th></tr></thead><tbody data-v-be77961e><tr data-v-be77961e><td data-v-be77961e>Width / cursor</td><td data-v-be77961e>4px strip, 2px bar / <code data-v-be77961e>col-resize</code> mid-range; <code data-v-be77961e>w-resize</code> / <code data-v-be77961e>e-resize</code> at the drag limits (hints the direction that still resizes)</td></tr><tr data-v-be77961e><td data-v-be77961e>Normal / hover / drag</td><td data-v-be77961e>transparent / <code data-v-be77961e>--color-selected</code> (f2) / <code data-v-be77961e>--color-line-strong</code> (f3) — the neutral ramp one step up, never accent</td></tr><tr data-v-be77961e><td data-v-be77961e>Layer</td><td data-v-be77961e><code data-v-be77961e>--z-dropdown</code>, above pane-level sticky chrome (chat dock at <code data-v-be77961e>--z-sticky</code>) so the overhang stays visible and grabbable</td></tr><tr data-v-be77961e><td data-v-be77961e>Behavior</td><td data-v-be77961e>panel width follows the pointer 1:1 while dragging (the parent disables transitions to avoid lag); on release it is persisted to localStorage</td></tr></tbody></table><h3 class="sub" data-v-be77961e>Right panel</h3><p data-v-be77961e>The right panels (file preview / Diff / compaction summary / sub-agent / side chat) share one track and one head primitive.</p><ul class="clean" data-v-be77961e><li data-v-be77961e>The panel head uses the <code data-v-be77961e>PanelHeader</code> primitive (48px = <code data-v-be77961e>--panel-head-h</code>), the same height as the conversation column head, so the hairline runs as one line.</li><li data-v-be77961e>Panel head: bold mono title + optional muted subtitle + middle slot (Badge / control / path) + close IconButton on the right.</li><li data-v-be77961e>When opened, the panel width snaps from <code data-v-be77961e>0 → var(--preview-w)</code> with no animation, squeezing the conversation column in a single layout.</li><li data-v-be77961e>At ≤640px the panel becomes a full-screen overlay (<code data-v-be77961e>position:fixed; inset:0</code>).</li></ul><h3 class="sub" data-v-be77961e>Bottom terminal panel (desktop-only)</h3><p data-v-be77961e>The native terminal (<code data-v-be77961e>components/terminal/</code>) sits in the conversation column's own bottom grid slot — the sidebar and the right panel span BOTH rows and keep full height (the VS Code layout: the panel belongs to the editor area, not to the whole window). Its height transitions <code data-v-be77961e>0 ↔ var(--terminal-h)</code> (260px default, 120 min, 60% viewport max; persisted), squeezing the conversation column above instead of overlaying it. The panel mounts lazily on first open and then stays mounted so xterm scrollback survives a collapse.</p><ul class="clean" data-v-be77961e><li data-v-be77961e>Resize: a horizontal twin of the ResizeHandle (4px strip over the 0.5px top hairline, <code data-v-be77961e>row-resize</code> mid-range, <code data-v-be77961e>n/s-resize</code> at the limits, same neutral f2/f3 ramp, never accent). The shared <code data-v-be77961e>useResizable</code> hook owns it via <code data-v-be77961e>axis: 'y'</code>; the height var is written imperatively during a drag (same no-Vue-rerender rule as <code data-v-be77961e>--preview-w</code>).</li><li data-v-be77961e>Toolbar (32px, 0.5px bottom hairline): tab strip on the left — each tab is a compact <code data-v-be77961e>radius-sm</code> pill (leading terminal glyph, muted while exited + shell label + hover close affordance), the active tab uses <code data-v-be77961e>--color-selected</code>, hover <code data-v-be77961e>--color-hover</code>; a "+" action appends a tab. Tabs follow the §08 tablist keyboard model (roving tabindex, ←/→/Home/End), the close affordance is its own button (no nested interactives), and the height separator is keyboard-operable (↑/↓ in steps, value exposed). Trailing actions: restart (only while the active tab exited) and a collapse chevron. Collapsing sets <code data-v-be77961e>inert</code> on the region — the xterm instances and their scrollback stay mounted but leave the tab order.</li><li data-v-be77961e>The xterm canvas cannot resolve CSS variables either, so its palette is resolved from the live <code data-v-be77961e>--color-*</code> tokens at runtime (re-read on scheme flips; the ANSI hues the status ramp doesn't cover use dedicated <code data-v-be77961e>--color-term-magenta/cyan</code> tokens); the font is the app JetBrains Mono stack sized off the content token scale. While focused, the panel owns every key except the registered app shortcuts (chat-level Esc / find / select-all chords stay inert inside it).</li><li data-v-be77961e>Entries: the chat header's terminal IconButton (right of Open in, lit while the panel is open) — on the empty-composer state, where no chat header renders, the same button floats at the conversation's top-right instead — plus <code data-v-be77961e>ctrl+`</code> (⌃` on macOS — VS Code's binding; ⌘` stays free for the OS window switcher — customizable in the shortcut registry), and the View menu's Toggle Terminal item. New tabs spawn in the visible workspace root. Terminal state is per session: switching sessions swaps the visible bucket while the others keep their PTYs and xterm views alive (scrollback survives a round trip; the ten most recent sessions are kept, LRU). The panel never renders on mobile / web.</li></ul><div class="callout info" data-v-be77961e><span class="ico" data-v-be77961e>i</span><div data-v-be77961e><b data-v-be77961e>One-sentence principle:</b> the sidebar / shell is a "list + grid" skeleton that reuses the §02 tokens and §03 primitives (Button / IconButton / Badge / Kbd / Menu / Spinner / PanelHeader); compact list controls that don't fit a primitive (search, New chat, inline rename, show-more) keep their custom form, governed by this section. </div></div></section><section id="a11y" data-v-be77961e><div class="sec-head" data-v-be77961e><span class="sec-num" data-v-be77961e>08</span><h2 class="sec-title" data-v-be77961e>Accessibility (pragmatic edition)</h2></div><p class="sec-desc" data-v-be77961e> Kimi Web is a local developer tool; it <b data-v-be77961e>does not target a specific WCAG conformance level</b>, nor maintain a full screen-reader QA matrix. This section collects only the rules that are "low-cost, don't hurt the look, and directly benefit keyboard-heavy users", as the baseline contract for each primitive; the more expensive, lower-ROI parts (such as real-time announcement orchestration for streaming output) are not mandatory for now. </p><div class="callout info" data-v-be77961e><span class="ico" data-v-be77961e>i</span><div data-v-be77961e><b data-v-be77961e>On the "ugly" focus ring:</b> the focus visibility required below always uses <code data-v-be77961e>:focus-visible</code> (not <code data-v-be77961e>:focus</code>). It appears <b data-v-be77961e>only on keyboard focus</b>; mouse clicks don't trigger it, so it doesn't pollute the mouse-driven visual; the ring's strength is tuned uniformly with <code data-v-be77961e>--p-focus-ring</code>, not overridden per place. </div></div><h4 class="mini" data-v-be77961e>1. Contrast & color</h4><ul class="clean" data-v-be77961e><li data-v-be77961e>Body text vs. background contrast <b data-v-be77961e>≥ 4.5:1</b>; control borders, icons, and key graphics <b data-v-be77961e>≥ 3:1</b>. When changing theme colors / dark mode, verify against §05 together.</li><li data-v-be77961e><b data-v-be77961e>Button text vs. button background</b>, and <b data-v-be77961e>form controls</b> (input, placeholder, helper / error text) <b data-v-be77961e>vs. their section background</b> must all have contrast ≥ 4.5:1 (large text ≥ 3:1). White-on-white text, a transparent borderless button floating over the page background, and a light placeholder on a near-white background are all flagged by the style rules.</li><li data-v-be77961e><b data-v-be77961e>State is not conveyed by color alone.</b> Error, selected, and disabled states also carry text, an icon, or a shape change (for example an error state is not just red, but also carries text or an icon).</li></ul><h4 class="mini" data-v-be77961e>2. Keyboard operable</h4><p data-v-be77961e>Anything doable with a mouse must also be doable with a keyboard; Tab order follows the DOM, with no invented skipping. Composite controls define their keyboard model per the table below; a missing model is treated as incomplete:</p><table class="dt" data-v-be77961e><thead data-v-be77961e><tr data-v-be77961e><th data-v-be77961e>Control</th><th data-v-be77961e>Keyboard behavior</th></tr></thead><tbody data-v-be77961e><tr data-v-be77961e><td class="tk" data-v-be77961e>Dialog</td><td data-v-be77961e><code data-v-be77961e>Tab</code> cycles within the dialog (focus trap); <code data-v-be77961e>Esc</code> closes; focus returns to the trigger element after closing.</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>Menu</td><td data-v-be77961e><code data-v-be77961e>↑</code> / <code data-v-be77961e>↓</code> move the highlight, <code data-v-be77961e>Enter</code> selects, <code data-v-be77961e>Esc</code> closes.</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>Tabs</td><td data-v-be77961e><code data-v-be77961e>←</code> / <code data-v-be77961e>→</code> switch tabs (roving tabindex); only the current tab is in the Tab sequence.</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>Switch / Segmented</td><td data-v-be77961e><code data-v-be77961e>←</code> / <code data-v-be77961e>→</code> or <code data-v-be77961e>Space</code> / <code data-v-be77961e>Enter</code> to toggle.</td></tr></tbody></table><h4 class="mini" data-v-be77961e>3. Focus visibility</h4><ul class="clean" data-v-be77961e><li data-v-be77961e>Every interactive element must have a visible focus indicator on keyboard focus, uniformly via <code data-v-be77961e>:focus-visible</code> + <code data-v-be77961e>--p-focus-ring</code> (primary actions may use <code data-v-be77961e>--p-focus-ring-strong</code>).</li><li data-v-be77961e>Bare <code data-v-be77961e>outline: none</code> is forbidden. To remove the default outline, you must provide an equivalent replacement style.</li></ul><h4 class="mini" data-v-be77961e>4. Labels & semantics</h4><ul class="clean" data-v-be77961e><li data-v-be77961e><b data-v-be77961e>Semantic HTML first</b> (button / a / input / dialog…); ARIA is added only when native semantics fall short.</li><li data-v-be77961e>Icon-only buttons must have an <code data-v-be77961e>aria-label</code> — <code data-v-be77961e>IconButton</code> already enforces this with a required <code data-v-be77961e>label</code> prop.</li><li data-v-be77961e>Dialog: <code data-v-be77961e>role="dialog"</code> + <code data-v-be77961e>aria-modal="true"</code>, with the title as the dialog's accessible name.</li><li data-v-be77961e>Purely decorative SVG / icons get <code data-v-be77961e>aria-hidden="true"</code> to avoid being read out by screen readers.</li></ul><h4 class="mini" data-v-be77961e>5. Target size</h4><p data-v-be77961e>Desktop click targets <b data-v-be77961e>≥ 32px</b>; touch devices <b data-v-be77961e>≥ 44px</b> (consistent with the §01 principle and the IconButton <code data-v-be77961e>lg</code> tier).</p><h4 class="mini" data-v-be77961e>6. Reduced motion</h4><p data-v-be77961e>Handled uniformly in the global styles per §02's <code data-v-be77961e>@media (prefers-reduced-motion: reduce)</code>; components do not check this individually. The chat working indicator's mascot renders its static fallback.</p><h4 class="mini" data-v-be77961e>7. Live announcements (non-mandatory)</h4><p data-v-be77961e>Screen-reader announcements are <b data-v-be77961e>not a mandatory contract</b> in this product. Short hints like Toast can use <code data-v-be77961e>role="status"</code> / <code data-v-be77961e>aria-live</code>; chat streaming output is currently not announced word-by-word, which is an acceptable trade-off, to be added later if a real need arises.</p><div class="callout good" data-v-be77961e><span class="ico" data-v-be77961e>✓</span><div data-v-be77961e><b data-v-be77961e>Explicitly not mandatory for now:</b> a WCAG conformance-level claim, a complete ARIA pattern table, a per-screen-reader QA matrix, and real-time announcement orchestration for streaming output — these are not written into the primitive contract, to avoid becoming slogans no one maintains. </div></div></section><section id="dialogs" data-v-be77961e><div class="sec-head" data-v-be77961e><span class="sec-num" data-v-be77961e>09</span><h2 class="sec-title" data-v-be77961e>Dialogs</h2></div><p class="sec-desc" data-v-be77961e> Every overlay in the app — pickers, browsers, managers, confirmations — is built on the single §03 Dialog primitive. This chapter fixes the two layout anatomies allowed inside that frame, plus the row and footer contracts that make all dialogs read as one family. Do not hand-roll a third anatomy. </p><h3 class="sub" data-v-be77961e>The frame (recap)</h3><p data-v-be77961e> All dialogs share the §03 primitive: <code data-v-be77961e>--radius-xl</code> radius, <code data-v-be77961e>--shadow-xl</code> shadow, a restrained 28% neutral backdrop, a head (title + IconButton close), a body, and a right-aligned foot. Widths <code data-v-be77961e>md</code> 440 / <code data-v-be77961e>lg</code> 640 / <code data-v-be77961e>xl</code> 760 and <code data-v-be77961e>auto</code> / <code data-v-be77961e>fixed</code> height are chosen per §03. One interruptive overlay at a time; <code data-v-be77961e>Esc</code> closes; focus is trapped and restored. A blocking flow that must be resolved rather than dismissed (server token) uses <code data-v-be77961e>hideClose</code> with <code data-v-be77961e>closeOnOverlay</code>/<code data-v-be77961e>closeOnEsc</code> off — never a hand-written overlay. </p><h3 class="sub" data-v-be77961e>Anatomy A — padded (forms & confirmations)</h3><p data-v-be77961e> The default: the body carries its own padding and the caller drops content straight in. Confirmations put their Buttons in the <code data-v-be77961e>#foot</code> slot (right-aligned, cancel → confirm). Used by: confirm, login, status panel, server token. </p><h3 class="sub" data-v-be77961e>Anatomy B — flush (pickers & browsers)</h3><p data-v-be77961e><code data-v-be77961e>:padded="false"</code> with <code data-v-be77961e>height="fixed"</code>; the consumer owns the zone layout inside a full-height column. The zones below are the whole vocabulary — a picker dialog composes them and adds nothing else. Used by: model picker, session search, folder browser, provider manager. </p><table class="dt" data-v-be77961e><thead data-v-be77961e><tr data-v-be77961e><th data-v-be77961e>Zone</th><th data-v-be77961e>Contract</th></tr></thead><tbody data-v-be77961e><tr data-v-be77961e><td class="tk" data-v-be77961e>Search</td><td data-v-be77961e>The boxed §03 Input, inset 22px so its edge aligns with the head title. Autofocus on open. No leading icon, no borderless variant.</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>Filter chips</td><td data-v-be77961e>Optional. 28px pill: transparent + muted text by default, <code data-v-be77961e>--color-hover</code> on hover, <code data-v-be77961e>--color-selected</code> + medium <code data-v-be77961e>--color-text</code> when active. Horizontally scrollable with the scrollbar hidden. Never a row of Buttons.</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>List</td><td data-v-be77961e><code data-v-be77961e>flex:1</code>, owns the vertical scrolling, padded 4px 8px so rows bleed near the dialog edge. <code data-v-be77961e>role="listbox"</code>; rows carry <code data-v-be77961e>role="option"</code> + <code data-v-be77961e>aria-selected</code>.</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>Row</td><td data-v-be77961e>8px 12px padding, <code data-v-be77961e>--radius-md</code>. Two quiet lines: name 14/20 (medium when current) and a meta line 12/18 in <code data-v-be77961e>--color-text-faint</code> — provider · context · capability labels, dot-separated. No badge rows, no raw-id line (search still matches them). Trailing slot: check icon (current row only), then the star IconButton.</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>Row states</td><td data-v-be77961e>Hover / keyboard-selected → <code data-v-be77961e>--color-hover</code>; current → <code data-v-be77961e>--color-selected</code> — a neutral "where I am" fill, never an accent tint, never an inset stroke. The star stays hidden until row hover, keyboard selection, or starred; it is always visible on touch devices and colored <code data-v-be77961e>--star</code> when starred.</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>State rows</td><td data-v-be77961e>Loading / unavailable / empty: centered on both axes, muted 14px; warning color only for the unavailable case.</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>Shortcut bar</td><td data-v-be77961e>The footer: full-bleed, padding 8px 16px, <code data-v-be77961e>border-top --color-line</code>, left-aligned. Keyboard hints are Kbd keycaps + 12px <code data-v-be77961e>--color-text-faint</code> labels, groups separated by "·", the whole bar <code data-v-be77961e>aria-hidden</code>. An instructional sentence (folder browser) reuses the same bar without keycaps.</td></tr></tbody></table><h4 class="mini" data-v-be77961e>Keyboard & behavior contract</h4><ul class="clean" data-v-be77961e><li data-v-be77961e><code data-v-be77961e>↑</code>/<code data-v-be77961e>↓</code> move a keyboard selection (rendered identical to hover) and always <code data-v-be77961e>scrollIntoView({ block: 'nearest' })</code>; <code data-v-be77961e>Enter</code> selects and closes; <code data-v-be77961e>Esc</code> closes.</li><li data-v-be77961e>Pointer hover drives the same selection index, so keyboard and mouse never disagree about which row is active.</li><li data-v-be77961e>Rows transition <code data-v-be77961e>background</code> only (<code data-v-be77961e>--duration-fast</code> ease-out); the open/close animation lives in the primitive, not in the consumer.</li><li data-v-be77961e>Selection is a fill, not a border (surface over stroke). Accent blue is reserved for actions — primary buttons and focus rings — never for "which row am I on".</li></ul><h4 class="mini" data-v-be77961e>Dialog map</h4><table class="dt" data-v-be77961e><thead data-v-be77961e><tr data-v-be77961e><th data-v-be77961e>Dialog</th><th data-v-be77961e>Anatomy</th><th data-v-be77961e>Composition</th></tr></thead><tbody data-v-be77961e><tr data-v-be77961e><td class="tk" data-v-be77961e>Model picker</td><td data-v-be77961e>flush · lg · fixed</td><td data-v-be77961e>search + provider chips + model rows + shortcut bar</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>Session search</td><td data-v-be77961e>flush · lg · fixed</td><td data-v-be77961e>search + result rows + shortcut bar</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>Folder browser</td><td data-v-be77961e>flush · lg · fixed</td><td data-v-be77961e>breadcrumb bar + filter bar + folder rows + actions + hint bar</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>Provider manager</td><td data-v-be77961e>flush · xl · fixed</td><td data-v-be77961e>management rows with inset dividers (rows are not selectable) + add section + shortcut bar</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>Confirm / Login / Status</td><td data-v-be77961e>padded · md · auto</td><td data-v-be77961e>title + message or form + right-aligned foot</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>App update (desktop)</td><td data-v-be77961e>padded · lg · auto</td><td data-v-be77961e>version title + quiet meta line (release date · current version) + height-capped scrolling what's-new list / progress bar + right-aligned action row (skip → download, later → restart) with the auto-download checkbox right-aligned on its own foot row below (a pure preference for future checks)</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>Server token</td><td data-v-be77961e>padded · md · auto</td><td data-v-be77961e><code data-v-be77961e>hideClose</code>, no Esc/overlay close — resolved only by a valid token</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>Settings</td><td data-v-be77961e>flush · xl · fixed</td><td data-v-be77961e>page-like exception: side-nav region, per §03</td></tr><tr data-v-be77961e><td class="tk" data-v-be77961e>Onboarding wizard</td><td data-v-be77961e>not a Dialog</td><td data-v-be77961e>full-page takeover (not built on §03): one centered column (brand lockup → step content → ghost actions + centered primary CTA); selectable options share the option-card pattern — 0.5px <code data-v-be77961e>--color-line</code> hairline, <code data-v-be77961e>--color-accent</code> border + <code data-v-be77961e>--color-accent-soft</code> fill when selected</td></tr></tbody></table><div class="callout good" data-v-be77961e><span class="ico" data-v-be77961e>✓</span><div data-v-be77961e><b data-v-be77961e>Design intent:</b> a picker dialog should feel like a quiet command palette — one boxed search, calm rows, a neutral "you are here" fill, and a predictable shortcut bar. Anything noisier — badge clouds, accent-selected rows, per-dialog footer inventions — is a regression to weed out. </div></div></section>',5))])])])]))}}),Se=M(ke,[["__scopeId","data-v-be77961e"]]);export{Se as default};
|