@ashlr/hub 3.9.0 → 3.9.1

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.
Files changed (63) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/dist/build-identity.json +2 -2
  3. package/dist/core/resources/claude-account-usage.d.ts +19 -0
  4. package/dist/core/resources/claude-account-usage.js +22 -1
  5. package/dist/core/resources/claude-account-usage.js.map +1 -1
  6. package/dist/core/verse/accounts.d.ts +6 -5
  7. package/dist/core/verse/accounts.js +9 -8
  8. package/dist/core/verse/accounts.js.map +1 -1
  9. package/dist/core/web/public/next/assets/{App-XURmiAAp.js → App-D2P1AJiD.js} +2 -2
  10. package/dist/core/web/public/next/assets/{App-XURmiAAp.js.map → App-D2P1AJiD.js.map} +1 -1
  11. package/dist/core/web/public/next/assets/{ApprovalsSection-BriUzoZc.js → ApprovalsSection-Dq66NMfx.js} +2 -2
  12. package/dist/core/web/public/next/assets/{ApprovalsSection-BriUzoZc.js.map → ApprovalsSection-Dq66NMfx.js.map} +1 -1
  13. package/dist/core/web/public/next/assets/{AutonomySection-C4GBsQVc.js → AutonomySection-DyxpeR5I.js} +2 -2
  14. package/dist/core/web/public/next/assets/{AutonomySection-C4GBsQVc.js.map → AutonomySection-DyxpeR5I.js.map} +1 -1
  15. package/dist/core/web/public/next/assets/{ChatSection-hlgPBRco.js → ChatSection-bzwFdbdw.js} +2 -2
  16. package/dist/core/web/public/next/assets/{ChatSection-hlgPBRco.js.map → ChatSection-bzwFdbdw.js.map} +1 -1
  17. package/dist/core/web/public/next/assets/{ConfirmDialog-BdQYI-Ng.js → ConfirmDialog-DkR0z5_a.js} +2 -2
  18. package/dist/core/web/public/next/assets/{ConfirmDialog-BdQYI-Ng.js.map → ConfirmDialog-DkR0z5_a.js.map} +1 -1
  19. package/dist/core/web/public/next/assets/{Dialog-BxdIfmFH.js → Dialog-CIjlQZqc.js} +2 -2
  20. package/dist/core/web/public/next/assets/{Dialog-BxdIfmFH.js.map → Dialog-CIjlQZqc.js.map} +1 -1
  21. package/dist/core/web/public/next/assets/{DiffViewer-DmeRWuc4.js → DiffViewer-BjQZAm5L.js} +2 -2
  22. package/dist/core/web/public/next/assets/{DiffViewer-DmeRWuc4.js.map → DiffViewer-BjQZAm5L.js.map} +1 -1
  23. package/dist/core/web/public/next/assets/{McpSection-q34T7Tho.js → McpSection-D2FySMy3.js} +2 -2
  24. package/dist/core/web/public/next/assets/{McpSection-q34T7Tho.js.map → McpSection-D2FySMy3.js.map} +1 -1
  25. package/dist/core/web/public/next/assets/{MutationTokenDialog-CFAkwOEt.js → MutationTokenDialog-QBPrD1UE.js} +2 -2
  26. package/dist/core/web/public/next/assets/{MutationTokenDialog-CFAkwOEt.js.map → MutationTokenDialog-QBPrD1UE.js.map} +1 -1
  27. package/dist/core/web/public/next/assets/{ResourcePoolConsoleApp-DHc8GFOY.js → ResourcePoolConsoleApp-Dzq9Ufq_.js} +2 -2
  28. package/dist/core/web/public/next/assets/{ResourcePoolConsoleApp-DHc8GFOY.js.map → ResourcePoolConsoleApp-Dzq9Ufq_.js.map} +1 -1
  29. package/dist/core/web/public/next/assets/{RouteErrorBoundary-ZL9F4qUn.js → RouteErrorBoundary-Chd-wt6w.js} +2 -2
  30. package/dist/core/web/public/next/assets/{RouteErrorBoundary-ZL9F4qUn.js.map → RouteErrorBoundary-Chd-wt6w.js.map} +1 -1
  31. package/dist/core/web/public/next/assets/{Segmented-DbRjVye3.js → Segmented-DhGh7Y1s.js} +2 -2
  32. package/dist/core/web/public/next/assets/{Segmented-DbRjVye3.js.map → Segmented-DhGh7Y1s.js.map} +1 -1
  33. package/dist/core/web/public/next/assets/{SettingsSection-nJGGGYqv.js → SettingsSection-Bqimu_Mg.js} +2 -2
  34. package/dist/core/web/public/next/assets/{SettingsSection-nJGGGYqv.js.map → SettingsSection-Bqimu_Mg.js.map} +1 -1
  35. package/dist/core/web/public/next/assets/{UniverseConsoleApp-6GJydgo4.js → UniverseConsoleApp-DzAyrE-A.js} +2 -2
  36. package/dist/core/web/public/next/assets/{UniverseConsoleApp-6GJydgo4.js.map → UniverseConsoleApp-DzAyrE-A.js.map} +1 -1
  37. package/dist/core/web/public/next/assets/{UniverseView-DLDtNYL8.js → UniverseView-BGl3DRV7.js} +2 -2
  38. package/dist/core/web/public/next/assets/{UniverseView-DLDtNYL8.js.map → UniverseView-BGl3DRV7.js.map} +1 -1
  39. package/dist/core/web/public/next/assets/{UsageSection-Cf9d9iFH.js → UsageSection-PaaJ1L90.js} +2 -2
  40. package/dist/core/web/public/next/assets/{UsageSection-Cf9d9iFH.js.map → UsageSection-PaaJ1L90.js.map} +1 -1
  41. package/dist/core/web/public/next/assets/{VerseConsoleApp-y0uOHixd.js → VerseConsoleApp-DYH7ottg.js} +3 -3
  42. package/dist/core/web/public/next/assets/{VerseConsoleApp-y0uOHixd.js.map → VerseConsoleApp-DYH7ottg.js.map} +1 -1
  43. package/dist/core/web/public/next/assets/{charts-CVooqJWN.js → charts-CnMDT08S.js} +2 -2
  44. package/dist/core/web/public/next/assets/{charts-CVooqJWN.js.map → charts-CnMDT08S.js.map} +1 -1
  45. package/dist/core/web/public/next/assets/{context-model-Bqv8DVuH.js → context-model-DTvb2uZy.js} +2 -2
  46. package/dist/core/web/public/next/assets/{context-model-Bqv8DVuH.js.map → context-model-DTvb2uZy.js.map} +1 -1
  47. package/dist/core/web/public/next/assets/{global-eaY9GyX3.js → global-F2K14nNo.js} +2 -2
  48. package/dist/core/web/public/next/assets/{global-eaY9GyX3.js.map → global-F2K14nNo.js.map} +1 -1
  49. package/dist/core/web/public/next/assets/{icons-vex8rclC.js → icons-B2fa40k6.js} +2 -2
  50. package/dist/core/web/public/next/assets/{icons-vex8rclC.js.map → icons-B2fa40k6.js.map} +1 -1
  51. package/dist/core/web/public/next/assets/{index-D2Eik-zt.js → index-DO9a5IaB.js} +3 -3
  52. package/dist/core/web/public/next/assets/{index-D2Eik-zt.js.map → index-DO9a5IaB.js.map} +1 -1
  53. package/dist/core/web/public/next/assets/{queries-bAYU0VNy.js → queries-BdSsWYSu.js} +2 -2
  54. package/dist/core/web/public/next/assets/{queries-bAYU0VNy.js.map → queries-BdSsWYSu.js.map} +1 -1
  55. package/dist/core/web/public/next/assets/{use-guarded-action-C7QrP7Yp.js → use-guarded-action-BrlIU0jJ.js} +2 -2
  56. package/dist/core/web/public/next/assets/{use-guarded-action-C7QrP7Yp.js.map → use-guarded-action-BrlIU0jJ.js.map} +1 -1
  57. package/dist/core/web/public/next/assets/{verse-model-TfDk9cky.js → verse-model-GWqHIjMG.js} +2 -2
  58. package/dist/core/web/public/next/assets/{verse-model-TfDk9cky.js.map → verse-model-GWqHIjMG.js.map} +1 -1
  59. package/dist/core/web/public/next/assets/{workspace-model-CHol5RVp.js → workspace-model-4yzB--1h.js} +2 -2
  60. package/dist/core/web/public/next/assets/{workspace-model-CHol5RVp.js.map → workspace-model-4yzB--1h.js.map} +1 -1
  61. package/dist/core/web/public/next/index.html +1 -1
  62. package/dist/release-dependency-inventory.json +1 -1
  63. package/package.json +1 -1
@@ -1,2 +1,2 @@
1
- import{a as e,n as t,o as n}from"./index-D2Eik-zt.js";var r={unknown:`_unknown_4gf0c_1`,dot:`_dot_4gf0c_11`,badge:`_badge_4gf0c_19`},i=t();function a(e){return!e||e.sourceState===`healthy`&&e.complete!==!1}function o({quality:e,children:t,renderControl:n,label:o}){if(a(e))return(0,i.jsxs)(i.Fragment,{children:[t,n?.()]});let s=e?.reason;return(0,i.jsxs)(`span`,{className:r.unknown,title:s?`Unavailable: ${s}`:`Source data unavailable`,children:[(0,i.jsx)(`span`,{className:r.dot,"aria-hidden":`true`}),`unknown`,o?(0,i.jsxs)(`span`,{className:`visually-hidden`,children:[` (`,o,`)`]}):null]})}function s({quality:e,label:t}){return a(e)?null:(0,i.jsxs)(`span`,{className:r.badge,title:e?.reason??`Source data unavailable`,children:[t?`${t}: `:``,`unknown`]})}var c=n(e(),1),l={region:`_region_1818x_1`,toast:`_toast_1818x_12`,"slide-in":`_slide-in_1818x_1`,success:`_success_1818x_27`,danger:`_danger_1818x_30`,dismiss:`_dismiss_1818x_34`},u=(0,c.createContext)(null);function d(){let e=(0,c.useContext)(u);if(!e)throw Error(`useToast() called outside <ToastProvider>`);return e}var f=5e3;function p({children:e}){let[t,n]=(0,c.useState)([]),r=(0,c.useRef)(0),a=(0,c.useCallback)(e=>{n(t=>t.filter(t=>t.id!==e))},[]),o=(0,c.useCallback)((e,t=`neutral`)=>{let i=r.current++;n(n=>[...n,{id:i,message:e,tone:t}]),setTimeout(()=>a(i),f)},[a]),s=(0,c.useMemo)(()=>({show:o}),[o]);return(0,i.jsxs)(u.Provider,{value:s,children:[e,(0,i.jsx)(`div`,{className:l.region,role:`status`,"aria-live":`polite`,children:t.map(e=>(0,i.jsxs)(`div`,{className:`${l.toast} ${l[e.tone]}`,children:[(0,i.jsx)(`span`,{children:e.message}),(0,i.jsx)(`button`,{type:`button`,className:l.dismiss,onClick:()=>a(e.id),"aria-label":`Dismiss`,children:`×`})]},e.id))})]})}var m={wrap:`_wrap_1759l_1`,title:`_title_1759l_14`,detail:`_detail_1759l_21`,retry:`_retry_1759l_27`},h=class extends c.Component{state={error:null};static getDerivedStateFromError(e){return{error:e}}componentDidCatch(e,t){console.error(`[ashlr] view crashed while rendering`,e,t.componentStack)}componentDidUpdate(e){this.state.error&&e.resetKey!==this.props.resetKey&&this.setState({error:null})}render(){return this.state.error?(0,i.jsxs)(`div`,{className:m.wrap,role:`alert`,children:[(0,i.jsx)(`p`,{className:m.title,children:`This view hit an error and couldn't render.`}),(0,i.jsxs)(`p`,{className:m.detail,children:[this.state.error.message||`Unknown error.`,` — the rest of ashlr is still working; pick another view from the sidebar, or retry this one.`]}),(0,i.jsx)(`button`,{type:`button`,className:m.retry,onClick:()=>this.setState({error:null}),children:`Retry this view`})]}):this.props.children}};export{s as a,o as i,p as n,a as o,d as r,h as t};
2
- //# sourceMappingURL=RouteErrorBoundary-ZL9F4qUn.js.map
1
+ import{a as e,n as t,o as n}from"./index-DO9a5IaB.js";var r={unknown:`_unknown_4gf0c_1`,dot:`_dot_4gf0c_11`,badge:`_badge_4gf0c_19`},i=t();function a(e){return!e||e.sourceState===`healthy`&&e.complete!==!1}function o({quality:e,children:t,renderControl:n,label:o}){if(a(e))return(0,i.jsxs)(i.Fragment,{children:[t,n?.()]});let s=e?.reason;return(0,i.jsxs)(`span`,{className:r.unknown,title:s?`Unavailable: ${s}`:`Source data unavailable`,children:[(0,i.jsx)(`span`,{className:r.dot,"aria-hidden":`true`}),`unknown`,o?(0,i.jsxs)(`span`,{className:`visually-hidden`,children:[` (`,o,`)`]}):null]})}function s({quality:e,label:t}){return a(e)?null:(0,i.jsxs)(`span`,{className:r.badge,title:e?.reason??`Source data unavailable`,children:[t?`${t}: `:``,`unknown`]})}var c=n(e(),1),l={region:`_region_1818x_1`,toast:`_toast_1818x_12`,"slide-in":`_slide-in_1818x_1`,success:`_success_1818x_27`,danger:`_danger_1818x_30`,dismiss:`_dismiss_1818x_34`},u=(0,c.createContext)(null);function d(){let e=(0,c.useContext)(u);if(!e)throw Error(`useToast() called outside <ToastProvider>`);return e}var f=5e3;function p({children:e}){let[t,n]=(0,c.useState)([]),r=(0,c.useRef)(0),a=(0,c.useCallback)(e=>{n(t=>t.filter(t=>t.id!==e))},[]),o=(0,c.useCallback)((e,t=`neutral`)=>{let i=r.current++;n(n=>[...n,{id:i,message:e,tone:t}]),setTimeout(()=>a(i),f)},[a]),s=(0,c.useMemo)(()=>({show:o}),[o]);return(0,i.jsxs)(u.Provider,{value:s,children:[e,(0,i.jsx)(`div`,{className:l.region,role:`status`,"aria-live":`polite`,children:t.map(e=>(0,i.jsxs)(`div`,{className:`${l.toast} ${l[e.tone]}`,children:[(0,i.jsx)(`span`,{children:e.message}),(0,i.jsx)(`button`,{type:`button`,className:l.dismiss,onClick:()=>a(e.id),"aria-label":`Dismiss`,children:`×`})]},e.id))})]})}var m={wrap:`_wrap_1759l_1`,title:`_title_1759l_14`,detail:`_detail_1759l_21`,retry:`_retry_1759l_27`},h=class extends c.Component{state={error:null};static getDerivedStateFromError(e){return{error:e}}componentDidCatch(e,t){console.error(`[ashlr] view crashed while rendering`,e,t.componentStack)}componentDidUpdate(e){this.state.error&&e.resetKey!==this.props.resetKey&&this.setState({error:null})}render(){return this.state.error?(0,i.jsxs)(`div`,{className:m.wrap,role:`alert`,children:[(0,i.jsx)(`p`,{className:m.title,children:`This view hit an error and couldn't render.`}),(0,i.jsxs)(`p`,{className:m.detail,children:[this.state.error.message||`Unknown error.`,` — the rest of ashlr is still working; pick another view from the sidebar, or retry this one.`]}),(0,i.jsx)(`button`,{type:`button`,className:m.retry,onClick:()=>this.setState({error:null}),children:`Retry this view`})]}):this.props.children}};export{s as a,o as i,p as n,a as o,d as r,h as t};
2
+ //# sourceMappingURL=RouteErrorBoundary-Chd-wt6w.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"RouteErrorBoundary-ZL9F4qUn.js","names":[],"sources":["../../../../../../src/web-ui/components/primitives/Epistemic.module.css","../../../../../../src/web-ui/components/primitives/Epistemic.tsx","../../../../../../src/web-ui/components/primitives/Toast.module.css","../../../../../../src/web-ui/components/primitives/Toast.tsx","../../../../../../src/web-ui/components/primitives/RouteErrorBoundary.module.css","../../../../../../src/web-ui/components/primitives/RouteErrorBoundary.tsx"],"sourcesContent":[".unknown {\n display: inline-flex;\n align-items: center;\n gap: var(--space-1);\n color: var(--status-unknown-fg);\n font-size: var(--text-sm-size);\n font-style: italic;\n cursor: help;\n}\n\n.dot {\n width: 6px;\n height: 6px;\n border-radius: var(--radius-full);\n background: var(--status-unknown-solid);\n flex-shrink: 0;\n}\n\n.badge {\n display: inline-flex;\n align-items: center;\n padding: 1px var(--space-2);\n border-radius: var(--radius-xs);\n background: var(--status-unknown-bg);\n color: var(--status-unknown-fg);\n font-size: var(--text-xs-size);\n font-style: italic;\n cursor: help;\n}\n","/**\n * components/primitives/Epistemic.tsx — the one thing the old UI got right,\n * promoted to a first-class primitive (foundation brief, item 7).\n *\n * When a piece of data's `sourceQuality.sourceState` is 'degraded' or\n * 'unknown'/'missing', this component displays \"unknown\" (never a stale or\n * guessed value dressed up as current) AND withholds any control built on\n * top of it (`renderControl` simply never runs). Every future view should\n * wrap sourceQuality-bearing fields in this rather than reinventing the\n * check — that is the whole point of making it a primitive instead of a\n * convention someone has to remember.\n *\n * Reference (old, correct behavior this replaces): app.js:311-314, :336-347.\n */\nimport type { ReactNode } from 'react';\nimport type { SourceQuality } from '../../data/api-types.js';\nimport styles from './Epistemic.module.css';\n\nexport function isKnown(quality: SourceQuality | undefined): boolean {\n if (!quality) return true; // absent quality metadata => field predates the pattern; trust it.\n return quality.sourceState === 'healthy' && quality.complete !== false;\n}\n\nexport interface EpistemicProps {\n quality: SourceQuality | undefined;\n /** Rendered when the value is known-good. */\n children: ReactNode;\n /**\n * A control (button, toggle, link) whose action depends on this data\n * being trustworthy — e.g. \"restart daemon\" next to daemon.running.\n * Omitted entirely (not disabled — omitted) when the source is degraded,\n * so an operator never sees an actionable-looking control wired to a\n * guess.\n */\n renderControl?: () => ReactNode;\n /** Optional label used in the \"unknown\" state, e.g. \"daemon status\". */\n label?: string;\n}\n\nexport function Epistemic({ quality, children, renderControl, label }: EpistemicProps): ReactNode {\n if (isKnown(quality)) {\n return (\n <>\n {children}\n {renderControl?.()}\n </>\n );\n }\n\n const reason = quality?.reason;\n return (\n <span className={styles.unknown} title={reason ? `Unavailable: ${reason}` : 'Source data unavailable'}>\n <span className={styles.dot} aria-hidden=\"true\" />\n unknown\n {label ? <span className=\"visually-hidden\"> ({label})</span> : null}\n </span>\n );\n}\n\n/** Small inline badge form, for table cells / dense rows. */\nexport function EpistemicBadge({ quality, label }: { quality: SourceQuality | undefined; label?: string }): ReactNode {\n if (isKnown(quality)) return null;\n return (\n <span className={styles.badge} title={quality?.reason ?? 'Source data unavailable'}>\n {label ? `${label}: ` : ''}unknown\n </span>\n );\n}\n",".region {\n position: fixed;\n bottom: var(--space-6);\n right: var(--space-6);\n display: flex;\n flex-direction: column;\n gap: var(--space-2);\n z-index: var(--z-toast);\n max-width: 360px;\n}\n\n.toast {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--space-3);\n padding: var(--space-3) var(--space-4);\n border-radius: var(--radius-md);\n box-shadow: var(--shadow-toast);\n font-size: var(--text-sm-size);\n animation: slide-in var(--duration-base) var(--ease-decelerate);\n border: 1px solid var(--border-default);\n background: var(--bg-surface-raised);\n color: var(--text-primary);\n}\n\n.success {\n border-color: var(--status-success-solid);\n}\n.danger {\n border-color: var(--status-danger-solid);\n}\n\n.dismiss {\n color: var(--text-tertiary);\n border-radius: var(--radius-xs);\n font-size: var(--text-lg-size);\n line-height: 1;\n padding: var(--space-1);\n}\n.dismiss {\n transition: color var(--duration-fast) var(--ease-standard), background var(--duration-fast) var(--ease-standard);\n}\n.dismiss:hover {\n color: var(--text-primary);\n background: var(--bg-hover);\n}\n.dismiss:active {\n background: var(--bg-active);\n}\n\n@keyframes slide-in {\n from {\n opacity: 0;\n transform: translateY(8px);\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n :root:not([data-motion=\"full\"]) .toast {\n animation: none;\n }\n}\n\n:root[data-motion=\"reduce\"] .toast {\n animation: none;\n}\n","/**\n * components/primitives/Toast.tsx — app-wide notifications. A single\n * aria-live region so screen readers announce toasts once, not per-toast\n * duplicate regions.\n */\nimport { createContext, useCallback, useContext, useMemo, useRef, useState, type ReactNode } from 'react';\nimport styles from './Toast.module.css';\n\nexport type ToastTone = 'neutral' | 'success' | 'danger';\n\ninterface ToastItem {\n id: number;\n message: string;\n tone: ToastTone;\n}\n\ninterface ToastContextValue {\n show: (message: string, tone?: ToastTone) => void;\n}\n\nconst ToastContext = createContext<ToastContextValue | null>(null);\n\nexport function useToast(): ToastContextValue {\n const ctx = useContext(ToastContext);\n if (!ctx) throw new Error('useToast() called outside <ToastProvider>');\n return ctx;\n}\n\nconst AUTO_DISMISS_MS = 5000;\n\nexport function ToastProvider({ children }: { children: ReactNode }) {\n const [toasts, setToasts] = useState<ToastItem[]>([]);\n const nextId = useRef(0);\n\n const dismiss = useCallback((id: number) => {\n setToasts((prev) => prev.filter((t) => t.id !== id));\n }, []);\n\n const show = useCallback(\n (message: string, tone: ToastTone = 'neutral') => {\n const id = nextId.current++;\n setToasts((prev) => [...prev, { id, message, tone }]);\n setTimeout(() => dismiss(id), AUTO_DISMISS_MS);\n },\n [dismiss],\n );\n\n const value = useMemo(() => ({ show }), [show]);\n\n return (\n <ToastContext.Provider value={value}>\n {children}\n <div className={styles.region} role=\"status\" aria-live=\"polite\">\n {toasts.map((t) => (\n <div key={t.id} className={`${styles.toast} ${styles[t.tone]}`}>\n <span>{t.message}</span>\n <button type=\"button\" className={styles.dismiss} onClick={() => dismiss(t.id)} aria-label=\"Dismiss\">\n ×\n </button>\n </div>\n ))}\n </div>\n </ToastContext.Provider>\n );\n}\n",".wrap {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: var(--space-3);\n max-width: 640px;\n margin: var(--space-8) auto;\n padding: var(--space-6);\n border: 1px solid var(--status-danger-solid);\n border-radius: var(--radius-lg);\n background: var(--status-danger-bg);\n}\n\n.title {\n font-size: var(--text-md-size);\n line-height: var(--text-md-line);\n font-weight: var(--font-weight-semibold);\n color: var(--status-danger-fg);\n}\n\n.detail {\n font-size: var(--text-sm-size);\n line-height: var(--text-sm-line);\n color: var(--text-secondary);\n}\n\n.retry {\n padding: var(--space-2) var(--space-4);\n border: 1px solid var(--status-danger-solid);\n border-radius: var(--radius-sm);\n background: var(--bg-surface);\n color: var(--status-danger-fg);\n font-size: var(--text-sm-size);\n font-weight: var(--font-weight-medium);\n cursor: pointer;\n}\n\n.retry:hover {\n background: var(--bg-hover);\n}\n","/**\n * components/primitives/RouteErrorBoundary.tsx — the last line of defense\n * against a view crashing on real-world data that doesn't match its\n * expected shape (see SwarmsView.tsx's `plan?.tasks` guard for a concrete\n * example: 2 of 80 real swarm records on this machine predate the `plan`\n * field entirely).\n *\n * React error boundaries can only be class components — there is no hook\n * equivalent. Wraps `<Outlet/>` in Shell.tsx, not the whole app: if one\n * routed view throws during render, the sidebar/topbar/command palette\n * survive and the operator can navigate to a working view instead of being\n * stuck on a permanently blank screen (DESIGN.md §11 point 4 — \"never a\n * full-page error, the shell stays usable\" — extended here to cover\n * uncaught render exceptions, not just query-status `'error'`).\n */\nimport { Component, type ErrorInfo, type ReactNode } from 'react';\nimport styles from './RouteErrorBoundary.module.css';\n\ninterface Props {\n children: ReactNode;\n /** Identity that, when changed, resets the boundary — pass the route pathname. */\n resetKey: string;\n}\n\ninterface State {\n error: Error | null;\n}\n\nexport class RouteErrorBoundary extends Component<Props, State> {\n override state: State = { error: null };\n\n static getDerivedStateFromError(error: Error): State {\n return { error };\n }\n\n override componentDidCatch(error: Error, info: ErrorInfo) {\n console.error('[ashlr] view crashed while rendering', error, info.componentStack);\n }\n\n override componentDidUpdate(prevProps: Props) {\n if (this.state.error && prevProps.resetKey !== this.props.resetKey) {\n this.setState({ error: null });\n }\n }\n\n override render() {\n if (this.state.error) {\n return (\n <div className={styles.wrap} role=\"alert\">\n <p className={styles.title}>This view hit an error and couldn&apos;t render.</p>\n <p className={styles.detail}>\n {this.state.error.message || 'Unknown error.'} — the rest of ashlr is still working; pick another view\n from the sidebar, or retry this one.\n </p>\n <button type=\"button\" className={styles.retry} onClick={() => this.setState({ error: null })}>\n Retry this view\n </button>\n </div>\n );\n }\n return this.props.children;\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"RouteErrorBoundary-Chd-wt6w.js","names":[],"sources":["../../../../../../src/web-ui/components/primitives/Epistemic.module.css","../../../../../../src/web-ui/components/primitives/Epistemic.tsx","../../../../../../src/web-ui/components/primitives/Toast.module.css","../../../../../../src/web-ui/components/primitives/Toast.tsx","../../../../../../src/web-ui/components/primitives/RouteErrorBoundary.module.css","../../../../../../src/web-ui/components/primitives/RouteErrorBoundary.tsx"],"sourcesContent":[".unknown {\n display: inline-flex;\n align-items: center;\n gap: var(--space-1);\n color: var(--status-unknown-fg);\n font-size: var(--text-sm-size);\n font-style: italic;\n cursor: help;\n}\n\n.dot {\n width: 6px;\n height: 6px;\n border-radius: var(--radius-full);\n background: var(--status-unknown-solid);\n flex-shrink: 0;\n}\n\n.badge {\n display: inline-flex;\n align-items: center;\n padding: 1px var(--space-2);\n border-radius: var(--radius-xs);\n background: var(--status-unknown-bg);\n color: var(--status-unknown-fg);\n font-size: var(--text-xs-size);\n font-style: italic;\n cursor: help;\n}\n","/**\n * components/primitives/Epistemic.tsx — the one thing the old UI got right,\n * promoted to a first-class primitive (foundation brief, item 7).\n *\n * When a piece of data's `sourceQuality.sourceState` is 'degraded' or\n * 'unknown'/'missing', this component displays \"unknown\" (never a stale or\n * guessed value dressed up as current) AND withholds any control built on\n * top of it (`renderControl` simply never runs). Every future view should\n * wrap sourceQuality-bearing fields in this rather than reinventing the\n * check — that is the whole point of making it a primitive instead of a\n * convention someone has to remember.\n *\n * Reference (old, correct behavior this replaces): app.js:311-314, :336-347.\n */\nimport type { ReactNode } from 'react';\nimport type { SourceQuality } from '../../data/api-types.js';\nimport styles from './Epistemic.module.css';\n\nexport function isKnown(quality: SourceQuality | undefined): boolean {\n if (!quality) return true; // absent quality metadata => field predates the pattern; trust it.\n return quality.sourceState === 'healthy' && quality.complete !== false;\n}\n\nexport interface EpistemicProps {\n quality: SourceQuality | undefined;\n /** Rendered when the value is known-good. */\n children: ReactNode;\n /**\n * A control (button, toggle, link) whose action depends on this data\n * being trustworthy — e.g. \"restart daemon\" next to daemon.running.\n * Omitted entirely (not disabled — omitted) when the source is degraded,\n * so an operator never sees an actionable-looking control wired to a\n * guess.\n */\n renderControl?: () => ReactNode;\n /** Optional label used in the \"unknown\" state, e.g. \"daemon status\". */\n label?: string;\n}\n\nexport function Epistemic({ quality, children, renderControl, label }: EpistemicProps): ReactNode {\n if (isKnown(quality)) {\n return (\n <>\n {children}\n {renderControl?.()}\n </>\n );\n }\n\n const reason = quality?.reason;\n return (\n <span className={styles.unknown} title={reason ? `Unavailable: ${reason}` : 'Source data unavailable'}>\n <span className={styles.dot} aria-hidden=\"true\" />\n unknown\n {label ? <span className=\"visually-hidden\"> ({label})</span> : null}\n </span>\n );\n}\n\n/** Small inline badge form, for table cells / dense rows. */\nexport function EpistemicBadge({ quality, label }: { quality: SourceQuality | undefined; label?: string }): ReactNode {\n if (isKnown(quality)) return null;\n return (\n <span className={styles.badge} title={quality?.reason ?? 'Source data unavailable'}>\n {label ? `${label}: ` : ''}unknown\n </span>\n );\n}\n",".region {\n position: fixed;\n bottom: var(--space-6);\n right: var(--space-6);\n display: flex;\n flex-direction: column;\n gap: var(--space-2);\n z-index: var(--z-toast);\n max-width: 360px;\n}\n\n.toast {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--space-3);\n padding: var(--space-3) var(--space-4);\n border-radius: var(--radius-md);\n box-shadow: var(--shadow-toast);\n font-size: var(--text-sm-size);\n animation: slide-in var(--duration-base) var(--ease-decelerate);\n border: 1px solid var(--border-default);\n background: var(--bg-surface-raised);\n color: var(--text-primary);\n}\n\n.success {\n border-color: var(--status-success-solid);\n}\n.danger {\n border-color: var(--status-danger-solid);\n}\n\n.dismiss {\n color: var(--text-tertiary);\n border-radius: var(--radius-xs);\n font-size: var(--text-lg-size);\n line-height: 1;\n padding: var(--space-1);\n}\n.dismiss {\n transition: color var(--duration-fast) var(--ease-standard), background var(--duration-fast) var(--ease-standard);\n}\n.dismiss:hover {\n color: var(--text-primary);\n background: var(--bg-hover);\n}\n.dismiss:active {\n background: var(--bg-active);\n}\n\n@keyframes slide-in {\n from {\n opacity: 0;\n transform: translateY(8px);\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n :root:not([data-motion=\"full\"]) .toast {\n animation: none;\n }\n}\n\n:root[data-motion=\"reduce\"] .toast {\n animation: none;\n}\n","/**\n * components/primitives/Toast.tsx — app-wide notifications. A single\n * aria-live region so screen readers announce toasts once, not per-toast\n * duplicate regions.\n */\nimport { createContext, useCallback, useContext, useMemo, useRef, useState, type ReactNode } from 'react';\nimport styles from './Toast.module.css';\n\nexport type ToastTone = 'neutral' | 'success' | 'danger';\n\ninterface ToastItem {\n id: number;\n message: string;\n tone: ToastTone;\n}\n\ninterface ToastContextValue {\n show: (message: string, tone?: ToastTone) => void;\n}\n\nconst ToastContext = createContext<ToastContextValue | null>(null);\n\nexport function useToast(): ToastContextValue {\n const ctx = useContext(ToastContext);\n if (!ctx) throw new Error('useToast() called outside <ToastProvider>');\n return ctx;\n}\n\nconst AUTO_DISMISS_MS = 5000;\n\nexport function ToastProvider({ children }: { children: ReactNode }) {\n const [toasts, setToasts] = useState<ToastItem[]>([]);\n const nextId = useRef(0);\n\n const dismiss = useCallback((id: number) => {\n setToasts((prev) => prev.filter((t) => t.id !== id));\n }, []);\n\n const show = useCallback(\n (message: string, tone: ToastTone = 'neutral') => {\n const id = nextId.current++;\n setToasts((prev) => [...prev, { id, message, tone }]);\n setTimeout(() => dismiss(id), AUTO_DISMISS_MS);\n },\n [dismiss],\n );\n\n const value = useMemo(() => ({ show }), [show]);\n\n return (\n <ToastContext.Provider value={value}>\n {children}\n <div className={styles.region} role=\"status\" aria-live=\"polite\">\n {toasts.map((t) => (\n <div key={t.id} className={`${styles.toast} ${styles[t.tone]}`}>\n <span>{t.message}</span>\n <button type=\"button\" className={styles.dismiss} onClick={() => dismiss(t.id)} aria-label=\"Dismiss\">\n ×\n </button>\n </div>\n ))}\n </div>\n </ToastContext.Provider>\n );\n}\n",".wrap {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: var(--space-3);\n max-width: 640px;\n margin: var(--space-8) auto;\n padding: var(--space-6);\n border: 1px solid var(--status-danger-solid);\n border-radius: var(--radius-lg);\n background: var(--status-danger-bg);\n}\n\n.title {\n font-size: var(--text-md-size);\n line-height: var(--text-md-line);\n font-weight: var(--font-weight-semibold);\n color: var(--status-danger-fg);\n}\n\n.detail {\n font-size: var(--text-sm-size);\n line-height: var(--text-sm-line);\n color: var(--text-secondary);\n}\n\n.retry {\n padding: var(--space-2) var(--space-4);\n border: 1px solid var(--status-danger-solid);\n border-radius: var(--radius-sm);\n background: var(--bg-surface);\n color: var(--status-danger-fg);\n font-size: var(--text-sm-size);\n font-weight: var(--font-weight-medium);\n cursor: pointer;\n}\n\n.retry:hover {\n background: var(--bg-hover);\n}\n","/**\n * components/primitives/RouteErrorBoundary.tsx — the last line of defense\n * against a view crashing on real-world data that doesn't match its\n * expected shape (see SwarmsView.tsx's `plan?.tasks` guard for a concrete\n * example: 2 of 80 real swarm records on this machine predate the `plan`\n * field entirely).\n *\n * React error boundaries can only be class components — there is no hook\n * equivalent. Wraps `<Outlet/>` in Shell.tsx, not the whole app: if one\n * routed view throws during render, the sidebar/topbar/command palette\n * survive and the operator can navigate to a working view instead of being\n * stuck on a permanently blank screen (DESIGN.md §11 point 4 — \"never a\n * full-page error, the shell stays usable\" — extended here to cover\n * uncaught render exceptions, not just query-status `'error'`).\n */\nimport { Component, type ErrorInfo, type ReactNode } from 'react';\nimport styles from './RouteErrorBoundary.module.css';\n\ninterface Props {\n children: ReactNode;\n /** Identity that, when changed, resets the boundary — pass the route pathname. */\n resetKey: string;\n}\n\ninterface State {\n error: Error | null;\n}\n\nexport class RouteErrorBoundary extends Component<Props, State> {\n override state: State = { error: null };\n\n static getDerivedStateFromError(error: Error): State {\n return { error };\n }\n\n override componentDidCatch(error: Error, info: ErrorInfo) {\n console.error('[ashlr] view crashed while rendering', error, info.componentStack);\n }\n\n override componentDidUpdate(prevProps: Props) {\n if (this.state.error && prevProps.resetKey !== this.props.resetKey) {\n this.setState({ error: null });\n }\n }\n\n override render() {\n if (this.state.error) {\n return (\n <div className={styles.wrap} role=\"alert\">\n <p className={styles.title}>This view hit an error and couldn&apos;t render.</p>\n <p className={styles.detail}>\n {this.state.error.message || 'Unknown error.'} — the rest of ashlr is still working; pick another view\n from the sidebar, or retry this one.\n </p>\n <button type=\"button\" className={styles.retry} onClick={() => this.setState({ error: null })}>\n Retry this view\n </button>\n </div>\n );\n }\n return this.props.children;\n }\n}\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- import{a as e,i as t,n,o as r}from"./index-D2Eik-zt.js";var i=r(e(),1),a=r(t(),1),o={wrapper:`_wrapper_6cgsw_4`,bubble:`_bubble_6cgsw_12`,"tooltip-in":`_tooltip-in_6cgsw_1`,label:`_label_6cgsw_44`,shortcut:`_shortcut_6cgsw_49`},s=n(),c=160,l=8,u=8;function d(e){let t={top:`bottom`,bottom:`top`,left:`right`,right:`left`},n=[`top`,`bottom`,`right`,`left`].filter(n=>n!==e&&n!==t[e]);return[e,t[e],...n]}function f(e,t,n,r){switch(e){case`top`:return{left:t.left+t.width/2-n/2,top:t.top-r-l};case`bottom`:return{left:t.left+t.width/2-n/2,top:t.bottom+l};case`left`:return{left:t.left-n-l,top:t.top+t.height/2-r/2};case`right`:return{left:t.right+l,top:t.top+t.height/2-r/2}}}function p(e,t,n,r){let i=window.innerWidth||0,a=window.innerHeight||0,o=null;for(let s of d(r)){let{left:r,top:c}=f(s,e,t,n);if(o||={left:r,top:c,placement:s},r>=u&&c>=u&&r+t<=i-u&&c+n<=a-u)return{left:r,top:c,placement:s}}let s=o;return{placement:s.placement,left:Math.max(u,Math.min(s.left,i-t-u)),top:Math.max(u,Math.min(s.top,a-n-u))}}function m(e){let{label:t,content:n,shortcut:r,placement:l=`top`,disabled:u=!1,className:d,children:f}=e,[m,h]=(0,i.useState)(!1),[g,_]=(0,i.useState)(null),v=(0,i.useId)(),y=(0,i.useRef)(null),b=(0,i.useRef)(null),x=(0,i.useRef)(null),S=(0,i.useCallback)(()=>{x.current!==null&&(clearTimeout(x.current),x.current=null)},[]),C=(0,i.useCallback)(()=>{S(),h(!1),_(null)},[S]);(0,i.useEffect)(()=>S,[S]);let w=(0,i.useCallback)(()=>{let e=y.current,t=b.current;if(!e||!t)return;let n=e.getBoundingClientRect();_(p(n,t.offsetWidth,t.offsetHeight,l))},[l]);(0,i.useLayoutEffect)(()=>{m&&w()},[m,w]),(0,i.useEffect)(()=>{if(!m)return;function e(e){e.key===`Escape`&&C()}return document.addEventListener(`keydown`,e),window.addEventListener(`resize`,w),window.addEventListener(`scroll`,w,!0),()=>{document.removeEventListener(`keydown`,e),window.removeEventListener(`resize`,w),window.removeEventListener(`scroll`,w,!0)}},[m,C,w]);let T=t??n;if(u||T==null||T===``)return f;function E(e){y.current=e,S(),x.current=setTimeout(()=>{x.current=null,h(!0)},c)}function D(e){y.current=e,S(),h(!0)}let O=f.props.disabled===!0,k=m?v:void 0,A=(0,i.cloneElement)(f,{"aria-describedby":k,onMouseEnter:e=>{f.props.onMouseEnter?.(e),E(e.currentTarget)},onMouseLeave:e=>{f.props.onMouseLeave?.(e),C()},onFocus:e=>{f.props.onFocus?.(e),D(e.currentTarget)},onBlur:e=>{f.props.onBlur?.(e),C()}});return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(`span`,{className:`${o.wrapper} ${d??``}`,"aria-describedby":k,"data-disabled-trigger":O?`true`:void 0,onMouseOver:O?e=>D(e.currentTarget):void 0,onMouseOut:O?C:void 0,children:A}),m?(0,a.createPortal)((0,s.jsxs)(`div`,{ref:b,role:`tooltip`,id:v,className:o.bubble,"data-placement":g?.placement??l,style:{position:`fixed`,left:g?`${g.left}px`:`0px`,top:g?`${g.top}px`:`0px`,visibility:g?`visible`:`hidden`},children:[(0,s.jsx)(`span`,{className:o.label,children:T}),r?(0,s.jsx)(`kbd`,{className:o.shortcut,children:r}):null]}),document.body):null]})}var h={wrap:`_wrap_ti026_1`,lineWrap:`_lineWrap_ti026_8`,head:`_head_ti026_12`,label:`_label_ti026_19`,value:`_value_ti026_28`,unknown:`_unknown_ti026_37`,track:`_track_ti026_43`,bar:`_bar_ti026_49`,line:`_line_ti026_8`,fill:`_fill_ti026_61`,accent:`_accent_ti026_66`,neutral:`_neutral_ti026_69`,running:`_running_ti026_72`,warning:`_warning_ti026_75`,danger:`_danger_ti026_78`,engine:`_engine_ti026_81`};function g(e){return e>=1e6?`${(e/1e6).toFixed(e>=1e7?0:1)}m`:e>=1e3?`${(e/1e3).toFixed(e>=1e4?0:1)}k`:String(Math.round(e))}function _(e){return e>=90?`danger`:e>=70?`warning`:`accent`}function v({value:e,max:t,variant:n=`labelled`,label:r,valueText:i,tone:a,engineColor:o,className:c,...l}){let u=e!==null&&Number.isFinite(e)&&t!==null&&Number.isFinite(t)&&t>0,d=u?Math.min(100,Math.max(0,e/t*100)):0,f=a??(u?_(d):`neutral`),p=u?i??`${g(e)} / ${g(t)}`:i??`unknown`,m=(0,s.jsx)(`div`,{className:`${h.track} ${n===`line`?h.line:h.bar}`,role:`meter`,"aria-valuenow":u?Math.round(d):void 0,"aria-valuemin":0,"aria-valuemax":100,"aria-valuetext":u?`${Math.round(d)}%`:`unknown`,...l,children:(0,s.jsx)(`div`,{className:`${h.fill} ${h[f]}`,style:{width:`${d}%`,...f===`engine`&&o?{background:o}:{}}})});return n===`line`?(0,s.jsx)(`div`,{className:`${h.lineWrap} ${c??``}`,children:m}):(0,s.jsxs)(`div`,{className:`${h.wrap} ${c??``}`,children:[r||p?(0,s.jsxs)(`div`,{className:h.head,children:[r?(0,s.jsx)(`span`,{className:h.label,children:r}):(0,s.jsx)(`span`,{}),(0,s.jsx)(`span`,{className:`${h.value} ${u?``:h.unknown}`,children:p})]}):null,m]})}var y={group:`_group_15ia8_1`,block:`_block_15ia8_11`,option:`_option_15ia8_16`,sm:`_sm_15ia8_45`,glyph:`_glyph_15ia8_72`};function b({options:e,value:t,onChange:n,size:r=`md`,block:a=!1,className:o,...c}){let l=(0,i.useRef)(null);function u(r){let i=e.filter(e=>!e.disabled);if(i.length===0)return;let a=i[((i.findIndex(e=>e.value===t)+r)%i.length+i.length)%i.length];n(a.value),requestAnimationFrame(()=>{l.current?.querySelector(`[data-value="${a.value}"]`)?.focus()})}return(0,s.jsx)(`div`,{ref:l,role:`radiogroup`,className:`${y.group} ${y[r]} ${a?y.block:``} ${o??``}`,...c,children:e.map(e=>{let r=e.value===t;return(0,s.jsxs)(`button`,{type:`button`,role:`radio`,"aria-checked":r,"aria-label":e.ariaLabel,"data-value":e.value,tabIndex:r?0:-1,disabled:e.disabled,className:y.option,onClick:()=>n(e.value),onKeyDown:e=>{e.key===`ArrowRight`||e.key===`ArrowDown`?(e.preventDefault(),u(1)):(e.key===`ArrowLeft`||e.key===`ArrowUp`)&&(e.preventDefault(),u(-1))},children:[e.icon?(0,s.jsx)(`span`,{className:y.glyph,children:e.icon}):null,e.label]},e.value)})})}export{v as n,m as r,b as t};
2
- //# sourceMappingURL=Segmented-DbRjVye3.js.map
1
+ import{a as e,i as t,n,o as r}from"./index-DO9a5IaB.js";var i=r(e(),1),a=r(t(),1),o={wrapper:`_wrapper_6cgsw_4`,bubble:`_bubble_6cgsw_12`,"tooltip-in":`_tooltip-in_6cgsw_1`,label:`_label_6cgsw_44`,shortcut:`_shortcut_6cgsw_49`},s=n(),c=160,l=8,u=8;function d(e){let t={top:`bottom`,bottom:`top`,left:`right`,right:`left`},n=[`top`,`bottom`,`right`,`left`].filter(n=>n!==e&&n!==t[e]);return[e,t[e],...n]}function f(e,t,n,r){switch(e){case`top`:return{left:t.left+t.width/2-n/2,top:t.top-r-l};case`bottom`:return{left:t.left+t.width/2-n/2,top:t.bottom+l};case`left`:return{left:t.left-n-l,top:t.top+t.height/2-r/2};case`right`:return{left:t.right+l,top:t.top+t.height/2-r/2}}}function p(e,t,n,r){let i=window.innerWidth||0,a=window.innerHeight||0,o=null;for(let s of d(r)){let{left:r,top:c}=f(s,e,t,n);if(o||={left:r,top:c,placement:s},r>=u&&c>=u&&r+t<=i-u&&c+n<=a-u)return{left:r,top:c,placement:s}}let s=o;return{placement:s.placement,left:Math.max(u,Math.min(s.left,i-t-u)),top:Math.max(u,Math.min(s.top,a-n-u))}}function m(e){let{label:t,content:n,shortcut:r,placement:l=`top`,disabled:u=!1,className:d,children:f}=e,[m,h]=(0,i.useState)(!1),[g,_]=(0,i.useState)(null),v=(0,i.useId)(),y=(0,i.useRef)(null),b=(0,i.useRef)(null),x=(0,i.useRef)(null),S=(0,i.useCallback)(()=>{x.current!==null&&(clearTimeout(x.current),x.current=null)},[]),C=(0,i.useCallback)(()=>{S(),h(!1),_(null)},[S]);(0,i.useEffect)(()=>S,[S]);let w=(0,i.useCallback)(()=>{let e=y.current,t=b.current;if(!e||!t)return;let n=e.getBoundingClientRect();_(p(n,t.offsetWidth,t.offsetHeight,l))},[l]);(0,i.useLayoutEffect)(()=>{m&&w()},[m,w]),(0,i.useEffect)(()=>{if(!m)return;function e(e){e.key===`Escape`&&C()}return document.addEventListener(`keydown`,e),window.addEventListener(`resize`,w),window.addEventListener(`scroll`,w,!0),()=>{document.removeEventListener(`keydown`,e),window.removeEventListener(`resize`,w),window.removeEventListener(`scroll`,w,!0)}},[m,C,w]);let T=t??n;if(u||T==null||T===``)return f;function E(e){y.current=e,S(),x.current=setTimeout(()=>{x.current=null,h(!0)},c)}function D(e){y.current=e,S(),h(!0)}let O=f.props.disabled===!0,k=m?v:void 0,A=(0,i.cloneElement)(f,{"aria-describedby":k,onMouseEnter:e=>{f.props.onMouseEnter?.(e),E(e.currentTarget)},onMouseLeave:e=>{f.props.onMouseLeave?.(e),C()},onFocus:e=>{f.props.onFocus?.(e),D(e.currentTarget)},onBlur:e=>{f.props.onBlur?.(e),C()}});return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(`span`,{className:`${o.wrapper} ${d??``}`,"aria-describedby":k,"data-disabled-trigger":O?`true`:void 0,onMouseOver:O?e=>D(e.currentTarget):void 0,onMouseOut:O?C:void 0,children:A}),m?(0,a.createPortal)((0,s.jsxs)(`div`,{ref:b,role:`tooltip`,id:v,className:o.bubble,"data-placement":g?.placement??l,style:{position:`fixed`,left:g?`${g.left}px`:`0px`,top:g?`${g.top}px`:`0px`,visibility:g?`visible`:`hidden`},children:[(0,s.jsx)(`span`,{className:o.label,children:T}),r?(0,s.jsx)(`kbd`,{className:o.shortcut,children:r}):null]}),document.body):null]})}var h={wrap:`_wrap_ti026_1`,lineWrap:`_lineWrap_ti026_8`,head:`_head_ti026_12`,label:`_label_ti026_19`,value:`_value_ti026_28`,unknown:`_unknown_ti026_37`,track:`_track_ti026_43`,bar:`_bar_ti026_49`,line:`_line_ti026_8`,fill:`_fill_ti026_61`,accent:`_accent_ti026_66`,neutral:`_neutral_ti026_69`,running:`_running_ti026_72`,warning:`_warning_ti026_75`,danger:`_danger_ti026_78`,engine:`_engine_ti026_81`};function g(e){return e>=1e6?`${(e/1e6).toFixed(e>=1e7?0:1)}m`:e>=1e3?`${(e/1e3).toFixed(e>=1e4?0:1)}k`:String(Math.round(e))}function _(e){return e>=90?`danger`:e>=70?`warning`:`accent`}function v({value:e,max:t,variant:n=`labelled`,label:r,valueText:i,tone:a,engineColor:o,className:c,...l}){let u=e!==null&&Number.isFinite(e)&&t!==null&&Number.isFinite(t)&&t>0,d=u?Math.min(100,Math.max(0,e/t*100)):0,f=a??(u?_(d):`neutral`),p=u?i??`${g(e)} / ${g(t)}`:i??`unknown`,m=(0,s.jsx)(`div`,{className:`${h.track} ${n===`line`?h.line:h.bar}`,role:`meter`,"aria-valuenow":u?Math.round(d):void 0,"aria-valuemin":0,"aria-valuemax":100,"aria-valuetext":u?`${Math.round(d)}%`:`unknown`,...l,children:(0,s.jsx)(`div`,{className:`${h.fill} ${h[f]}`,style:{width:`${d}%`,...f===`engine`&&o?{background:o}:{}}})});return n===`line`?(0,s.jsx)(`div`,{className:`${h.lineWrap} ${c??``}`,children:m}):(0,s.jsxs)(`div`,{className:`${h.wrap} ${c??``}`,children:[r||p?(0,s.jsxs)(`div`,{className:h.head,children:[r?(0,s.jsx)(`span`,{className:h.label,children:r}):(0,s.jsx)(`span`,{}),(0,s.jsx)(`span`,{className:`${h.value} ${u?``:h.unknown}`,children:p})]}):null,m]})}var y={group:`_group_15ia8_1`,block:`_block_15ia8_11`,option:`_option_15ia8_16`,sm:`_sm_15ia8_45`,glyph:`_glyph_15ia8_72`};function b({options:e,value:t,onChange:n,size:r=`md`,block:a=!1,className:o,...c}){let l=(0,i.useRef)(null);function u(r){let i=e.filter(e=>!e.disabled);if(i.length===0)return;let a=i[((i.findIndex(e=>e.value===t)+r)%i.length+i.length)%i.length];n(a.value),requestAnimationFrame(()=>{l.current?.querySelector(`[data-value="${a.value}"]`)?.focus()})}return(0,s.jsx)(`div`,{ref:l,role:`radiogroup`,className:`${y.group} ${y[r]} ${a?y.block:``} ${o??``}`,...c,children:e.map(e=>{let r=e.value===t;return(0,s.jsxs)(`button`,{type:`button`,role:`radio`,"aria-checked":r,"aria-label":e.ariaLabel,"data-value":e.value,tabIndex:r?0:-1,disabled:e.disabled,className:y.option,onClick:()=>n(e.value),onKeyDown:e=>{e.key===`ArrowRight`||e.key===`ArrowDown`?(e.preventDefault(),u(1)):(e.key===`ArrowLeft`||e.key===`ArrowUp`)&&(e.preventDefault(),u(-1))},children:[e.icon?(0,s.jsx)(`span`,{className:y.glyph,children:e.icon}):null,e.label]},e.value)})})}export{v as n,m as r,b as t};
2
+ //# sourceMappingURL=Segmented-DhGh7Y1s.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"Segmented-DbRjVye3.js","names":[],"sources":["../../../../../../src/web-ui/components/primitives/Tooltip.module.css","../../../../../../src/web-ui/components/primitives/Tooltip.tsx","../../../../../../src/web-ui/components/primitives/Meter.module.css","../../../../../../src/web-ui/components/primitives/Meter.tsx","../../../../../../src/web-ui/components/primitives/Segmented.module.css","../../../../../../src/web-ui/components/primitives/Segmented.tsx"],"sourcesContent":["/* The trigger's stand-in in the tree: present in the DOM so the describedby\n relationship has somewhere to sit, but generating NO box, so a trigger in a\n flex or grid track keeps its own placement exactly as it had it. */\n.wrapper {\n display: contents;\n}\n\n/* Fixed, not absolute, and portalled to <body>: the bubble's coordinates come\n from the trigger's viewport rect, so no ancestor's overflow, transform or\n stacking context can clip it. That clipping is the exact failure of the\n native `title=` tooltip this replaces. */\n.bubble {\n position: fixed;\n /* Above every other layer in the scale, toasts included: a tooltip can\n describe a control inside a dialog, a sheet or the command palette, and\n a bubble drawn behind the thing it explains is the same bug as one that\n is clipped. There is no --z-tooltip token yet; this stays one step above\n the top of the scale rather than inventing a number. */\n z-index: calc(var(--z-toast) + 1);\n display: flex;\n align-items: center;\n gap: var(--space-2);\n max-width: 260px;\n padding: var(--space-1) var(--space-2);\n border: 1px solid var(--border-default);\n border-radius: var(--radius-xs);\n background: var(--bg-surface-raised);\n color: var(--text-primary);\n box-shadow: var(--shadow-menu);\n font-family: var(--font-ui);\n font-size: var(--text-xs-size);\n line-height: var(--text-xs-line);\n font-weight: var(--font-weight-regular);\n /* Was `nowrap`, which defeated the max-width above: a long tooltip ran off\n the viewport instead of wrapping inside 260px. */\n white-space: normal;\n text-wrap: pretty;\n /* It follows the pointer's target, it is never a pointer target itself —\n otherwise a bubble drawn under the cursor triggers its own mouseleave. */\n pointer-events: none;\n animation: tooltip-in var(--duration-fast) var(--ease-standard);\n}\n\n.label {\n min-width: 0;\n}\n\n/* The shortcut reads as a key, not as more sentence. */\n.shortcut {\n flex: none;\n padding: 0 4px;\n border: 1px solid var(--border-subtle);\n border-radius: var(--radius-xs);\n background: var(--bg-code);\n color: var(--text-secondary);\n font-family: var(--font-mono);\n font-size: var(--text-2xs-size, 11px);\n line-height: 1.5;\n white-space: nowrap;\n}\n\n@keyframes tooltip-in {\n from {\n opacity: 0;\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n :root:not([data-motion=\"full\"]) .bubble {\n animation: none;\n }\n}\n\n:root[data-motion=\"reduce\"] .bubble {\n animation: none;\n}\n\n/* A disabled trigger emits no pointer events, so the wrapper has to become a\n real box to catch the hover. Only then — `display: contents` is the default\n precisely so the wrapper never disturbs the trigger's flex/grid placement. */\n.wrapper[data-disabled-trigger='true'] {\n display: inline-flex;\n}\n","/**\n * components/primitives/Tooltip.tsx — the label for an icon-only control and\n * the numbers behind a 2px meter line.\n *\n * Rules it enforces so a tooltip never becomes the only way to read the UI:\n * - it shows on HOVER *and* on keyboard FOCUS (a mouse-only tooltip is\n * invisible to keyboard operators);\n * - Escape dismisses it while the trigger keeps focus (WCAG 1.4.13);\n * - the trigger is wired with aria-describedby, so the content is\n * announced rather than merely drawn;\n * - it carries description, never the accessible NAME — icon buttons still\n * need their own aria-label.\n *\n * WHY IT RENDERS THROUGH A PORTAL. This used to be an absolutely-positioned\n * span inside a `position: relative` wrapper, which is correct right up until\n * the trigger lives inside something that clips — and the first real consumer,\n * the Verse rail, is exactly that: a 56px flex column inside a grid shell with\n * its own stacking context. A bubble anchored there is cut off at the rail's\n * edge, which is the failure the native `title=` tooltip already had. Rendering\n * into `document.body` at `position: fixed`, positioned from the trigger's\n * bounding rect, means NO ancestor can clip it: not `overflow: hidden`, not a\n * `transform`, not a lower `z-index` on a sibling pane.\n *\n * The Popover API (`popover=\"manual\"` + the top layer) would do the same job\n * without the portal, but it is not yet safe to depend on across the WebKit\n * version bundled with the Tauri shell, and it still needs manual positioning\n * without `anchor-name`. The portal is the boring, verifiable option.\n *\n * TWO PROP SPELLINGS, on purpose. `label` + `shortcut` is the shape icon\n * controls want, and is what new call sites should use. `content` predates it\n * and takes arbitrary nodes (the Meter's numbers, for one), so it stays as the\n * other half of a union rather than being renamed out from under its callers.\n * Exactly one of the two is required.\n */\nimport {\n cloneElement,\n useCallback,\n useEffect,\n useId,\n useLayoutEffect,\n useRef,\n useState,\n type FocusEvent as ReactFocusEvent,\n type MouseEvent as ReactMouseEvent,\n type ReactElement,\n type ReactNode,\n} from 'react';\nimport { createPortal } from 'react-dom';\nimport styles from './Tooltip.module.css';\n\nexport type TooltipPlacement = 'top' | 'bottom' | 'left' | 'right';\n\n/**\n * Hovering across a rail of six icons must not strobe six bubbles, so the\n * POINTER waits. Focus does not: a keyboard operator has already committed to\n * the control, and a delay there just reads as lag.\n */\nconst OPEN_DELAY_MS = 160;\n/** Distance between the trigger's edge and the bubble. */\nconst GAP = 8;\n/** Smallest distance the bubble may sit from the viewport edge. */\nconst MARGIN = 8;\n\n/** The props Tooltip clones onto its trigger. */\ninterface TooltipTriggerProps {\n 'aria-describedby'?: string;\n /** Read, never set: a disabled trigger fires no pointer events, so the wrapper must take over. */\n disabled?: boolean;\n onMouseEnter?: (event: ReactMouseEvent<HTMLElement>) => void;\n onMouseLeave?: (event: ReactMouseEvent<HTMLElement>) => void;\n onFocus?: (event: ReactFocusEvent<HTMLElement>) => void;\n onBlur?: (event: ReactFocusEvent<HTMLElement>) => void;\n}\n\ninterface TooltipCommonProps {\n /** Rendered as a distinct keycap beside the label, e.g. `⌘2`. */\n shortcut?: string;\n placement?: TooltipPlacement;\n /** Skip the tooltip entirely without changing the tree (e.g. the label is already visible). */\n disabled?: boolean;\n className?: string;\n /** The trigger. Tooltip adds no layout box of its own around it. */\n children: ReactElement<TooltipTriggerProps>;\n}\n\nexport type TooltipProps = TooltipCommonProps &\n (\n | { label: string; content?: undefined }\n | { label?: undefined; content: ReactNode }\n );\n\ninterface Position {\n left: number;\n top: number;\n placement: TooltipPlacement;\n}\n\n/** Placement order: the caller's choice first, then its opposite, then the rest. */\nfunction candidates(preferred: TooltipPlacement): TooltipPlacement[] {\n const opposite: Record<TooltipPlacement, TooltipPlacement> = {\n top: 'bottom', bottom: 'top', left: 'right', right: 'left',\n };\n const rest = (['top', 'bottom', 'right', 'left'] as const).filter(\n (p) => p !== preferred && p !== opposite[preferred],\n );\n return [preferred, opposite[preferred], ...rest];\n}\n\nfunction place(placement: TooltipPlacement, trigger: DOMRect, w: number, h: number): { left: number; top: number } {\n switch (placement) {\n case 'top': return { left: trigger.left + trigger.width / 2 - w / 2, top: trigger.top - h - GAP };\n case 'bottom': return { left: trigger.left + trigger.width / 2 - w / 2, top: trigger.bottom + GAP };\n case 'left': return { left: trigger.left - w - GAP, top: trigger.top + trigger.height / 2 - h / 2 };\n case 'right': return { left: trigger.right + GAP, top: trigger.top + trigger.height / 2 - h / 2 };\n }\n}\n\n/**\n * First placement whose bubble fits the viewport wins; if none fits we keep\n * the preferred one and clamp, which is still readable — a bubble nudged\n * inward beats a bubble half off-screen.\n */\nfunction resolve(trigger: DOMRect, w: number, h: number, preferred: TooltipPlacement): Position {\n const vw = window.innerWidth || 0;\n const vh = window.innerHeight || 0;\n let fallback: Position | null = null;\n for (const placement of candidates(preferred)) {\n const { left, top } = place(placement, trigger, w, h);\n if (!fallback) fallback = { left, top, placement };\n const fits = left >= MARGIN && top >= MARGIN && left + w <= vw - MARGIN && top + h <= vh - MARGIN;\n if (fits) return { left, top, placement };\n }\n const chosen = fallback!;\n return {\n placement: chosen.placement,\n left: Math.max(MARGIN, Math.min(chosen.left, vw - w - MARGIN)),\n top: Math.max(MARGIN, Math.min(chosen.top, vh - h - MARGIN)),\n };\n}\n\nexport function Tooltip(props: TooltipProps) {\n const { label, content, shortcut, placement = 'top', disabled = false, className, children } = props;\n const [open, setOpen] = useState(false);\n const [position, setPosition] = useState<Position | null>(null);\n const id = useId();\n const triggerRef = useRef<HTMLElement | null>(null);\n const bubbleRef = useRef<HTMLDivElement | null>(null);\n const timer = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n const cancelTimer = useCallback(() => {\n if (timer.current !== null) {\n clearTimeout(timer.current);\n timer.current = null;\n }\n }, []);\n\n const close = useCallback(() => {\n cancelTimer();\n setOpen(false);\n setPosition(null);\n }, [cancelTimer]);\n\n // Unmounting mid-delay must not fire setOpen on a dead component.\n useEffect(() => cancelTimer, [cancelTimer]);\n\n const reposition = useCallback(() => {\n const trigger = triggerRef.current;\n const bubble = bubbleRef.current;\n if (!trigger || !bubble) return;\n const rect = trigger.getBoundingClientRect();\n setPosition(resolve(rect, bubble.offsetWidth, bubble.offsetHeight, placement));\n }, [placement]);\n\n // Measure AFTER the bubble exists but BEFORE paint, so it is never seen at\n // its unpositioned origin first.\n useLayoutEffect(() => {\n if (!open) return;\n reposition();\n }, [open, reposition]);\n\n // Escape closes while the trigger keeps focus (WCAG 1.4.13). Held at the\n // document so it works wherever the keystroke lands, and a scroll or resize\n // under an open bubble re-anchors it rather than leaving it stranded.\n useEffect(() => {\n if (!open) return;\n function onKeyDown(event: globalThis.KeyboardEvent) {\n if (event.key === 'Escape') close();\n }\n document.addEventListener('keydown', onKeyDown);\n window.addEventListener('resize', reposition);\n // Capture: a scroll inside any ancestor moves the trigger too, and scroll\n // events from those do not bubble to window.\n window.addEventListener('scroll', reposition, true);\n return () => {\n document.removeEventListener('keydown', onKeyDown);\n window.removeEventListener('resize', reposition);\n window.removeEventListener('scroll', reposition, true);\n };\n }, [open, close, reposition]);\n\n const body = label ?? content;\n const empty = body === null || body === undefined || body === '';\n if (disabled || empty) return children;\n\n function openAfterDelay(element: HTMLElement) {\n triggerRef.current = element;\n cancelTimer();\n timer.current = setTimeout(() => {\n timer.current = null;\n setOpen(true);\n }, OPEN_DELAY_MS);\n }\n\n function openNow(element: HTMLElement) {\n triggerRef.current = element;\n cancelTimer();\n setOpen(true);\n }\n\n const triggerDisabled = children.props.disabled === true;\n const describedBy = open ? id : undefined;\n\n const trigger = cloneElement(children, {\n 'aria-describedby': describedBy,\n onMouseEnter: (event: ReactMouseEvent<HTMLElement>) => {\n children.props.onMouseEnter?.(event);\n openAfterDelay(event.currentTarget);\n },\n onMouseLeave: (event: ReactMouseEvent<HTMLElement>) => {\n children.props.onMouseLeave?.(event);\n close();\n },\n onFocus: (event: ReactFocusEvent<HTMLElement>) => {\n children.props.onFocus?.(event);\n openNow(event.currentTarget);\n },\n onBlur: (event: ReactFocusEvent<HTMLElement>) => {\n children.props.onBlur?.(event);\n close();\n },\n });\n\n return (\n <>\n {/*\n `display: contents` — the wrapper exists in the DOM but generates no\n box, so it cannot disturb the trigger's own flex/grid placement. It is\n here only to carry the describedby relationship at the trigger's parent\n as well as on the trigger itself; the behaviour all lives on the cloned\n child, which is what actually has a hover target.\n */}\n <span\n className={`${styles.wrapper} ${className ?? ''}`}\n aria-describedby={describedBy}\n /*\n A DISABLED trigger emits no pointer events at all, so handlers on the\n cloned child can never fire and its tooltip could never open — which\n is precisely the tooltip that matters, since it is the one explaining\n WHY the control is disabled. When the child is disabled the wrapper\n stops being `display: contents`, takes a box of its own, and carries\n the hover itself. Focus is not mirrored here: a disabled control is\n not focusable, so there is no focus to mirror.\n */\n data-disabled-trigger={triggerDisabled ? 'true' : undefined}\n onMouseOver={triggerDisabled ? (event) => openNow(event.currentTarget) : undefined}\n onMouseOut={triggerDisabled ? close : undefined}\n >\n {trigger}\n </span>\n {open\n ? createPortal(\n <div\n ref={bubbleRef}\n role=\"tooltip\"\n id={id}\n className={styles.bubble}\n data-placement={position?.placement ?? placement}\n style={{\n // Inline, though the stylesheet says it too: `left`/`top`\n // below are VIEWPORT coordinates, and they only mean that\n // under `fixed`. The two travel together or the bubble lands\n // somewhere arbitrary.\n position: 'fixed',\n left: position ? `${position.left}px` : '0px',\n top: position ? `${position.top}px` : '0px',\n // Hidden until measured, but still laid out — the measurement\n // needs a real offsetWidth, and `visibility` gives one where\n // `display: none` would not.\n visibility: position ? 'visible' : 'hidden',\n }}\n >\n <span className={styles.label}>{body}</span>\n {shortcut ? <kbd className={styles.shortcut}>{shortcut}</kbd> : null}\n </div>,\n document.body,\n )\n : null}\n </>\n );\n}\n",".wrap {\n display: flex;\n flex-direction: column;\n gap: var(--space-1);\n min-width: 0;\n}\n\n.lineWrap {\n width: 100%;\n}\n\n.head {\n display: flex;\n align-items: baseline;\n justify-content: space-between;\n gap: var(--space-2);\n}\n\n.label {\n font-size: var(--text-xs-size);\n line-height: var(--text-xs-line);\n color: var(--text-secondary);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.value {\n flex: none;\n font-family: var(--font-display);\n font-variant-numeric: tabular-nums;\n font-size: var(--text-xs-size);\n letter-spacing: var(--tracking-display);\n color: var(--text-secondary);\n}\n\n.unknown {\n font-family: var(--font-ui);\n font-style: italic;\n color: var(--status-unknown-fg);\n}\n\n.track {\n width: 100%;\n overflow: hidden;\n background: var(--bg-active);\n}\n\n.bar {\n height: 4px;\n border-radius: var(--radius-full);\n}\n\n/* The 2px rule that sits under a header strip — square, full-bleed. */\n.line {\n height: 2px;\n border-radius: 0;\n background: var(--border-subtle);\n}\n\n.fill {\n height: 100%;\n transition: width var(--duration-base) var(--ease-standard);\n}\n\n.accent {\n background: var(--accent-500);\n}\n.neutral {\n background: var(--status-neutral-solid);\n}\n.running {\n background: var(--status-running-solid);\n}\n.warning {\n background: var(--status-warning-solid);\n}\n.danger {\n background: var(--status-danger-solid);\n}\n.engine {\n background: var(--status-neutral-solid);\n}\n","/**\n * components/primitives/Meter.tsx — occupancy against a limit: context\n * window, daily spend against the cap, a plan's usage window.\n *\n * Two forms, one component:\n * variant=\"line\" the 2px full-width rule the design language puts\n * directly under a header strip. No text of its own —\n * wrap it in <Tooltip> and put the numbers beside it.\n * variant=\"labelled\" a 4px bar with a label and a right-aligned\n * \"18k / 66k\" in the display face.\n *\n * Tone thresholds match the V1 context meter (amber >= 70%, danger >= 90%)\n * and are ALWAYS paired with the readable value, never color alone.\n * An unknown limit renders an honest empty track and the text \"unknown\" —\n * never a full bar, never zero (DESIGN.md §7).\n */\nimport type { ReactNode } from 'react';\nimport styles from './Meter.module.css';\n\nexport type MeterTone = 'accent' | 'neutral' | 'running' | 'warning' | 'danger' | 'engine';\n\nexport interface MeterProps {\n /** Current value. null = genuinely unknown. */\n value: number | null;\n /** Limit. null/0 = unknown limit; the meter says so instead of guessing. */\n max: number | null;\n variant?: 'line' | 'labelled';\n label?: ReactNode;\n /** Right-aligned readout. Defaults to `value / max` formatted compactly. */\n valueText?: ReactNode;\n /** Force a tone; by default it is derived from the percentage. */\n tone?: MeterTone;\n /** For tone=\"engine\": the CSS color to paint the fill (an --engine-* var). */\n engineColor?: string;\n /** Accessible name when no visible label is rendered (the line variant). */\n 'aria-label'?: string;\n className?: string;\n}\n\nfunction compact(n: number): string {\n if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(n >= 10_000_000 ? 0 : 1)}m`;\n if (n >= 1_000) return `${(n / 1_000).toFixed(n >= 10_000 ? 0 : 1)}k`;\n return String(Math.round(n));\n}\n\nexport function toneForPercent(percent: number): MeterTone {\n if (percent >= 90) return 'danger';\n if (percent >= 70) return 'warning';\n return 'accent';\n}\n\nexport function Meter({\n value,\n max,\n variant = 'labelled',\n label,\n valueText,\n tone,\n engineColor,\n className,\n ...aria\n}: MeterProps) {\n const known = value !== null && Number.isFinite(value) && max !== null && Number.isFinite(max) && max > 0;\n const percent = known ? Math.min(100, Math.max(0, (value! / max!) * 100)) : 0;\n const resolvedTone = tone ?? (known ? toneForPercent(percent) : 'neutral');\n const readout = known ? (valueText ?? `${compact(value!)} / ${compact(max!)}`) : (valueText ?? 'unknown');\n\n const track = (\n <div\n className={`${styles.track} ${variant === 'line' ? styles.line : styles.bar}`}\n role=\"meter\"\n aria-valuenow={known ? Math.round(percent) : undefined}\n aria-valuemin={0}\n aria-valuemax={100}\n aria-valuetext={known ? `${Math.round(percent)}%` : 'unknown'}\n {...aria}\n >\n <div\n className={`${styles.fill} ${styles[resolvedTone]}`}\n style={{\n width: `${percent}%`,\n ...(resolvedTone === 'engine' && engineColor ? { background: engineColor } : {}),\n }}\n />\n </div>\n );\n\n if (variant === 'line') return <div className={`${styles.lineWrap} ${className ?? ''}`}>{track}</div>;\n\n return (\n <div className={`${styles.wrap} ${className ?? ''}`}>\n {label || readout ? (\n <div className={styles.head}>\n {label ? <span className={styles.label}>{label}</span> : <span />}\n <span className={`${styles.value} ${known ? '' : styles.unknown}`}>{readout}</span>\n </div>\n ) : null}\n {track}\n </div>\n );\n}\n",".group {\n display: inline-flex;\n align-items: center;\n gap: 2px;\n padding: 2px;\n background: var(--bg-hover);\n border: 1px solid var(--border-subtle);\n border-radius: var(--radius-sm);\n}\n\n.block {\n display: flex;\n width: 100%;\n}\n\n.block .option {\n flex: 1;\n justify-content: center;\n}\n\n.option {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: var(--space-1);\n height: calc(var(--density-row) - 6px);\n padding: 0 var(--space-3);\n /* Transparent rather than absent so the checked state can colour it without\n changing the option's box — everything is border-box, so the hairline\n costs no height and no horizontal drift when it appears. */\n border: 1px solid transparent;\n border-radius: calc(var(--radius-sm) - 1px);\n background: transparent;\n color: var(--text-secondary);\n font-family: var(--font-ui);\n font-size: var(--text-xs-size);\n font-weight: var(--font-weight-medium);\n white-space: nowrap;\n cursor: pointer;\n transition:\n background-color var(--duration-fast) var(--ease-standard),\n color var(--duration-fast) var(--ease-standard);\n}\n\n.sm .option {\n height: calc(var(--density-row-sm) - 6px);\n padding: 0 var(--space-2);\n font-size: var(--text-2xs-size);\n}\n\n.option:hover:not(:disabled):not([aria-checked='true']) {\n color: var(--text-primary);\n}\n\n/* No shadow. DESIGN-V2 §1.2 allows drop shadows only on true overlays\n (dialog, menu, toast) and §2 retires every elevation token but those three;\n a segmented control is chrome. It was not carrying its weight either: in\n dark, --shadow-sm is rgba(0,0,0,0.4) on a near-black ground, i.e. invisible,\n so the selected thumb read as fill-only in dark and fill+shadow in light —\n two looks for one state. The hairline does the separating in both. */\n.option[aria-checked='true'] {\n background: var(--bg-surface);\n color: var(--text-primary);\n border-color: var(--border-default);\n}\n\n.option:disabled {\n color: var(--text-disabled);\n cursor: not-allowed;\n}\n\n.glyph {\n display: inline-flex;\n flex: none;\n}\n","/**\n * components/primitives/Segmented.tsx — pick one of 2-4 options where the\n * choice applies instantly (theme, density, a status filter). It replaces\n * both a <select> (too heavy for three options) and a row of toggle buttons\n * (no single-selection semantics for a screen reader).\n *\n * Implemented as a radiogroup with ROVING TABINDEX: one Tab stop for the\n * group, arrows move between options and select as they go — the WAI-ARIA\n * radio-group pattern. Every option is reachable by keyboard and announced\n * as \"N of M\".\n */\nimport { useRef, type ReactNode } from 'react';\nimport styles from './Segmented.module.css';\n\nexport interface SegmentedOption<T extends string> {\n value: T;\n label: ReactNode;\n /** Leading 16px glyph; the text label always stays visible. */\n icon?: ReactNode;\n /** Spoken name when `label` is a glyph or an abbreviation. */\n ariaLabel?: string;\n disabled?: boolean;\n}\n\nexport interface SegmentedProps<T extends string> {\n options: ReadonlyArray<SegmentedOption<T>>;\n value: T;\n onChange: (next: T) => void;\n /** Accessible name for the group — required when no visible label precedes it. */\n 'aria-label'?: string;\n 'aria-labelledby'?: string;\n size?: 'sm' | 'md';\n /** Stretch each option to an equal share of the width. */\n block?: boolean;\n className?: string;\n}\n\nexport function Segmented<T extends string>({\n options,\n value,\n onChange,\n size = 'md',\n block = false,\n className,\n ...aria\n}: SegmentedProps<T>) {\n const groupRef = useRef<HTMLDivElement>(null);\n\n function move(delta: number): void {\n const enabled = options.filter((o) => !o.disabled);\n if (enabled.length === 0) return;\n const current = enabled.findIndex((o) => o.value === value);\n const next = enabled[(((current + delta) % enabled.length) + enabled.length) % enabled.length]!;\n onChange(next.value);\n // Keep DOM focus on the newly selected option (roving tabindex).\n requestAnimationFrame(() => {\n groupRef.current?.querySelector<HTMLButtonElement>(`[data-value=\"${next.value}\"]`)?.focus();\n });\n }\n\n return (\n <div\n ref={groupRef}\n role=\"radiogroup\"\n className={`${styles.group} ${styles[size]} ${block ? styles.block : ''} ${className ?? ''}`}\n {...aria}\n >\n {options.map((option) => {\n const selected = option.value === value;\n return (\n <button\n key={option.value}\n type=\"button\"\n role=\"radio\"\n aria-checked={selected}\n aria-label={option.ariaLabel}\n data-value={option.value}\n tabIndex={selected ? 0 : -1}\n disabled={option.disabled}\n className={styles.option}\n onClick={() => onChange(option.value)}\n onKeyDown={(e) => {\n if (e.key === 'ArrowRight' || e.key === 'ArrowDown') {\n e.preventDefault();\n move(1);\n } else if (e.key === 'ArrowLeft' || e.key === 'ArrowUp') {\n e.preventDefault();\n move(-1);\n }\n }}\n >\n {option.icon ? <span className={styles.glyph}>{option.icon}</span> : null}\n {option.label}\n </button>\n );\n })}\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"Segmented-DhGh7Y1s.js","names":[],"sources":["../../../../../../src/web-ui/components/primitives/Tooltip.module.css","../../../../../../src/web-ui/components/primitives/Tooltip.tsx","../../../../../../src/web-ui/components/primitives/Meter.module.css","../../../../../../src/web-ui/components/primitives/Meter.tsx","../../../../../../src/web-ui/components/primitives/Segmented.module.css","../../../../../../src/web-ui/components/primitives/Segmented.tsx"],"sourcesContent":["/* The trigger's stand-in in the tree: present in the DOM so the describedby\n relationship has somewhere to sit, but generating NO box, so a trigger in a\n flex or grid track keeps its own placement exactly as it had it. */\n.wrapper {\n display: contents;\n}\n\n/* Fixed, not absolute, and portalled to <body>: the bubble's coordinates come\n from the trigger's viewport rect, so no ancestor's overflow, transform or\n stacking context can clip it. That clipping is the exact failure of the\n native `title=` tooltip this replaces. */\n.bubble {\n position: fixed;\n /* Above every other layer in the scale, toasts included: a tooltip can\n describe a control inside a dialog, a sheet or the command palette, and\n a bubble drawn behind the thing it explains is the same bug as one that\n is clipped. There is no --z-tooltip token yet; this stays one step above\n the top of the scale rather than inventing a number. */\n z-index: calc(var(--z-toast) + 1);\n display: flex;\n align-items: center;\n gap: var(--space-2);\n max-width: 260px;\n padding: var(--space-1) var(--space-2);\n border: 1px solid var(--border-default);\n border-radius: var(--radius-xs);\n background: var(--bg-surface-raised);\n color: var(--text-primary);\n box-shadow: var(--shadow-menu);\n font-family: var(--font-ui);\n font-size: var(--text-xs-size);\n line-height: var(--text-xs-line);\n font-weight: var(--font-weight-regular);\n /* Was `nowrap`, which defeated the max-width above: a long tooltip ran off\n the viewport instead of wrapping inside 260px. */\n white-space: normal;\n text-wrap: pretty;\n /* It follows the pointer's target, it is never a pointer target itself —\n otherwise a bubble drawn under the cursor triggers its own mouseleave. */\n pointer-events: none;\n animation: tooltip-in var(--duration-fast) var(--ease-standard);\n}\n\n.label {\n min-width: 0;\n}\n\n/* The shortcut reads as a key, not as more sentence. */\n.shortcut {\n flex: none;\n padding: 0 4px;\n border: 1px solid var(--border-subtle);\n border-radius: var(--radius-xs);\n background: var(--bg-code);\n color: var(--text-secondary);\n font-family: var(--font-mono);\n font-size: var(--text-2xs-size, 11px);\n line-height: 1.5;\n white-space: nowrap;\n}\n\n@keyframes tooltip-in {\n from {\n opacity: 0;\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n :root:not([data-motion=\"full\"]) .bubble {\n animation: none;\n }\n}\n\n:root[data-motion=\"reduce\"] .bubble {\n animation: none;\n}\n\n/* A disabled trigger emits no pointer events, so the wrapper has to become a\n real box to catch the hover. Only then — `display: contents` is the default\n precisely so the wrapper never disturbs the trigger's flex/grid placement. */\n.wrapper[data-disabled-trigger='true'] {\n display: inline-flex;\n}\n","/**\n * components/primitives/Tooltip.tsx — the label for an icon-only control and\n * the numbers behind a 2px meter line.\n *\n * Rules it enforces so a tooltip never becomes the only way to read the UI:\n * - it shows on HOVER *and* on keyboard FOCUS (a mouse-only tooltip is\n * invisible to keyboard operators);\n * - Escape dismisses it while the trigger keeps focus (WCAG 1.4.13);\n * - the trigger is wired with aria-describedby, so the content is\n * announced rather than merely drawn;\n * - it carries description, never the accessible NAME — icon buttons still\n * need their own aria-label.\n *\n * WHY IT RENDERS THROUGH A PORTAL. This used to be an absolutely-positioned\n * span inside a `position: relative` wrapper, which is correct right up until\n * the trigger lives inside something that clips — and the first real consumer,\n * the Verse rail, is exactly that: a 56px flex column inside a grid shell with\n * its own stacking context. A bubble anchored there is cut off at the rail's\n * edge, which is the failure the native `title=` tooltip already had. Rendering\n * into `document.body` at `position: fixed`, positioned from the trigger's\n * bounding rect, means NO ancestor can clip it: not `overflow: hidden`, not a\n * `transform`, not a lower `z-index` on a sibling pane.\n *\n * The Popover API (`popover=\"manual\"` + the top layer) would do the same job\n * without the portal, but it is not yet safe to depend on across the WebKit\n * version bundled with the Tauri shell, and it still needs manual positioning\n * without `anchor-name`. The portal is the boring, verifiable option.\n *\n * TWO PROP SPELLINGS, on purpose. `label` + `shortcut` is the shape icon\n * controls want, and is what new call sites should use. `content` predates it\n * and takes arbitrary nodes (the Meter's numbers, for one), so it stays as the\n * other half of a union rather than being renamed out from under its callers.\n * Exactly one of the two is required.\n */\nimport {\n cloneElement,\n useCallback,\n useEffect,\n useId,\n useLayoutEffect,\n useRef,\n useState,\n type FocusEvent as ReactFocusEvent,\n type MouseEvent as ReactMouseEvent,\n type ReactElement,\n type ReactNode,\n} from 'react';\nimport { createPortal } from 'react-dom';\nimport styles from './Tooltip.module.css';\n\nexport type TooltipPlacement = 'top' | 'bottom' | 'left' | 'right';\n\n/**\n * Hovering across a rail of six icons must not strobe six bubbles, so the\n * POINTER waits. Focus does not: a keyboard operator has already committed to\n * the control, and a delay there just reads as lag.\n */\nconst OPEN_DELAY_MS = 160;\n/** Distance between the trigger's edge and the bubble. */\nconst GAP = 8;\n/** Smallest distance the bubble may sit from the viewport edge. */\nconst MARGIN = 8;\n\n/** The props Tooltip clones onto its trigger. */\ninterface TooltipTriggerProps {\n 'aria-describedby'?: string;\n /** Read, never set: a disabled trigger fires no pointer events, so the wrapper must take over. */\n disabled?: boolean;\n onMouseEnter?: (event: ReactMouseEvent<HTMLElement>) => void;\n onMouseLeave?: (event: ReactMouseEvent<HTMLElement>) => void;\n onFocus?: (event: ReactFocusEvent<HTMLElement>) => void;\n onBlur?: (event: ReactFocusEvent<HTMLElement>) => void;\n}\n\ninterface TooltipCommonProps {\n /** Rendered as a distinct keycap beside the label, e.g. `⌘2`. */\n shortcut?: string;\n placement?: TooltipPlacement;\n /** Skip the tooltip entirely without changing the tree (e.g. the label is already visible). */\n disabled?: boolean;\n className?: string;\n /** The trigger. Tooltip adds no layout box of its own around it. */\n children: ReactElement<TooltipTriggerProps>;\n}\n\nexport type TooltipProps = TooltipCommonProps &\n (\n | { label: string; content?: undefined }\n | { label?: undefined; content: ReactNode }\n );\n\ninterface Position {\n left: number;\n top: number;\n placement: TooltipPlacement;\n}\n\n/** Placement order: the caller's choice first, then its opposite, then the rest. */\nfunction candidates(preferred: TooltipPlacement): TooltipPlacement[] {\n const opposite: Record<TooltipPlacement, TooltipPlacement> = {\n top: 'bottom', bottom: 'top', left: 'right', right: 'left',\n };\n const rest = (['top', 'bottom', 'right', 'left'] as const).filter(\n (p) => p !== preferred && p !== opposite[preferred],\n );\n return [preferred, opposite[preferred], ...rest];\n}\n\nfunction place(placement: TooltipPlacement, trigger: DOMRect, w: number, h: number): { left: number; top: number } {\n switch (placement) {\n case 'top': return { left: trigger.left + trigger.width / 2 - w / 2, top: trigger.top - h - GAP };\n case 'bottom': return { left: trigger.left + trigger.width / 2 - w / 2, top: trigger.bottom + GAP };\n case 'left': return { left: trigger.left - w - GAP, top: trigger.top + trigger.height / 2 - h / 2 };\n case 'right': return { left: trigger.right + GAP, top: trigger.top + trigger.height / 2 - h / 2 };\n }\n}\n\n/**\n * First placement whose bubble fits the viewport wins; if none fits we keep\n * the preferred one and clamp, which is still readable — a bubble nudged\n * inward beats a bubble half off-screen.\n */\nfunction resolve(trigger: DOMRect, w: number, h: number, preferred: TooltipPlacement): Position {\n const vw = window.innerWidth || 0;\n const vh = window.innerHeight || 0;\n let fallback: Position | null = null;\n for (const placement of candidates(preferred)) {\n const { left, top } = place(placement, trigger, w, h);\n if (!fallback) fallback = { left, top, placement };\n const fits = left >= MARGIN && top >= MARGIN && left + w <= vw - MARGIN && top + h <= vh - MARGIN;\n if (fits) return { left, top, placement };\n }\n const chosen = fallback!;\n return {\n placement: chosen.placement,\n left: Math.max(MARGIN, Math.min(chosen.left, vw - w - MARGIN)),\n top: Math.max(MARGIN, Math.min(chosen.top, vh - h - MARGIN)),\n };\n}\n\nexport function Tooltip(props: TooltipProps) {\n const { label, content, shortcut, placement = 'top', disabled = false, className, children } = props;\n const [open, setOpen] = useState(false);\n const [position, setPosition] = useState<Position | null>(null);\n const id = useId();\n const triggerRef = useRef<HTMLElement | null>(null);\n const bubbleRef = useRef<HTMLDivElement | null>(null);\n const timer = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n const cancelTimer = useCallback(() => {\n if (timer.current !== null) {\n clearTimeout(timer.current);\n timer.current = null;\n }\n }, []);\n\n const close = useCallback(() => {\n cancelTimer();\n setOpen(false);\n setPosition(null);\n }, [cancelTimer]);\n\n // Unmounting mid-delay must not fire setOpen on a dead component.\n useEffect(() => cancelTimer, [cancelTimer]);\n\n const reposition = useCallback(() => {\n const trigger = triggerRef.current;\n const bubble = bubbleRef.current;\n if (!trigger || !bubble) return;\n const rect = trigger.getBoundingClientRect();\n setPosition(resolve(rect, bubble.offsetWidth, bubble.offsetHeight, placement));\n }, [placement]);\n\n // Measure AFTER the bubble exists but BEFORE paint, so it is never seen at\n // its unpositioned origin first.\n useLayoutEffect(() => {\n if (!open) return;\n reposition();\n }, [open, reposition]);\n\n // Escape closes while the trigger keeps focus (WCAG 1.4.13). Held at the\n // document so it works wherever the keystroke lands, and a scroll or resize\n // under an open bubble re-anchors it rather than leaving it stranded.\n useEffect(() => {\n if (!open) return;\n function onKeyDown(event: globalThis.KeyboardEvent) {\n if (event.key === 'Escape') close();\n }\n document.addEventListener('keydown', onKeyDown);\n window.addEventListener('resize', reposition);\n // Capture: a scroll inside any ancestor moves the trigger too, and scroll\n // events from those do not bubble to window.\n window.addEventListener('scroll', reposition, true);\n return () => {\n document.removeEventListener('keydown', onKeyDown);\n window.removeEventListener('resize', reposition);\n window.removeEventListener('scroll', reposition, true);\n };\n }, [open, close, reposition]);\n\n const body = label ?? content;\n const empty = body === null || body === undefined || body === '';\n if (disabled || empty) return children;\n\n function openAfterDelay(element: HTMLElement) {\n triggerRef.current = element;\n cancelTimer();\n timer.current = setTimeout(() => {\n timer.current = null;\n setOpen(true);\n }, OPEN_DELAY_MS);\n }\n\n function openNow(element: HTMLElement) {\n triggerRef.current = element;\n cancelTimer();\n setOpen(true);\n }\n\n const triggerDisabled = children.props.disabled === true;\n const describedBy = open ? id : undefined;\n\n const trigger = cloneElement(children, {\n 'aria-describedby': describedBy,\n onMouseEnter: (event: ReactMouseEvent<HTMLElement>) => {\n children.props.onMouseEnter?.(event);\n openAfterDelay(event.currentTarget);\n },\n onMouseLeave: (event: ReactMouseEvent<HTMLElement>) => {\n children.props.onMouseLeave?.(event);\n close();\n },\n onFocus: (event: ReactFocusEvent<HTMLElement>) => {\n children.props.onFocus?.(event);\n openNow(event.currentTarget);\n },\n onBlur: (event: ReactFocusEvent<HTMLElement>) => {\n children.props.onBlur?.(event);\n close();\n },\n });\n\n return (\n <>\n {/*\n `display: contents` — the wrapper exists in the DOM but generates no\n box, so it cannot disturb the trigger's own flex/grid placement. It is\n here only to carry the describedby relationship at the trigger's parent\n as well as on the trigger itself; the behaviour all lives on the cloned\n child, which is what actually has a hover target.\n */}\n <span\n className={`${styles.wrapper} ${className ?? ''}`}\n aria-describedby={describedBy}\n /*\n A DISABLED trigger emits no pointer events at all, so handlers on the\n cloned child can never fire and its tooltip could never open — which\n is precisely the tooltip that matters, since it is the one explaining\n WHY the control is disabled. When the child is disabled the wrapper\n stops being `display: contents`, takes a box of its own, and carries\n the hover itself. Focus is not mirrored here: a disabled control is\n not focusable, so there is no focus to mirror.\n */\n data-disabled-trigger={triggerDisabled ? 'true' : undefined}\n onMouseOver={triggerDisabled ? (event) => openNow(event.currentTarget) : undefined}\n onMouseOut={triggerDisabled ? close : undefined}\n >\n {trigger}\n </span>\n {open\n ? createPortal(\n <div\n ref={bubbleRef}\n role=\"tooltip\"\n id={id}\n className={styles.bubble}\n data-placement={position?.placement ?? placement}\n style={{\n // Inline, though the stylesheet says it too: `left`/`top`\n // below are VIEWPORT coordinates, and they only mean that\n // under `fixed`. The two travel together or the bubble lands\n // somewhere arbitrary.\n position: 'fixed',\n left: position ? `${position.left}px` : '0px',\n top: position ? `${position.top}px` : '0px',\n // Hidden until measured, but still laid out — the measurement\n // needs a real offsetWidth, and `visibility` gives one where\n // `display: none` would not.\n visibility: position ? 'visible' : 'hidden',\n }}\n >\n <span className={styles.label}>{body}</span>\n {shortcut ? <kbd className={styles.shortcut}>{shortcut}</kbd> : null}\n </div>,\n document.body,\n )\n : null}\n </>\n );\n}\n",".wrap {\n display: flex;\n flex-direction: column;\n gap: var(--space-1);\n min-width: 0;\n}\n\n.lineWrap {\n width: 100%;\n}\n\n.head {\n display: flex;\n align-items: baseline;\n justify-content: space-between;\n gap: var(--space-2);\n}\n\n.label {\n font-size: var(--text-xs-size);\n line-height: var(--text-xs-line);\n color: var(--text-secondary);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.value {\n flex: none;\n font-family: var(--font-display);\n font-variant-numeric: tabular-nums;\n font-size: var(--text-xs-size);\n letter-spacing: var(--tracking-display);\n color: var(--text-secondary);\n}\n\n.unknown {\n font-family: var(--font-ui);\n font-style: italic;\n color: var(--status-unknown-fg);\n}\n\n.track {\n width: 100%;\n overflow: hidden;\n background: var(--bg-active);\n}\n\n.bar {\n height: 4px;\n border-radius: var(--radius-full);\n}\n\n/* The 2px rule that sits under a header strip — square, full-bleed. */\n.line {\n height: 2px;\n border-radius: 0;\n background: var(--border-subtle);\n}\n\n.fill {\n height: 100%;\n transition: width var(--duration-base) var(--ease-standard);\n}\n\n.accent {\n background: var(--accent-500);\n}\n.neutral {\n background: var(--status-neutral-solid);\n}\n.running {\n background: var(--status-running-solid);\n}\n.warning {\n background: var(--status-warning-solid);\n}\n.danger {\n background: var(--status-danger-solid);\n}\n.engine {\n background: var(--status-neutral-solid);\n}\n","/**\n * components/primitives/Meter.tsx — occupancy against a limit: context\n * window, daily spend against the cap, a plan's usage window.\n *\n * Two forms, one component:\n * variant=\"line\" the 2px full-width rule the design language puts\n * directly under a header strip. No text of its own —\n * wrap it in <Tooltip> and put the numbers beside it.\n * variant=\"labelled\" a 4px bar with a label and a right-aligned\n * \"18k / 66k\" in the display face.\n *\n * Tone thresholds match the V1 context meter (amber >= 70%, danger >= 90%)\n * and are ALWAYS paired with the readable value, never color alone.\n * An unknown limit renders an honest empty track and the text \"unknown\" —\n * never a full bar, never zero (DESIGN.md §7).\n */\nimport type { ReactNode } from 'react';\nimport styles from './Meter.module.css';\n\nexport type MeterTone = 'accent' | 'neutral' | 'running' | 'warning' | 'danger' | 'engine';\n\nexport interface MeterProps {\n /** Current value. null = genuinely unknown. */\n value: number | null;\n /** Limit. null/0 = unknown limit; the meter says so instead of guessing. */\n max: number | null;\n variant?: 'line' | 'labelled';\n label?: ReactNode;\n /** Right-aligned readout. Defaults to `value / max` formatted compactly. */\n valueText?: ReactNode;\n /** Force a tone; by default it is derived from the percentage. */\n tone?: MeterTone;\n /** For tone=\"engine\": the CSS color to paint the fill (an --engine-* var). */\n engineColor?: string;\n /** Accessible name when no visible label is rendered (the line variant). */\n 'aria-label'?: string;\n className?: string;\n}\n\nfunction compact(n: number): string {\n if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(n >= 10_000_000 ? 0 : 1)}m`;\n if (n >= 1_000) return `${(n / 1_000).toFixed(n >= 10_000 ? 0 : 1)}k`;\n return String(Math.round(n));\n}\n\nexport function toneForPercent(percent: number): MeterTone {\n if (percent >= 90) return 'danger';\n if (percent >= 70) return 'warning';\n return 'accent';\n}\n\nexport function Meter({\n value,\n max,\n variant = 'labelled',\n label,\n valueText,\n tone,\n engineColor,\n className,\n ...aria\n}: MeterProps) {\n const known = value !== null && Number.isFinite(value) && max !== null && Number.isFinite(max) && max > 0;\n const percent = known ? Math.min(100, Math.max(0, (value! / max!) * 100)) : 0;\n const resolvedTone = tone ?? (known ? toneForPercent(percent) : 'neutral');\n const readout = known ? (valueText ?? `${compact(value!)} / ${compact(max!)}`) : (valueText ?? 'unknown');\n\n const track = (\n <div\n className={`${styles.track} ${variant === 'line' ? styles.line : styles.bar}`}\n role=\"meter\"\n aria-valuenow={known ? Math.round(percent) : undefined}\n aria-valuemin={0}\n aria-valuemax={100}\n aria-valuetext={known ? `${Math.round(percent)}%` : 'unknown'}\n {...aria}\n >\n <div\n className={`${styles.fill} ${styles[resolvedTone]}`}\n style={{\n width: `${percent}%`,\n ...(resolvedTone === 'engine' && engineColor ? { background: engineColor } : {}),\n }}\n />\n </div>\n );\n\n if (variant === 'line') return <div className={`${styles.lineWrap} ${className ?? ''}`}>{track}</div>;\n\n return (\n <div className={`${styles.wrap} ${className ?? ''}`}>\n {label || readout ? (\n <div className={styles.head}>\n {label ? <span className={styles.label}>{label}</span> : <span />}\n <span className={`${styles.value} ${known ? '' : styles.unknown}`}>{readout}</span>\n </div>\n ) : null}\n {track}\n </div>\n );\n}\n",".group {\n display: inline-flex;\n align-items: center;\n gap: 2px;\n padding: 2px;\n background: var(--bg-hover);\n border: 1px solid var(--border-subtle);\n border-radius: var(--radius-sm);\n}\n\n.block {\n display: flex;\n width: 100%;\n}\n\n.block .option {\n flex: 1;\n justify-content: center;\n}\n\n.option {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: var(--space-1);\n height: calc(var(--density-row) - 6px);\n padding: 0 var(--space-3);\n /* Transparent rather than absent so the checked state can colour it without\n changing the option's box — everything is border-box, so the hairline\n costs no height and no horizontal drift when it appears. */\n border: 1px solid transparent;\n border-radius: calc(var(--radius-sm) - 1px);\n background: transparent;\n color: var(--text-secondary);\n font-family: var(--font-ui);\n font-size: var(--text-xs-size);\n font-weight: var(--font-weight-medium);\n white-space: nowrap;\n cursor: pointer;\n transition:\n background-color var(--duration-fast) var(--ease-standard),\n color var(--duration-fast) var(--ease-standard);\n}\n\n.sm .option {\n height: calc(var(--density-row-sm) - 6px);\n padding: 0 var(--space-2);\n font-size: var(--text-2xs-size);\n}\n\n.option:hover:not(:disabled):not([aria-checked='true']) {\n color: var(--text-primary);\n}\n\n/* No shadow. DESIGN-V2 §1.2 allows drop shadows only on true overlays\n (dialog, menu, toast) and §2 retires every elevation token but those three;\n a segmented control is chrome. It was not carrying its weight either: in\n dark, --shadow-sm is rgba(0,0,0,0.4) on a near-black ground, i.e. invisible,\n so the selected thumb read as fill-only in dark and fill+shadow in light —\n two looks for one state. The hairline does the separating in both. */\n.option[aria-checked='true'] {\n background: var(--bg-surface);\n color: var(--text-primary);\n border-color: var(--border-default);\n}\n\n.option:disabled {\n color: var(--text-disabled);\n cursor: not-allowed;\n}\n\n.glyph {\n display: inline-flex;\n flex: none;\n}\n","/**\n * components/primitives/Segmented.tsx — pick one of 2-4 options where the\n * choice applies instantly (theme, density, a status filter). It replaces\n * both a <select> (too heavy for three options) and a row of toggle buttons\n * (no single-selection semantics for a screen reader).\n *\n * Implemented as a radiogroup with ROVING TABINDEX: one Tab stop for the\n * group, arrows move between options and select as they go — the WAI-ARIA\n * radio-group pattern. Every option is reachable by keyboard and announced\n * as \"N of M\".\n */\nimport { useRef, type ReactNode } from 'react';\nimport styles from './Segmented.module.css';\n\nexport interface SegmentedOption<T extends string> {\n value: T;\n label: ReactNode;\n /** Leading 16px glyph; the text label always stays visible. */\n icon?: ReactNode;\n /** Spoken name when `label` is a glyph or an abbreviation. */\n ariaLabel?: string;\n disabled?: boolean;\n}\n\nexport interface SegmentedProps<T extends string> {\n options: ReadonlyArray<SegmentedOption<T>>;\n value: T;\n onChange: (next: T) => void;\n /** Accessible name for the group — required when no visible label precedes it. */\n 'aria-label'?: string;\n 'aria-labelledby'?: string;\n size?: 'sm' | 'md';\n /** Stretch each option to an equal share of the width. */\n block?: boolean;\n className?: string;\n}\n\nexport function Segmented<T extends string>({\n options,\n value,\n onChange,\n size = 'md',\n block = false,\n className,\n ...aria\n}: SegmentedProps<T>) {\n const groupRef = useRef<HTMLDivElement>(null);\n\n function move(delta: number): void {\n const enabled = options.filter((o) => !o.disabled);\n if (enabled.length === 0) return;\n const current = enabled.findIndex((o) => o.value === value);\n const next = enabled[(((current + delta) % enabled.length) + enabled.length) % enabled.length]!;\n onChange(next.value);\n // Keep DOM focus on the newly selected option (roving tabindex).\n requestAnimationFrame(() => {\n groupRef.current?.querySelector<HTMLButtonElement>(`[data-value=\"${next.value}\"]`)?.focus();\n });\n }\n\n return (\n <div\n ref={groupRef}\n role=\"radiogroup\"\n className={`${styles.group} ${styles[size]} ${block ? styles.block : ''} ${className ?? ''}`}\n {...aria}\n >\n {options.map((option) => {\n const selected = option.value === value;\n return (\n <button\n key={option.value}\n type=\"button\"\n role=\"radio\"\n aria-checked={selected}\n aria-label={option.ariaLabel}\n data-value={option.value}\n tabIndex={selected ? 0 : -1}\n disabled={option.disabled}\n className={styles.option}\n onClick={() => onChange(option.value)}\n onKeyDown={(e) => {\n if (e.key === 'ArrowRight' || e.key === 'ArrowDown') {\n e.preventDefault();\n move(1);\n } else if (e.key === 'ArrowLeft' || e.key === 'ArrowUp') {\n e.preventDefault();\n move(-1);\n }\n }}\n >\n {option.icon ? <span className={styles.glyph}>{option.icon}</span> : null}\n {option.label}\n </button>\n );\n })}\n </div>\n );\n}\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- import{a as e,n as t,o as n}from"./index-D2Eik-zt.js";import{C as r,O as i,S as a,T as o,_ as s,g as c,h as l,k as u,p as d,w as f}from"./global-eaY9GyX3.js";import{t as p}from"./Dialog-BxdIfmFH.js";import{r as m}from"./RouteErrorBoundary-ZL9F4qUn.js";import{n as h,t as g}from"./Segmented-DbRjVye3.js";import{f as _,h as v,l as ee,m as y,p as b,r as te,s as ne,t as x,y as S}from"./icons-vex8rclC.js";import{B as C,C as w,S as T,U as E,V as D,W as O,z as k}from"./VerseConsoleApp-y0uOHixd.js";var A={section:`_section_3qpbp_5`,strip:`_strip_3qpbp_13`,stripTitle:`_stripTitle_3qpbp_25`,scroll:`_scroll_3qpbp_32`,column:`_column_3qpbp_38`,panel:`_panel_3qpbp_51`,panelHead:`_panelHead_3qpbp_56`,panelTitle:`_panelTitle_3qpbp_64`,panelBody:`_panelBody_3qpbp_74`,row:`_row_3qpbp_81`,rowStacked:`_rowStacked_3qpbp_90`,rowText:`_rowText_3qpbp_95`,rowLabel:`_rowLabel_3qpbp_99`,rowDescription:`_rowDescription_3qpbp_107`,rowControl:`_rowControl_3qpbp_114`,swatches:`_swatches_3qpbp_140`,swatch:`_swatch_3qpbp_140`,swatchCheck:`_swatchCheck_3qpbp_165`,sliderStack:`_sliderStack_3qpbp_173`,contrastWarning:`_contrastWarning_3qpbp_180`,preview:`_preview_3qpbp_197`,previewRow:`_previewRow_3qpbp_207`,previewHeading:`_previewHeading_3qpbp_214`,previewBody:`_previewBody_3qpbp_222`,previewNumerals:`_previewNumerals_3qpbp_228`,origin:`_origin_3qpbp_238`,note:`_note_3qpbp_242`,noteIcon:`_noteIcon_3qpbp_258`,code:`_code_3qpbp_249`,shortcuts:`_shortcuts_3qpbp_278`,shortcut:`_shortcut_3qpbp_278`,keys:`_keys_3qpbp_294`,key:`_key_3qpbp_294`,about:`_about_3qpbp_315`,aboutLine:`_aboutLine_3qpbp_321`,aboutName:`_aboutName_3qpbp_329`,link:`_link_3qpbp_338`},j=t();function M({title:e,action:t,children:n}){return(0,j.jsxs)(`section`,{className:A.panel,children:[(0,j.jsxs)(`div`,{className:A.panelHead,children:[(0,j.jsx)(`h3`,{className:A.panelTitle,children:e}),t]}),(0,j.jsx)(`div`,{className:A.panelBody,children:n})]})}function N({label:e,description:t,htmlFor:n,labelId:r,stacked:i=!1,children:a}){let o=(0,j.jsxs)(j.Fragment,{children:[(0,j.jsx)(`span`,{className:A.rowLabel,id:r,children:e}),t?(0,j.jsx)(`span`,{className:A.rowDescription,children:t}):null]});return(0,j.jsxs)(`div`,{className:`${A.row} ${i?A.rowStacked:``}`,children:[(0,j.jsx)(`div`,{className:A.rowText,children:n?(0,j.jsx)(`label`,{htmlFor:n,className:A.rowText,children:o}):o}),(0,j.jsx)(`div`,{className:A.rowControl,children:a})]})}function P(e,t){return e?`Last completed ${new Date(e).toLocaleDateString()}.`:t?`Skipped. Nothing was missed — it is all reachable from the sections themselves.`:`Not seen yet.`}function F(){let{completedAt:e,dismissedAt:t,open:n}=T();return(0,j.jsx)(M,{title:`Onboarding`,children:(0,j.jsx)(N,{label:`Replay the first-run tour`,description:`Your connected seats, whether a local runtime is available, and what each of the three stop controls really halts. ${P(e,t)}`,children:(0,j.jsx)(O,{variant:`subtle`,size:`sm`,onClick:w,disabled:n,children:n?`Showing`:`Replay`})})})}var I=`3.9.0`,L=`Ashlr Verse`;function R(){return(0,j.jsx)(M,{title:`About`,children:(0,j.jsx)(`div`,{className:`${A.row} ${A.rowStacked}`,children:(0,j.jsxs)(`div`,{className:A.about,children:[(0,j.jsx)(`p`,{className:A.aboutName,children:L}),(0,j.jsxs)(`p`,{className:A.aboutLine,children:[(0,j.jsxs)(k,{mono:!0,children:[`v`,I]}),(0,j.jsx)(`span`,{children:`ashlr-hub — local-first command center for agentic engineers.`})]}),(0,j.jsx)(`p`,{className:A.aboutLine,children:`Every session, token and artifact stays on this machine. The console talks to one local server and no third-party service.`}),(0,j.jsx)(`p`,{className:A.aboutLine,children:(0,j.jsxs)(`a`,{className:A.link,href:`https://github.com/ashlrai/ashlr-hub`,target:`_blank`,rel:`noreferrer noopener`,children:[`Source and changelog`,(0,j.jsx)(ee,{size:12})]})})]})})})}var z=n(e(),1),B=/^#([0-9a-f]{3,8})$/i,V=/^(rgba?|hsla?)\(([^)]*)\)$/i;function H(e,t,n){return Math.min(n,Math.max(t,e))}function U(e,t){let n=e.trim();return n.endsWith(`%`)?Number.parseFloat(n)/100*t:Number.parseFloat(n)}function W(e,t,n){let r=(e%360+360)%360,i=(1-Math.abs(2*n-1))*t,a=i*(1-Math.abs(r/60%2-1)),o=n-i/2,s;return s=r<60?[i,a,0]:r<120?[a,i,0]:r<180?[0,i,a]:r<240?[0,a,i]:r<300?[a,0,i]:[i,0,a],{r:Math.round((s[0]+o)*255),g:Math.round((s[1]+o)*255),b:Math.round((s[2]+o)*255)}}function G(e){let t=[],n=0,r=``;for(let i of e){if(i===`(`?n+=1:i===`)`&&--n,i===`,`&&n===0){t.push(r),r=``;continue}r+=i}return t.push(r),t.map(e=>e.trim()).filter(e=>e.length>0)}function K(e){let t=e.trim(),n=/\s(\d*\.?\d+)%$/.exec(t);if(n)return{color:t.slice(0,n.index).trim(),pct:Number.parseFloat(n[1])};let r=/^(\d*\.?\d+)%\s/.exec(t);return r?{color:t.slice(r[0].length).trim(),pct:Number.parseFloat(r[1])}:{color:t,pct:null}}function q(e){let t=/^color-mix\((.*)\)$/is.exec(e.trim());if(!t)return null;let n=G(t[1]);if(n.length!==3||n[0].replace(/\s+/g,` `).trim()!==`in srgb`)return null;let r=K(n[1]),i=K(n[2]);if(!r||!i)return null;let a=J(r.color),o=J(i.color);if(!a||!o)return null;let s=r.pct,c=i.pct;s===null&&c===null?(s=50,c=50):s===null?s=100-c:c===null&&(c=100-s);let l=s+c;if(l<=0)return null;let u=s/l,d=c/l,f=a.a*u+o.a*d;if(f<=0)return{r:0,g:0,b:0,a:0};let p=e=>H(Math.round((a[e]*a.a*u+o[e]*o.a*d)/f),0,255);return{r:p(`r`),g:p(`g`),b:p(`b`),a:H(f,0,1)}}function J(e){let t=e.trim().toLowerCase();if(t===`transparent`)return{r:0,g:0,b:0,a:0};if(t.startsWith(`color-mix(`))return q(t);let n=B.exec(t);if(n){let e=n[1],t=e=>Number.parseInt(e.length===1?e+e:e,16);return e.length===3||e.length===4?{r:t(e[0]),g:t(e[1]),b:t(e[2]),a:e.length===4?t(e[3])/255:1}:e.length===6||e.length===8?{r:t(e.slice(0,2)),g:t(e.slice(2,4)),b:t(e.slice(4,6)),a:e.length===8?t(e.slice(6,8))/255:1}:null}let r=V.exec(t);if(!r)return null;let i=r[1],a=r[2].replace(/\//g,` `).split(/[\s,]+/).filter(Boolean);if(a.length<3)return null;let o=a.length>=4?H(U(a[3],1),0,1):1;if(i.startsWith(`rgb`))return{r:H(Math.round(U(a[0],255)),0,255),g:H(Math.round(U(a[1],255)),0,255),b:H(Math.round(U(a[2],255)),0,255),a:o};let s=Number.parseFloat(a[0]),c=H(U(a[1],1),0,1),l=H(U(a[2],1),0,1);return Number.isFinite(s)?{...W(s,c,l),a:o}:null}function Y(e,t){if(e.a>=1)return e;let n=e.a;return{r:Math.round(e.r*n+t.r*(1-n)),g:Math.round(e.g*n+t.g*(1-n)),b:Math.round(e.b*n+t.b*(1-n)),a:1}}function X(e){let t=e=>{let t=e/255;return t<=.03928?t/12.92:((t+.055)/1.055)**2.4};return .2126*t(e.r)+.7152*t(e.g)+.0722*t(e.b)}function Z(e,t,n=`#ffffff`){let r=J(t),i=J(e),a=J(n);if(!r||!i||!a)return null;let o=Y(r,a),s=X(Y(i,o)),c=X(o),[l,u]=s>=c?[s,c]:[c,s];return(l+.05)/(u+.05)}function Q(e,t,n,r=0){let i=W(e,H(t,0,100)/100,H(n+r,0,100)/100),a=e=>e.toString(16).padStart(2,`0`);return`#${a(i.r)}${a(i.g)}${a(i.b)}`}var re=`#ffffff`,ie=`#121214`,ae=[{value:`system`,label:`System`,icon:(0,j.jsx)(y,{})},{value:`light`,label:`Light`,icon:(0,j.jsx)(S,{})},{value:`dark`,label:`Dark`,icon:(0,j.jsx)(v,{})}],oe=[{value:`default`,label:`Default`},{value:`large`,label:`Large`},{value:`xlarge`,label:`Extra large`}],se=[{value:`comfortable`,label:`Comfortable`},{value:`compact`,label:`Compact`}],ce=[{value:`grotesk`,label:`Space Grotesk`},{value:`ui`,label:`UI sans`},{value:`mono`,label:`Mono`}],le=[{value:`sharp`,label:`Sharp`},{value:`default`,label:`Default`},{value:`soft`,label:`Soft`}];function ue(e,t){return`linear-gradient(to right, ${[0,60,120,180,240,300,360].map(n=>`${Q(n,e,t)} ${n/360*100}%`).join(`, `)})`}function de(e,t,n){let r=Q(e,t,n,-8),i=Q(e,t,n,14),a=Z(r,re),o=Z(i,ie),s=[];return a!==null&&a<4.5&&s.push(`light`),o!==null&&o<4.5&&s.push(`dark`),{lightRatio:a,darkRatio:o,failing:s}}function fe({appearance:e,onChange:t,onReset:n}){let i=(0,z.useId)(),s=(0,z.useId)(),c=(0,z.useId)(),l=(0,z.useId)(),u=(0,z.useId)(),p=o(e),m=de(e.accentH,e.accentS,e.accentL);return(0,j.jsxs)(M,{title:`Appearance`,action:(0,j.jsx)(O,{variant:`ghost`,size:`sm`,onClick:n,disabled:f(e),title:`Restore the shipped theme, accent, display size, density, font, radius and motion`,children:`Reset to defaults`}),children:[(0,j.jsx)(N,{label:`Theme`,description:`System follows your OS appearance setting.`,children:(0,j.jsx)(g,{"aria-label":`Theme`,options:ae,value:e.theme,onChange:e=>t({theme:e})})}),(0,j.jsx)(N,{label:`Accent`,labelId:i,description:p?`${p.label} — used for links, focus rings and the one primary action per surface.`:`Custom hue — used for links, focus rings and the one primary action per surface.`,children:(0,j.jsx)(`div`,{className:A.swatches,role:`radiogroup`,"aria-labelledby":i,children:r.map(e=>{let n=p?.id===e.id;return(0,j.jsx)(`button`,{type:`button`,role:`radio`,"aria-checked":n,"aria-label":e.label,tabIndex:n||!p&&e.id===r[0].id?0:-1,className:A.swatch,style:{background:Q(e.h,e.s,e.l)},onClick:()=>t({accentH:e.h,accentS:e.s,accentL:e.l}),children:n?(0,j.jsx)(`span`,{className:A.swatchCheck,children:(0,j.jsx)(te,{size:12})}):null},e.id)})})}),(0,j.jsx)(N,{label:`Accent channels`,description:`Hue, saturation and lightness are written straight onto the root element — the whole app follows as you drag.`,stacked:!0,children:(0,j.jsxs)(`div`,{className:A.sliderStack,children:[(0,j.jsx)(D,{id:s,label:`Hue`,min:0,max:359,value:e.accentH,valueLabel:`${e.accentH}°`,valueText:`${e.accentH} degrees`,trackImage:ue(e.accentS,e.accentL),onChange:e=>t({accentH:Number(e.currentTarget.value)})}),(0,j.jsx)(D,{id:c,label:`Saturation`,min:0,max:100,value:e.accentS,valueLabel:`${e.accentS}%`,valueText:`${e.accentS} percent`,onChange:e=>t({accentS:Number(e.currentTarget.value)})}),(0,j.jsx)(D,{id:l,label:`Lightness`,min:a.min,max:a.max,value:e.accentL,valueLabel:`${e.accentL}%`,valueText:`${e.accentL} percent`,onChange:e=>t({accentL:Number(e.currentTarget.value)})}),m.failing.length>0?(0,j.jsxs)(`p`,{className:A.contrastWarning,role:`status`,children:[(0,j.jsx)(x,{size:14}),(0,j.jsxs)(`span`,{children:[`This accent falls below 4.5:1 as link text in`,` `,m.failing.length===2?`both themes`:`${m.failing[0]} mode`,`. Raise or lower the lightness to keep it readable.`]})]}):null]})}),(0,j.jsx)(N,{label:`Display size`,description:`Scales text AND spacing together across the whole app, for a large display or a long session. Applies as you pick it.`,children:(0,j.jsx)(g,{"aria-label":`Display size`,options:oe,value:e.uiScale,onChange:e=>t({uiScale:e})})}),(0,j.jsx)(N,{label:`Density`,description:`Row height and control padding across every view. Independent of display size — a large, tightly packed interface is a valid choice.`,children:(0,j.jsx)(g,{"aria-label":`Density`,options:se,value:e.density,onChange:e=>t({density:e})})}),(0,j.jsx)(N,{label:`Display font`,description:`Titles, seat names, metrics and numerals. Body copy stays in the UI sans.`,children:(0,j.jsx)(g,{"aria-label":`Display font`,options:ce,value:e.displayFont,onChange:e=>t({displayFont:e})})}),(0,j.jsx)(N,{label:`Corner radius`,description:`Sharp reads like a terminal; soft reads like a consumer app.`,children:(0,j.jsx)(g,{"aria-label":`Corner radius`,options:le,value:e.radius,onChange:e=>t({radius:e})})}),(0,j.jsx)(N,{label:`Reduce motion`,htmlFor:u,description:`Collapses every transition to 1ms. Defaults to your system preference; turning it off here overrides that.`,children:(0,j.jsx)(C,{id:u,checked:e.reduceMotion,onChange:e=>t({reduceMotion:e}),"aria-label":`Reduce motion`})}),(0,j.jsx)(N,{label:`Preview`,description:`Live — these are the real primitives, not a picture of them.`,stacked:!0,children:(0,j.jsxs)(`div`,{className:A.preview,children:[(0,j.jsx)(`p`,{className:A.previewHeading,children:`Ashlr Verse`}),(0,j.jsxs)(`p`,{className:A.previewBody,children:[`Body copy stays in the UI sans at the reading measure; numerals like`,` `,(0,j.jsx)(`span`,{className:A.previewNumerals,children:`18,420 / 66,000`}),` use the display face.`]}),(0,j.jsxs)(`div`,{className:A.previewRow,children:[(0,j.jsx)(O,{variant:`primary`,size:`sm`,children:`Primary`}),(0,j.jsx)(O,{variant:`subtle`,size:`sm`,children:`Subtle`}),(0,j.jsx)(O,{variant:`ghost`,size:`sm`,children:`Ghost`}),(0,j.jsx)(O,{variant:`danger`,size:`sm`,children:`Destructive`})]}),(0,j.jsxs)(`div`,{className:A.previewRow,children:[(0,j.jsx)(d,{status:`running`}),(0,j.jsx)(k,{engine:`claude`,children:`Claude Max`}),(0,j.jsx)(k,{engine:`local`,mono:!0,children:`qwen3-coder`})]}),(0,j.jsx)(h,{value:18420,max:66e3,label:`Context`}),(0,j.jsx)(E,{"aria-label":`Preview field`,placeholder:`A field at the current density`,size:`sm`})]})})]})}function pe(e){if(e===null)return`locked`;let t=Math.max(0,Math.round((e-Date.now())/6e4));return t<=0?`expiring now`:`unlocked for ${t} more minute${t===1?``:`s`}`}function $(){let e=c(),t=s(),n=m(),[r,a]=(0,z.useState)(!1),o=(0,z.useRef)(null),l=typeof window>`u`?``:window.location.origin;async function f(){try{await navigator.clipboard.writeText(l),n.show(`Server address copied.`)}catch{n.show(`Could not reach the clipboard — select the field and copy manually.`,`danger`)}}async function h(){a(!1),await u(),n.show(`Disconnected. Paste the read token to reconnect.`)}return(0,j.jsxs)(M,{title:`Connection`,children:[(0,j.jsxs)(N,{label:`Server`,description:`This window talks to one local hub. Nothing leaves your machine.`,children:[(0,j.jsx)(`div`,{className:A.origin,children:(0,j.jsx)(E,{value:l,readOnly:!0,mono:!0,size:`sm`,"aria-label":`Server address`})}),(0,j.jsx)(O,{variant:`ghost`,size:`sm`,icon:(0,j.jsx)(ne,{}),onClick:()=>void f(),children:`Copy`})]}),(0,j.jsx)(N,{label:`Read session`,description:`A 15-minute signed cookie, renewed silently while this tab stays open.`,children:(0,j.jsx)(d,{status:e===`authenticated`?`connected`:e===`checking`?`checking`:`disconnected`,tone:e===`authenticated`?`success`:e===`checking`?`running`:`danger`})}),(0,j.jsxs)(N,{label:`Mutation hold`,description:`Every action that changes something needs the mutation token. It is held in memory only, never stored, and clears when this tab closes.`,children:[(0,j.jsx)(d,{status:t.hasHold?pe(t.heldUntil):`locked`,tone:t.hasHold?`success`:`neutral`}),(0,j.jsx)(O,{variant:`subtle`,size:`sm`,icon:(0,j.jsx)(b,{}),onClick:()=>{i(),n.show(`Mutation hold cleared.`)},disabled:!t.hasHold,children:`Lock now`})]}),(0,j.jsx)(N,{label:`Disconnect`,description:`Ends the read session in this browser and clears the mutation hold. Chats on the server are untouched.`,children:(0,j.jsx)(O,{variant:`danger`,size:`sm`,onClick:()=>a(!0),children:`Disconnect`})}),(0,j.jsx)(`div`,{className:A.row,children:(0,j.jsxs)(`p`,{className:A.note,children:[(0,j.jsx)(`span`,{className:A.noteIcon,children:(0,j.jsx)(_,{size:14})}),(0,j.jsxs)(`span`,{children:[(0,j.jsx)(`strong`,{children:`Where the tokens come from.`}),` Start the console with`,` `,(0,j.jsx)(`code`,{className:A.code,children:`ashlr verse`}),` — it prints a read token and a mutation token in that terminal, and opens this page. The desktop app injects both for you. Tokens are never displayed here, and the hub never writes them into a page, a log line or an API response. Lost them? Stop the server and start it again; it mints a fresh pair.`]})]})}),(0,j.jsx)(p,{open:r,onClose:()=>a(!1),titleId:`verse-settings-disconnect`,title:`Disconnect from this server?`,description:`You will need the read token again to get back in. Running sessions keep running on the server.`,initialFocusRef:o,children:(0,j.jsxs)(`div`,{className:A.previewRow,style:{justifyContent:`flex-end`},children:[(0,j.jsx)(O,{ref:o,variant:`subtle`,onClick:()=>a(!1),children:`Cancel`}),(0,j.jsx)(O,{variant:`danger`,onClick:()=>void h(),children:`Disconnect`})]})})]})}function me(){if(typeof navigator>`u`)return!0;let e=`${navigator.platform??``} ${navigator.userAgent??``}`;return/mac|iphone|ipad|ipod/i.test(e)}function he(e){return[{title:`Navigation`,items:[{keys:[e,`1`],action:`Chat`},{keys:[e,`2`],action:`Autonomy`},{keys:[e,`3`],action:`Approvals`},{keys:[e,`4`],action:`Usage`},{keys:[e,`5`],action:`Settings`},{keys:[e,`K`],action:`Quick switcher`},{keys:[e,`,`],action:`Settings`}]},{title:`Chat`,items:[{keys:[e,`N`],action:`New chat`},{keys:[`Enter`],action:`Send message`},{keys:[`Shift`,`Enter`],action:`New line`},{keys:[`Esc`],action:`Stop dictation / close overlay`}]}]}function ge(){let e=me()?`⌘`:`Ctrl`;return(0,j.jsx)(M,{title:`Keyboard`,children:(0,j.jsx)(`div`,{className:A.shortcuts,children:he(e).flatMap(e=>e.items.map(t=>(0,j.jsxs)(`div`,{className:A.shortcut,children:[(0,j.jsx)(`span`,{children:t.action}),(0,j.jsx)(`span`,{className:A.keys,children:t.keys.map(e=>(0,j.jsx)(`kbd`,{className:A.key,children:e},e))})]},`${e.title}-${t.action}-${t.keys.join(`+`)}`)))})})}function _e(){let{appearance:e,set:t,reset:n}=l();return(0,j.jsxs)(`div`,{className:A.section,children:[(0,j.jsx)(`header`,{className:A.strip,"data-app-region":`drag`,children:(0,j.jsx)(`h2`,{className:A.stripTitle,children:`Settings`})}),(0,j.jsx)(`div`,{className:A.scroll,children:(0,j.jsxs)(`div`,{className:A.column,children:[(0,j.jsx)(fe,{appearance:e,onChange:t,onReset:n}),(0,j.jsx)($,{}),(0,j.jsx)(ge,{}),(0,j.jsx)(F,{}),(0,j.jsx)(R,{})]})})]})}export{_e as SettingsSection};
2
- //# sourceMappingURL=SettingsSection-nJGGGYqv.js.map
1
+ import{a as e,n as t,o as n}from"./index-DO9a5IaB.js";import{C as r,O as i,S as a,T as o,_ as s,g as c,h as l,k as u,p as d,w as f}from"./global-F2K14nNo.js";import{t as p}from"./Dialog-CIjlQZqc.js";import{r as m}from"./RouteErrorBoundary-Chd-wt6w.js";import{n as h,t as g}from"./Segmented-DhGh7Y1s.js";import{f as _,h as v,l as ee,m as y,p as b,r as te,s as ne,t as x,y as S}from"./icons-B2fa40k6.js";import{B as C,C as w,S as T,U as E,V as D,W as O,z as k}from"./VerseConsoleApp-DYH7ottg.js";var A={section:`_section_3qpbp_5`,strip:`_strip_3qpbp_13`,stripTitle:`_stripTitle_3qpbp_25`,scroll:`_scroll_3qpbp_32`,column:`_column_3qpbp_38`,panel:`_panel_3qpbp_51`,panelHead:`_panelHead_3qpbp_56`,panelTitle:`_panelTitle_3qpbp_64`,panelBody:`_panelBody_3qpbp_74`,row:`_row_3qpbp_81`,rowStacked:`_rowStacked_3qpbp_90`,rowText:`_rowText_3qpbp_95`,rowLabel:`_rowLabel_3qpbp_99`,rowDescription:`_rowDescription_3qpbp_107`,rowControl:`_rowControl_3qpbp_114`,swatches:`_swatches_3qpbp_140`,swatch:`_swatch_3qpbp_140`,swatchCheck:`_swatchCheck_3qpbp_165`,sliderStack:`_sliderStack_3qpbp_173`,contrastWarning:`_contrastWarning_3qpbp_180`,preview:`_preview_3qpbp_197`,previewRow:`_previewRow_3qpbp_207`,previewHeading:`_previewHeading_3qpbp_214`,previewBody:`_previewBody_3qpbp_222`,previewNumerals:`_previewNumerals_3qpbp_228`,origin:`_origin_3qpbp_238`,note:`_note_3qpbp_242`,noteIcon:`_noteIcon_3qpbp_258`,code:`_code_3qpbp_249`,shortcuts:`_shortcuts_3qpbp_278`,shortcut:`_shortcut_3qpbp_278`,keys:`_keys_3qpbp_294`,key:`_key_3qpbp_294`,about:`_about_3qpbp_315`,aboutLine:`_aboutLine_3qpbp_321`,aboutName:`_aboutName_3qpbp_329`,link:`_link_3qpbp_338`},j=t();function M({title:e,action:t,children:n}){return(0,j.jsxs)(`section`,{className:A.panel,children:[(0,j.jsxs)(`div`,{className:A.panelHead,children:[(0,j.jsx)(`h3`,{className:A.panelTitle,children:e}),t]}),(0,j.jsx)(`div`,{className:A.panelBody,children:n})]})}function N({label:e,description:t,htmlFor:n,labelId:r,stacked:i=!1,children:a}){let o=(0,j.jsxs)(j.Fragment,{children:[(0,j.jsx)(`span`,{className:A.rowLabel,id:r,children:e}),t?(0,j.jsx)(`span`,{className:A.rowDescription,children:t}):null]});return(0,j.jsxs)(`div`,{className:`${A.row} ${i?A.rowStacked:``}`,children:[(0,j.jsx)(`div`,{className:A.rowText,children:n?(0,j.jsx)(`label`,{htmlFor:n,className:A.rowText,children:o}):o}),(0,j.jsx)(`div`,{className:A.rowControl,children:a})]})}function P(e,t){return e?`Last completed ${new Date(e).toLocaleDateString()}.`:t?`Skipped. Nothing was missed — it is all reachable from the sections themselves.`:`Not seen yet.`}function F(){let{completedAt:e,dismissedAt:t,open:n}=T();return(0,j.jsx)(M,{title:`Onboarding`,children:(0,j.jsx)(N,{label:`Replay the first-run tour`,description:`Your connected seats, whether a local runtime is available, and what each of the three stop controls really halts. ${P(e,t)}`,children:(0,j.jsx)(O,{variant:`subtle`,size:`sm`,onClick:w,disabled:n,children:n?`Showing`:`Replay`})})})}var I=`3.9.1`,L=`Ashlr Verse`;function R(){return(0,j.jsx)(M,{title:`About`,children:(0,j.jsx)(`div`,{className:`${A.row} ${A.rowStacked}`,children:(0,j.jsxs)(`div`,{className:A.about,children:[(0,j.jsx)(`p`,{className:A.aboutName,children:L}),(0,j.jsxs)(`p`,{className:A.aboutLine,children:[(0,j.jsxs)(k,{mono:!0,children:[`v`,I]}),(0,j.jsx)(`span`,{children:`ashlr-hub — local-first command center for agentic engineers.`})]}),(0,j.jsx)(`p`,{className:A.aboutLine,children:`Every session, token and artifact stays on this machine. The console talks to one local server and no third-party service.`}),(0,j.jsx)(`p`,{className:A.aboutLine,children:(0,j.jsxs)(`a`,{className:A.link,href:`https://github.com/ashlrai/ashlr-hub`,target:`_blank`,rel:`noreferrer noopener`,children:[`Source and changelog`,(0,j.jsx)(ee,{size:12})]})})]})})})}var z=n(e(),1),B=/^#([0-9a-f]{3,8})$/i,V=/^(rgba?|hsla?)\(([^)]*)\)$/i;function H(e,t,n){return Math.min(n,Math.max(t,e))}function U(e,t){let n=e.trim();return n.endsWith(`%`)?Number.parseFloat(n)/100*t:Number.parseFloat(n)}function W(e,t,n){let r=(e%360+360)%360,i=(1-Math.abs(2*n-1))*t,a=i*(1-Math.abs(r/60%2-1)),o=n-i/2,s;return s=r<60?[i,a,0]:r<120?[a,i,0]:r<180?[0,i,a]:r<240?[0,a,i]:r<300?[a,0,i]:[i,0,a],{r:Math.round((s[0]+o)*255),g:Math.round((s[1]+o)*255),b:Math.round((s[2]+o)*255)}}function G(e){let t=[],n=0,r=``;for(let i of e){if(i===`(`?n+=1:i===`)`&&--n,i===`,`&&n===0){t.push(r),r=``;continue}r+=i}return t.push(r),t.map(e=>e.trim()).filter(e=>e.length>0)}function K(e){let t=e.trim(),n=/\s(\d*\.?\d+)%$/.exec(t);if(n)return{color:t.slice(0,n.index).trim(),pct:Number.parseFloat(n[1])};let r=/^(\d*\.?\d+)%\s/.exec(t);return r?{color:t.slice(r[0].length).trim(),pct:Number.parseFloat(r[1])}:{color:t,pct:null}}function q(e){let t=/^color-mix\((.*)\)$/is.exec(e.trim());if(!t)return null;let n=G(t[1]);if(n.length!==3||n[0].replace(/\s+/g,` `).trim()!==`in srgb`)return null;let r=K(n[1]),i=K(n[2]);if(!r||!i)return null;let a=J(r.color),o=J(i.color);if(!a||!o)return null;let s=r.pct,c=i.pct;s===null&&c===null?(s=50,c=50):s===null?s=100-c:c===null&&(c=100-s);let l=s+c;if(l<=0)return null;let u=s/l,d=c/l,f=a.a*u+o.a*d;if(f<=0)return{r:0,g:0,b:0,a:0};let p=e=>H(Math.round((a[e]*a.a*u+o[e]*o.a*d)/f),0,255);return{r:p(`r`),g:p(`g`),b:p(`b`),a:H(f,0,1)}}function J(e){let t=e.trim().toLowerCase();if(t===`transparent`)return{r:0,g:0,b:0,a:0};if(t.startsWith(`color-mix(`))return q(t);let n=B.exec(t);if(n){let e=n[1],t=e=>Number.parseInt(e.length===1?e+e:e,16);return e.length===3||e.length===4?{r:t(e[0]),g:t(e[1]),b:t(e[2]),a:e.length===4?t(e[3])/255:1}:e.length===6||e.length===8?{r:t(e.slice(0,2)),g:t(e.slice(2,4)),b:t(e.slice(4,6)),a:e.length===8?t(e.slice(6,8))/255:1}:null}let r=V.exec(t);if(!r)return null;let i=r[1],a=r[2].replace(/\//g,` `).split(/[\s,]+/).filter(Boolean);if(a.length<3)return null;let o=a.length>=4?H(U(a[3],1),0,1):1;if(i.startsWith(`rgb`))return{r:H(Math.round(U(a[0],255)),0,255),g:H(Math.round(U(a[1],255)),0,255),b:H(Math.round(U(a[2],255)),0,255),a:o};let s=Number.parseFloat(a[0]),c=H(U(a[1],1),0,1),l=H(U(a[2],1),0,1);return Number.isFinite(s)?{...W(s,c,l),a:o}:null}function Y(e,t){if(e.a>=1)return e;let n=e.a;return{r:Math.round(e.r*n+t.r*(1-n)),g:Math.round(e.g*n+t.g*(1-n)),b:Math.round(e.b*n+t.b*(1-n)),a:1}}function X(e){let t=e=>{let t=e/255;return t<=.03928?t/12.92:((t+.055)/1.055)**2.4};return .2126*t(e.r)+.7152*t(e.g)+.0722*t(e.b)}function Z(e,t,n=`#ffffff`){let r=J(t),i=J(e),a=J(n);if(!r||!i||!a)return null;let o=Y(r,a),s=X(Y(i,o)),c=X(o),[l,u]=s>=c?[s,c]:[c,s];return(l+.05)/(u+.05)}function Q(e,t,n,r=0){let i=W(e,H(t,0,100)/100,H(n+r,0,100)/100),a=e=>e.toString(16).padStart(2,`0`);return`#${a(i.r)}${a(i.g)}${a(i.b)}`}var re=`#ffffff`,ie=`#121214`,ae=[{value:`system`,label:`System`,icon:(0,j.jsx)(y,{})},{value:`light`,label:`Light`,icon:(0,j.jsx)(S,{})},{value:`dark`,label:`Dark`,icon:(0,j.jsx)(v,{})}],oe=[{value:`default`,label:`Default`},{value:`large`,label:`Large`},{value:`xlarge`,label:`Extra large`}],se=[{value:`comfortable`,label:`Comfortable`},{value:`compact`,label:`Compact`}],ce=[{value:`grotesk`,label:`Space Grotesk`},{value:`ui`,label:`UI sans`},{value:`mono`,label:`Mono`}],le=[{value:`sharp`,label:`Sharp`},{value:`default`,label:`Default`},{value:`soft`,label:`Soft`}];function ue(e,t){return`linear-gradient(to right, ${[0,60,120,180,240,300,360].map(n=>`${Q(n,e,t)} ${n/360*100}%`).join(`, `)})`}function de(e,t,n){let r=Q(e,t,n,-8),i=Q(e,t,n,14),a=Z(r,re),o=Z(i,ie),s=[];return a!==null&&a<4.5&&s.push(`light`),o!==null&&o<4.5&&s.push(`dark`),{lightRatio:a,darkRatio:o,failing:s}}function fe({appearance:e,onChange:t,onReset:n}){let i=(0,z.useId)(),s=(0,z.useId)(),c=(0,z.useId)(),l=(0,z.useId)(),u=(0,z.useId)(),p=o(e),m=de(e.accentH,e.accentS,e.accentL);return(0,j.jsxs)(M,{title:`Appearance`,action:(0,j.jsx)(O,{variant:`ghost`,size:`sm`,onClick:n,disabled:f(e),title:`Restore the shipped theme, accent, display size, density, font, radius and motion`,children:`Reset to defaults`}),children:[(0,j.jsx)(N,{label:`Theme`,description:`System follows your OS appearance setting.`,children:(0,j.jsx)(g,{"aria-label":`Theme`,options:ae,value:e.theme,onChange:e=>t({theme:e})})}),(0,j.jsx)(N,{label:`Accent`,labelId:i,description:p?`${p.label} — used for links, focus rings and the one primary action per surface.`:`Custom hue — used for links, focus rings and the one primary action per surface.`,children:(0,j.jsx)(`div`,{className:A.swatches,role:`radiogroup`,"aria-labelledby":i,children:r.map(e=>{let n=p?.id===e.id;return(0,j.jsx)(`button`,{type:`button`,role:`radio`,"aria-checked":n,"aria-label":e.label,tabIndex:n||!p&&e.id===r[0].id?0:-1,className:A.swatch,style:{background:Q(e.h,e.s,e.l)},onClick:()=>t({accentH:e.h,accentS:e.s,accentL:e.l}),children:n?(0,j.jsx)(`span`,{className:A.swatchCheck,children:(0,j.jsx)(te,{size:12})}):null},e.id)})})}),(0,j.jsx)(N,{label:`Accent channels`,description:`Hue, saturation and lightness are written straight onto the root element — the whole app follows as you drag.`,stacked:!0,children:(0,j.jsxs)(`div`,{className:A.sliderStack,children:[(0,j.jsx)(D,{id:s,label:`Hue`,min:0,max:359,value:e.accentH,valueLabel:`${e.accentH}°`,valueText:`${e.accentH} degrees`,trackImage:ue(e.accentS,e.accentL),onChange:e=>t({accentH:Number(e.currentTarget.value)})}),(0,j.jsx)(D,{id:c,label:`Saturation`,min:0,max:100,value:e.accentS,valueLabel:`${e.accentS}%`,valueText:`${e.accentS} percent`,onChange:e=>t({accentS:Number(e.currentTarget.value)})}),(0,j.jsx)(D,{id:l,label:`Lightness`,min:a.min,max:a.max,value:e.accentL,valueLabel:`${e.accentL}%`,valueText:`${e.accentL} percent`,onChange:e=>t({accentL:Number(e.currentTarget.value)})}),m.failing.length>0?(0,j.jsxs)(`p`,{className:A.contrastWarning,role:`status`,children:[(0,j.jsx)(x,{size:14}),(0,j.jsxs)(`span`,{children:[`This accent falls below 4.5:1 as link text in`,` `,m.failing.length===2?`both themes`:`${m.failing[0]} mode`,`. Raise or lower the lightness to keep it readable.`]})]}):null]})}),(0,j.jsx)(N,{label:`Display size`,description:`Scales text AND spacing together across the whole app, for a large display or a long session. Applies as you pick it.`,children:(0,j.jsx)(g,{"aria-label":`Display size`,options:oe,value:e.uiScale,onChange:e=>t({uiScale:e})})}),(0,j.jsx)(N,{label:`Density`,description:`Row height and control padding across every view. Independent of display size — a large, tightly packed interface is a valid choice.`,children:(0,j.jsx)(g,{"aria-label":`Density`,options:se,value:e.density,onChange:e=>t({density:e})})}),(0,j.jsx)(N,{label:`Display font`,description:`Titles, seat names, metrics and numerals. Body copy stays in the UI sans.`,children:(0,j.jsx)(g,{"aria-label":`Display font`,options:ce,value:e.displayFont,onChange:e=>t({displayFont:e})})}),(0,j.jsx)(N,{label:`Corner radius`,description:`Sharp reads like a terminal; soft reads like a consumer app.`,children:(0,j.jsx)(g,{"aria-label":`Corner radius`,options:le,value:e.radius,onChange:e=>t({radius:e})})}),(0,j.jsx)(N,{label:`Reduce motion`,htmlFor:u,description:`Collapses every transition to 1ms. Defaults to your system preference; turning it off here overrides that.`,children:(0,j.jsx)(C,{id:u,checked:e.reduceMotion,onChange:e=>t({reduceMotion:e}),"aria-label":`Reduce motion`})}),(0,j.jsx)(N,{label:`Preview`,description:`Live — these are the real primitives, not a picture of them.`,stacked:!0,children:(0,j.jsxs)(`div`,{className:A.preview,children:[(0,j.jsx)(`p`,{className:A.previewHeading,children:`Ashlr Verse`}),(0,j.jsxs)(`p`,{className:A.previewBody,children:[`Body copy stays in the UI sans at the reading measure; numerals like`,` `,(0,j.jsx)(`span`,{className:A.previewNumerals,children:`18,420 / 66,000`}),` use the display face.`]}),(0,j.jsxs)(`div`,{className:A.previewRow,children:[(0,j.jsx)(O,{variant:`primary`,size:`sm`,children:`Primary`}),(0,j.jsx)(O,{variant:`subtle`,size:`sm`,children:`Subtle`}),(0,j.jsx)(O,{variant:`ghost`,size:`sm`,children:`Ghost`}),(0,j.jsx)(O,{variant:`danger`,size:`sm`,children:`Destructive`})]}),(0,j.jsxs)(`div`,{className:A.previewRow,children:[(0,j.jsx)(d,{status:`running`}),(0,j.jsx)(k,{engine:`claude`,children:`Claude Max`}),(0,j.jsx)(k,{engine:`local`,mono:!0,children:`qwen3-coder`})]}),(0,j.jsx)(h,{value:18420,max:66e3,label:`Context`}),(0,j.jsx)(E,{"aria-label":`Preview field`,placeholder:`A field at the current density`,size:`sm`})]})})]})}function pe(e){if(e===null)return`locked`;let t=Math.max(0,Math.round((e-Date.now())/6e4));return t<=0?`expiring now`:`unlocked for ${t} more minute${t===1?``:`s`}`}function $(){let e=c(),t=s(),n=m(),[r,a]=(0,z.useState)(!1),o=(0,z.useRef)(null),l=typeof window>`u`?``:window.location.origin;async function f(){try{await navigator.clipboard.writeText(l),n.show(`Server address copied.`)}catch{n.show(`Could not reach the clipboard — select the field and copy manually.`,`danger`)}}async function h(){a(!1),await u(),n.show(`Disconnected. Paste the read token to reconnect.`)}return(0,j.jsxs)(M,{title:`Connection`,children:[(0,j.jsxs)(N,{label:`Server`,description:`This window talks to one local hub. Nothing leaves your machine.`,children:[(0,j.jsx)(`div`,{className:A.origin,children:(0,j.jsx)(E,{value:l,readOnly:!0,mono:!0,size:`sm`,"aria-label":`Server address`})}),(0,j.jsx)(O,{variant:`ghost`,size:`sm`,icon:(0,j.jsx)(ne,{}),onClick:()=>void f(),children:`Copy`})]}),(0,j.jsx)(N,{label:`Read session`,description:`A 15-minute signed cookie, renewed silently while this tab stays open.`,children:(0,j.jsx)(d,{status:e===`authenticated`?`connected`:e===`checking`?`checking`:`disconnected`,tone:e===`authenticated`?`success`:e===`checking`?`running`:`danger`})}),(0,j.jsxs)(N,{label:`Mutation hold`,description:`Every action that changes something needs the mutation token. It is held in memory only, never stored, and clears when this tab closes.`,children:[(0,j.jsx)(d,{status:t.hasHold?pe(t.heldUntil):`locked`,tone:t.hasHold?`success`:`neutral`}),(0,j.jsx)(O,{variant:`subtle`,size:`sm`,icon:(0,j.jsx)(b,{}),onClick:()=>{i(),n.show(`Mutation hold cleared.`)},disabled:!t.hasHold,children:`Lock now`})]}),(0,j.jsx)(N,{label:`Disconnect`,description:`Ends the read session in this browser and clears the mutation hold. Chats on the server are untouched.`,children:(0,j.jsx)(O,{variant:`danger`,size:`sm`,onClick:()=>a(!0),children:`Disconnect`})}),(0,j.jsx)(`div`,{className:A.row,children:(0,j.jsxs)(`p`,{className:A.note,children:[(0,j.jsx)(`span`,{className:A.noteIcon,children:(0,j.jsx)(_,{size:14})}),(0,j.jsxs)(`span`,{children:[(0,j.jsx)(`strong`,{children:`Where the tokens come from.`}),` Start the console with`,` `,(0,j.jsx)(`code`,{className:A.code,children:`ashlr verse`}),` — it prints a read token and a mutation token in that terminal, and opens this page. The desktop app injects both for you. Tokens are never displayed here, and the hub never writes them into a page, a log line or an API response. Lost them? Stop the server and start it again; it mints a fresh pair.`]})]})}),(0,j.jsx)(p,{open:r,onClose:()=>a(!1),titleId:`verse-settings-disconnect`,title:`Disconnect from this server?`,description:`You will need the read token again to get back in. Running sessions keep running on the server.`,initialFocusRef:o,children:(0,j.jsxs)(`div`,{className:A.previewRow,style:{justifyContent:`flex-end`},children:[(0,j.jsx)(O,{ref:o,variant:`subtle`,onClick:()=>a(!1),children:`Cancel`}),(0,j.jsx)(O,{variant:`danger`,onClick:()=>void h(),children:`Disconnect`})]})})]})}function me(){if(typeof navigator>`u`)return!0;let e=`${navigator.platform??``} ${navigator.userAgent??``}`;return/mac|iphone|ipad|ipod/i.test(e)}function he(e){return[{title:`Navigation`,items:[{keys:[e,`1`],action:`Chat`},{keys:[e,`2`],action:`Autonomy`},{keys:[e,`3`],action:`Approvals`},{keys:[e,`4`],action:`Usage`},{keys:[e,`5`],action:`Settings`},{keys:[e,`K`],action:`Quick switcher`},{keys:[e,`,`],action:`Settings`}]},{title:`Chat`,items:[{keys:[e,`N`],action:`New chat`},{keys:[`Enter`],action:`Send message`},{keys:[`Shift`,`Enter`],action:`New line`},{keys:[`Esc`],action:`Stop dictation / close overlay`}]}]}function ge(){let e=me()?`⌘`:`Ctrl`;return(0,j.jsx)(M,{title:`Keyboard`,children:(0,j.jsx)(`div`,{className:A.shortcuts,children:he(e).flatMap(e=>e.items.map(t=>(0,j.jsxs)(`div`,{className:A.shortcut,children:[(0,j.jsx)(`span`,{children:t.action}),(0,j.jsx)(`span`,{className:A.keys,children:t.keys.map(e=>(0,j.jsx)(`kbd`,{className:A.key,children:e},e))})]},`${e.title}-${t.action}-${t.keys.join(`+`)}`)))})})}function _e(){let{appearance:e,set:t,reset:n}=l();return(0,j.jsxs)(`div`,{className:A.section,children:[(0,j.jsx)(`header`,{className:A.strip,"data-app-region":`drag`,children:(0,j.jsx)(`h2`,{className:A.stripTitle,children:`Settings`})}),(0,j.jsx)(`div`,{className:A.scroll,children:(0,j.jsxs)(`div`,{className:A.column,children:[(0,j.jsx)(fe,{appearance:e,onChange:t,onReset:n}),(0,j.jsx)($,{}),(0,j.jsx)(ge,{}),(0,j.jsx)(F,{}),(0,j.jsx)(R,{})]})})]})}export{_e as SettingsSection};
2
+ //# sourceMappingURL=SettingsSection-Bqimu_Mg.js.map