@ashlr/hub 3.5.1 → 3.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +165 -0
- package/README.md +3 -0
- package/dist/build-identity.json +2 -2
- package/dist/cli/activation.d.ts +41 -0
- package/dist/cli/activation.js +485 -0
- package/dist/cli/activation.js.map +1 -0
- package/dist/core/daemon/cutoff-checkpoint-scheduler.js +7 -0
- package/dist/core/daemon/cutoff-checkpoint-scheduler.js.map +1 -1
- package/dist/core/daemon/loop.d.ts +19 -0
- package/dist/core/daemon/loop.js +235 -5
- package/dist/core/daemon/loop.js.map +1 -1
- package/dist/core/daemon/overnight-status.d.ts +152 -0
- package/dist/core/daemon/overnight-status.js +275 -0
- package/dist/core/daemon/overnight-status.js.map +1 -0
- package/dist/core/daemon/post-merge-halt.d.ts +212 -0
- package/dist/core/daemon/post-merge-halt.js +503 -0
- package/dist/core/daemon/post-merge-halt.js.map +1 -0
- package/dist/core/daemon/run-window.d.ts +218 -0
- package/dist/core/daemon/run-window.js +420 -0
- package/dist/core/daemon/run-window.js.map +1 -0
- package/dist/core/fleet/generated-repair-resolution.d.ts +36 -0
- package/dist/core/fleet/generated-repair-resolution.js +245 -0
- package/dist/core/fleet/generated-repair-resolution.js.map +1 -0
- package/dist/core/local-runtime/llama/port.d.ts +17 -0
- package/dist/core/local-runtime/llama/port.js +47 -0
- package/dist/core/local-runtime/llama/port.js.map +1 -0
- package/dist/core/local-runtime/llama/proxy-host.d.ts +54 -0
- package/dist/core/local-runtime/llama/proxy-host.js +159 -0
- package/dist/core/local-runtime/llama/proxy-host.js.map +1 -0
- package/dist/core/local-runtime/llama/proxy-process.d.ts +148 -0
- package/dist/core/local-runtime/llama/proxy-process.js +250 -0
- package/dist/core/local-runtime/llama/proxy-process.js.map +1 -0
- package/dist/core/local-runtime/llama/proxy-record.d.ts +40 -0
- package/dist/core/local-runtime/llama/proxy-record.js +133 -0
- package/dist/core/local-runtime/llama/proxy-record.js.map +1 -0
- package/dist/core/local-runtime/llama/proxy-supervisor.d.ts +118 -0
- package/dist/core/local-runtime/llama/proxy-supervisor.js +511 -0
- package/dist/core/local-runtime/llama/proxy-supervisor.js.map +1 -0
- package/dist/core/policy/local-only.d.ts +142 -2
- package/dist/core/policy/local-only.js +204 -15
- package/dist/core/policy/local-only.js.map +1 -1
- package/dist/core/run/budget.d.ts +22 -8
- package/dist/core/run/budget.js +80 -12
- package/dist/core/run/budget.js.map +1 -1
- package/dist/core/run/sandboxed-engine.js +3 -3
- package/dist/core/run/sandboxed-engine.js.map +1 -1
- package/dist/core/universe/builtins/preparation/manifest.json +1 -1
- package/dist/core/universe/builtins/preparation/preparation-bridge.mjs +61 -9
- package/dist/core/universe/builtins/preparation/preparation-verification-fixtures.mjs +60 -8
- package/dist/core/verse/session-engine.d.ts +42 -1
- package/dist/core/verse/session-engine.js +79 -2
- package/dist/core/verse/session-engine.js.map +1 -1
- package/dist/core/web/public/next/assets/{App-BbVoUCIi.js → App-BsbJFA2j.js} +2 -2
- package/dist/core/web/public/next/assets/{App-BbVoUCIi.js.map → App-BsbJFA2j.js.map} +1 -1
- package/dist/core/web/public/next/assets/{ApprovalsSection-BW9iVjUa.js → ApprovalsSection-2CCSoBTJ.js} +2 -2
- package/dist/core/web/public/next/assets/{ApprovalsSection-BW9iVjUa.js.map → ApprovalsSection-2CCSoBTJ.js.map} +1 -1
- package/dist/core/web/public/next/assets/{AutonomySection-Cpk_oEhc.css → AutonomySection-CHjxJt2z.css} +1 -1
- package/dist/core/web/public/next/assets/AutonomySection-yeNNUPur.js +2 -0
- package/dist/core/web/public/next/assets/AutonomySection-yeNNUPur.js.map +1 -0
- package/dist/core/web/public/next/assets/ChatSection-BfnLe5dT.css +1 -0
- package/dist/core/web/public/next/assets/ChatSection-DwDDnCZQ.js +80 -0
- package/dist/core/web/public/next/assets/ChatSection-DwDDnCZQ.js.map +1 -0
- package/dist/core/web/public/next/assets/{ConfirmDialog-CXevY3Au.js → ConfirmDialog-C363ty_c.js} +2 -2
- package/dist/core/web/public/next/assets/{ConfirmDialog-CXevY3Au.js.map → ConfirmDialog-C363ty_c.js.map} +1 -1
- package/dist/core/web/public/next/assets/{Dialog-Bn0CgEGz.js → Dialog-nK15L1M0.js} +2 -2
- package/dist/core/web/public/next/assets/{Dialog-Bn0CgEGz.js.map → Dialog-nK15L1M0.js.map} +1 -1
- package/dist/core/web/public/next/assets/{DiffViewer-QKyisN2L.js → DiffViewer-D369eZe8.js} +2 -2
- package/dist/core/web/public/next/assets/{DiffViewer-QKyisN2L.js.map → DiffViewer-D369eZe8.js.map} +1 -1
- package/dist/core/web/public/next/assets/McpSection-D88QlDpG.css +1 -0
- package/dist/core/web/public/next/assets/McpSection-DVgiSYG1.js +2 -0
- package/dist/core/web/public/next/assets/McpSection-DVgiSYG1.js.map +1 -0
- package/dist/core/web/public/next/assets/Meter-CLcr-4wS.css +1 -0
- package/dist/core/web/public/next/assets/Meter-Cmo1pLbk.js +2 -0
- package/dist/core/web/public/next/assets/Meter-Cmo1pLbk.js.map +1 -0
- package/dist/core/web/public/next/assets/{MutationTokenDialog-DbSHPy3w.js → MutationTokenDialog-B9I4PAcp.js} +2 -2
- package/dist/core/web/public/next/assets/{MutationTokenDialog-DbSHPy3w.js.map → MutationTokenDialog-B9I4PAcp.js.map} +1 -1
- package/dist/core/web/public/next/assets/{ResourcePoolConsoleApp-C-PCgCzo.js → ResourcePoolConsoleApp-z6tJj3s9.js} +2 -2
- package/dist/core/web/public/next/assets/{ResourcePoolConsoleApp-C-PCgCzo.js.map → ResourcePoolConsoleApp-z6tJj3s9.js.map} +1 -1
- package/dist/core/web/public/next/assets/{RouteErrorBoundary-Dw8ZjmkS.js → RouteErrorBoundary-OSajL45F.js} +2 -2
- package/dist/core/web/public/next/assets/{RouteErrorBoundary-Dw8ZjmkS.js.map → RouteErrorBoundary-OSajL45F.js.map} +1 -1
- package/dist/core/web/public/next/assets/SettingsSection-B3YH2Npi.js +2 -0
- package/dist/core/web/public/next/assets/SettingsSection-B3YH2Npi.js.map +1 -0
- package/dist/core/web/public/next/assets/{UniverseConsoleApp-Bk5HipM0.js → UniverseConsoleApp-wEKIzl1M.js} +2 -2
- package/dist/core/web/public/next/assets/{UniverseConsoleApp-Bk5HipM0.js.map → UniverseConsoleApp-wEKIzl1M.js.map} +1 -1
- package/dist/core/web/public/next/assets/{UniverseView-D8k9q0ce.js → UniverseView-CQVpJqqI.js} +2 -2
- package/dist/core/web/public/next/assets/{UniverseView-D8k9q0ce.js.map → UniverseView-CQVpJqqI.js.map} +1 -1
- package/dist/core/web/public/next/assets/{UsageSection-B2E06ZH1.js → UsageSection-dToBThXe.js} +2 -2
- package/dist/core/web/public/next/assets/{UsageSection-B2E06ZH1.js.map → UsageSection-dToBThXe.js.map} +1 -1
- package/dist/core/web/public/next/assets/VerseConsoleApp-CMkCizvL.css +1 -0
- package/dist/core/web/public/next/assets/VerseConsoleApp-DHU32JCQ.js +3 -0
- package/dist/core/web/public/next/assets/VerseConsoleApp-DHU32JCQ.js.map +1 -0
- package/dist/core/web/public/next/assets/{charts-DSUpczxG.js → charts-BopViIha.js} +2 -2
- package/dist/core/web/public/next/assets/{charts-DSUpczxG.js.map → charts-BopViIha.js.map} +1 -1
- package/dist/core/web/public/next/assets/global-DMWakEhP.js +2 -0
- package/dist/core/web/public/next/assets/global-DMWakEhP.js.map +1 -0
- package/dist/core/web/public/next/assets/global-z-JfD3ow.css +1 -0
- package/dist/core/web/public/next/assets/{icons-BxOX8-XB.js → icons-L06pQH6j.js} +2 -2
- package/dist/core/web/public/next/assets/{icons-BxOX8-XB.js.map → icons-L06pQH6j.js.map} +1 -1
- package/dist/core/web/public/next/assets/{index-BMRIET7F.js → index-CXgVQHek.js} +3 -3
- package/dist/core/web/public/next/assets/{index-BMRIET7F.js.map → index-CXgVQHek.js.map} +1 -1
- package/dist/core/web/public/next/assets/{queries-AxnVa02h.js → queries-D3sSlan1.js} +2 -2
- package/dist/core/web/public/next/assets/{queries-AxnVa02h.js.map → queries-D3sSlan1.js.map} +1 -1
- package/dist/core/web/public/next/assets/{use-guarded-action-BO6c94Wp.js → use-guarded-action-BxfJ9E-n.js} +2 -2
- package/dist/core/web/public/next/assets/{use-guarded-action-BO6c94Wp.js.map → use-guarded-action-BxfJ9E-n.js.map} +1 -1
- package/dist/core/web/public/next/assets/workspace-model-v0a1gl-C.js +2 -0
- package/dist/core/web/public/next/assets/workspace-model-v0a1gl-C.js.map +1 -0
- package/dist/core/web/public/next/index.html +1 -1
- package/dist/release-dependency-inventory.json +1 -1
- package/package.json +1 -1
- package/dist/core/web/public/next/assets/AutonomySection-CRqHUhED.js +0 -2
- package/dist/core/web/public/next/assets/AutonomySection-CRqHUhED.js.map +0 -1
- package/dist/core/web/public/next/assets/ChatSection-5irrh6Jw.js +0 -80
- package/dist/core/web/public/next/assets/ChatSection-5irrh6Jw.js.map +0 -1
- package/dist/core/web/public/next/assets/ChatSection-LQiAn6Eu.css +0 -1
- package/dist/core/web/public/next/assets/SettingsSection-CWxtTNlD.js +0 -2
- package/dist/core/web/public/next/assets/SettingsSection-CWxtTNlD.js.map +0 -1
- package/dist/core/web/public/next/assets/VerseConsoleApp-CRfZUV-T.js +0 -3
- package/dist/core/web/public/next/assets/VerseConsoleApp-CRfZUV-T.js.map +0 -1
- package/dist/core/web/public/next/assets/VerseConsoleApp-D2E3mU9a.css +0 -1
- package/dist/core/web/public/next/assets/global-C4jfYHrj.js +0 -2
- package/dist/core/web/public/next/assets/global-C4jfYHrj.js.map +0 -1
- package/dist/core/web/public/next/assets/global-D463gG1X.css +0 -1
- package/dist/core/web/public/next/assets/workspace-model-cE4LkKpE.js +0 -2
- package/dist/core/web/public/next/assets/workspace-model-cE4LkKpE.js.map +0 -1
package/dist/core/web/public/next/assets/{ConfirmDialog-CXevY3Au.js → ConfirmDialog-C363ty_c.js}
RENAMED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{a as e,n as t,o as n}from"./index-
|
|
2
|
-
//# sourceMappingURL=ConfirmDialog-
|
|
1
|
+
import{a as e,n as t,o as n}from"./index-CXgVQHek.js";import{G as r,I as i,W as a,j as o,u as s}from"./global-DMWakEhP.js";import{t as c}from"./Dialog-nK15L1M0.js";var l=class extends Error{constructor(){super(`No mutation token held for this session yet.`),this.name=`MissingMutationTokenError`}};async function u(e,t){let n=o();if(!n)throw new l;let r=await s(e,t,n);return i(),r}async function d(){await u(`/api/fleet/pause`,{}),a(`dashboard-snapshot`),a(`control-snapshot`),a(`fleet`),a(`fleet-activity`)}async function f(){await u(`/api/fleet/resume`,{}),a(`dashboard-snapshot`),a(`control-snapshot`),a(`fleet`),a(`fleet-activity`)}async function p(e){await u(`/api/inbox/${encodeURIComponent(e)}/approve`,{}),a(`inbox`),a(`dashboard-snapshot`),a(`proposal-detail-${e}`),r(`inbox-list:`)}async function m(e){await u(`/api/inbox/${encodeURIComponent(e)}/reject`,{}),a(`inbox`),a(`dashboard-snapshot`),a(`proposal-detail-${e}`),r(`inbox-list:`)}var h=n(e(),1),g={body:`_body_15hsz_1`,error:`_error_15hsz_8`,actions:`_actions_15hsz_17`,cancel:`_cancel_15hsz_23`,confirm:`_confirm_15hsz_33`,destructive:`_destructive_15hsz_48`},_=t();function v({open:e,onClose:t,title:n,body:r,confirmLabel:i,destructive:a,onConfirm:o,busy:s,error:l}){let u=(0,h.useId)(),d=(0,h.useRef)(null);return(0,_.jsxs)(c,{open:e,onClose:t,titleId:u,title:n,initialFocusRef:d,children:[(0,_.jsx)(`div`,{className:g.body,children:r}),l?(0,_.jsx)(`p`,{className:g.error,role:`alert`,children:l}):null,(0,_.jsxs)(`div`,{className:g.actions,children:[(0,_.jsx)(`button`,{type:`button`,className:g.cancel,onClick:t,disabled:s,children:`Cancel`}),(0,_.jsx)(`button`,{ref:d,type:`button`,className:a?g.destructive:g.confirm,onClick:o,disabled:s,children:s?`Working…`:i})]})]})}export{f as a,m as i,p as n,d as r,v as t};
|
|
2
|
+
//# sourceMappingURL=ConfirmDialog-C363ty_c.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ConfirmDialog-
|
|
1
|
+
{"version":3,"file":"ConfirmDialog-C363ty_c.js","names":[],"sources":["../../../../../../src/web-ui/data/mutations.ts","../../../../../../src/web-ui/routes/inbox/ConfirmDialog.module.css","../../../../../../src/web-ui/routes/inbox/ConfirmDialog.tsx"],"sourcesContent":["/**\n * data/mutations.ts — every mutating call the foundation ships with, each\n * wrapped so it (a) pulls the held mutation token from auth-store instead\n * of asking every call site to thread it through, (b) touches the hold on\n * success so a burst of approvals doesn't idle-expire mid-session, and\n * (c) invalidates whatever cache keys the mutation obviously affects so\n * views update without waiting for the next SSE tick.\n *\n * Routes mirror the mutating surface documented at the top of\n * src/core/web/api.ts. Every one of these 404s (not 401/403) when the\n * server was started without --allow-dispatch — apiPost already maps that\n * to DispatchDisabledError.\n */\nimport { apiPost } from './client.js';\nimport { getMutationToken, touchMutationHold } from './auth-store.js';\nimport { invalidate, invalidatePrefix } from './cache.js';\n\nclass MissingMutationTokenError extends Error {\n constructor() {\n super('No mutation token held for this session yet.');\n this.name = 'MissingMutationTokenError';\n }\n}\n\nasync function post<T>(path: string, body: unknown): Promise<T> {\n const token = getMutationToken();\n if (!token) throw new MissingMutationTokenError();\n const result = await apiPost<T>(path, body, token);\n touchMutationHold();\n return result;\n}\n\nexport async function pauseFleet(): Promise<void> {\n await post('/api/fleet/pause', {});\n invalidate('dashboard-snapshot');\n invalidate('control-snapshot');\n invalidate('fleet');\n invalidate('fleet-activity');\n}\n\nexport async function resumeFleet(): Promise<void> {\n await post('/api/fleet/resume', {});\n invalidate('dashboard-snapshot');\n invalidate('control-snapshot');\n invalidate('fleet');\n invalidate('fleet-activity');\n}\n\nexport async function approveProposal(id: string): Promise<void> {\n await post(`/api/inbox/${encodeURIComponent(id)}/approve`, {});\n invalidate('inbox');\n invalidate('dashboard-snapshot');\n invalidate(`proposal-detail-${id}`);\n invalidatePrefix('inbox-list:');\n}\n\nexport async function rejectProposal(id: string): Promise<void> {\n await post(`/api/inbox/${encodeURIComponent(id)}/reject`, {});\n invalidate('inbox');\n invalidate('dashboard-snapshot');\n invalidate(`proposal-detail-${id}`);\n invalidatePrefix('inbox-list:');\n}\n\nexport interface OpenTargetRequest {\n path: string;\n target: 'editor' | 'finder';\n}\n\nexport async function openTarget(req: OpenTargetRequest): Promise<void> {\n await post('/api/open', req);\n}\n",".body {\n font-size: var(--text-sm-size);\n color: var(--text-secondary);\n line-height: var(--text-sm-line);\n margin: var(--space-3) 0;\n}\n\n.error {\n padding: var(--space-2) var(--space-3);\n border-radius: var(--radius-sm);\n background: var(--status-danger-bg);\n color: var(--status-danger-fg);\n font-size: var(--text-xs-size);\n margin-bottom: var(--space-3);\n}\n\n.actions {\n display: flex;\n justify-content: flex-end;\n gap: var(--space-2);\n}\n\n.cancel {\n padding: var(--space-2) var(--space-4);\n border-radius: var(--radius-sm);\n border: 1px solid var(--border-default);\n background: var(--bg-surface);\n color: var(--text-secondary);\n font-size: var(--text-sm-size);\n cursor: pointer;\n}\n\n.confirm {\n padding: var(--space-2) var(--space-4);\n border-radius: var(--radius-sm);\n border: 1px solid var(--accent-600);\n background: var(--accent-600);\n color: var(--text-on-accent);\n font-size: var(--text-sm-size);\n font-weight: var(--font-weight-medium);\n cursor: pointer;\n}\n.confirm:disabled {\n opacity: 0.6;\n cursor: not-allowed;\n}\n\n.destructive {\n padding: var(--space-2) var(--space-4);\n border-radius: var(--radius-sm);\n border: 1px solid var(--status-danger-solid);\n background: var(--status-danger-solid);\n color: var(--text-on-accent);\n font-size: var(--text-sm-size);\n font-weight: var(--font-weight-medium);\n cursor: pointer;\n}\n.destructive:disabled {\n opacity: 0.6;\n cursor: not-allowed;\n}\n","/**\n * routes/inbox/ConfirmDialog.tsx — explicit confirmation for approve/reject\n * (single or batch). Approve is the single most irreversible action in the\n * product (operator-console brief non-negotiable): it must require explicit\n * confirmation and clearly state what will happen, never fire on a single\n * click. Built on the shared Dialog primitive so it gets the same focus\n * trap / Escape / backdrop-close behavior as MutationTokenDialog and the\n * command palette for free.\n */\nimport { useId, useRef, type ReactNode } from 'react';\nimport { Dialog } from '../../components/primitives/Dialog.js';\nimport styles from './ConfirmDialog.module.css';\n\nexport interface ConfirmDialogProps {\n open: boolean;\n onClose: () => void;\n title: string;\n body: ReactNode;\n confirmLabel: string;\n /**\n * Weight the confirm button as the dangerous one. The rule is IRREVERSIBLE,\n * not \"negative-sounding\": whichever branch the operator cannot undo gets\n * the weight. In Verse's approvals that is Approve — a `pr` pushes a branch\n * and opens a real pull request other people see immediately, a `patch`\n * writes to disk now — while Reject only discards a proposal that stays in\n * history. This doc used to say the opposite, which is how the red button\n * came to be the safe one.\n */\n destructive?: boolean;\n onConfirm: () => void;\n busy?: boolean;\n error?: string | null;\n}\n\nexport function ConfirmDialog({ open, onClose, title, body, confirmLabel, destructive, onConfirm, busy, error }: ConfirmDialogProps) {\n const titleId = useId();\n const confirmRef = useRef<HTMLButtonElement>(null);\n\n return (\n <Dialog open={open} onClose={onClose} titleId={titleId} title={title} initialFocusRef={confirmRef}>\n <div className={styles.body}>{body}</div>\n {error ? (\n <p className={styles.error} role=\"alert\">\n {error}\n </p>\n ) : null}\n <div className={styles.actions}>\n <button type=\"button\" className={styles.cancel} onClick={onClose} disabled={busy}>\n Cancel\n </button>\n <button\n ref={confirmRef}\n type=\"button\"\n className={destructive ? styles.destructive : styles.confirm}\n onClick={onConfirm}\n disabled={busy}\n >\n {busy ? 'Working…' : confirmLabel}\n </button>\n </div>\n </Dialog>\n );\n}\n"],"mappings":"oKAiBA,IAAM,EAAN,cAAwC,KAAM,CAC5C,aAAc,CACZ,MAAM,8CAA8C,EACpD,KAAK,KAAO,2BACd,CACF,EAEA,eAAe,EAAQ,EAAc,EAA2B,CAC9D,IAAM,EAAQ,EAAiB,EAC/B,GAAI,CAAC,EAAO,MAAM,IAAI,EACtB,IAAM,EAAS,MAAM,EAAW,EAAM,EAAM,CAAK,EAEjD,OADA,EAAkB,EACX,CACT,CAEA,eAAsB,GAA4B,CAChD,MAAM,EAAK,mBAAoB,CAAC,CAAC,EACjC,EAAW,oBAAoB,EAC/B,EAAW,kBAAkB,EAC7B,EAAW,OAAO,EAClB,EAAW,gBAAgB,CAC7B,CAEA,eAAsB,GAA6B,CACjD,MAAM,EAAK,oBAAqB,CAAC,CAAC,EAClC,EAAW,oBAAoB,EAC/B,EAAW,kBAAkB,EAC7B,EAAW,OAAO,EAClB,EAAW,gBAAgB,CAC7B,CAEA,eAAsB,EAAgB,EAA2B,CAC/D,MAAM,EAAK,cAAc,mBAAmB,CAAE,EAAE,UAAW,CAAC,CAAC,EAC7D,EAAW,OAAO,EAClB,EAAW,oBAAoB,EAC/B,EAAW,mBAAmB,GAAI,EAClC,EAAiB,aAAa,CAChC,CAEA,eAAsB,EAAe,EAA2B,CAC9D,MAAM,EAAK,cAAc,mBAAmB,CAAE,EAAE,SAAU,CAAC,CAAC,EAC5D,EAAW,OAAO,EAClB,EAAW,oBAAoB,EAC/B,EAAW,mBAAmB,GAAI,EAClC,EAAiB,aAAa,CAChC,4LE5BA,SAAgB,EAAc,CAAE,OAAM,UAAS,QAAO,OAAM,eAAc,cAAa,YAAW,OAAM,SAA6B,CACnI,IAAM,GAAA,EAAU,EAAA,MAAA,CAAM,EAChB,GAAA,EAAa,EAAA,OAAA,CAA0B,IAAI,EAEjD,OACE,EAAA,EAAA,KAAA,CAAC,EAAD,CAAc,OAAe,UAAkB,UAAgB,QAAO,gBAAiB,EAAvF,SAAA,EACE,EAAA,EAAA,IAAA,CAAC,MAAD,CAAK,UAAW,EAAO,KAAO,SAAA,CAAU,CAAA,EACvC,GACC,EAAA,EAAA,IAAA,CAAC,IAAD,CAAG,UAAW,EAAO,MAAO,KAAK,QAC9B,SAAA,CACA,CAAA,EACD,MACJ,EAAA,EAAA,KAAA,CAAC,MAAD,CAAK,UAAW,EAAO,QAAvB,SAAA,EACE,EAAA,EAAA,IAAA,CAAC,SAAD,CAAQ,KAAK,SAAS,UAAW,EAAO,OAAQ,QAAS,EAAS,SAAU,EAAM,SAAA,QAE1E,CAAA,GACR,EAAA,EAAA,IAAA,CAAC,SAAD,CACE,IAAK,EACL,KAAK,SACL,UAAW,EAAc,EAAO,YAAc,EAAO,QACrD,QAAS,EACT,SAAU,EAET,SAAA,EAAO,WAAa,CACf,CAAA,CACL,GACC,GAEZ"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{a as e,i as t,n,o as r}from"./index-
|
|
2
|
-
//# sourceMappingURL=Dialog-
|
|
1
|
+
import{a as e,i as t,n,o as r}from"./index-CXgVQHek.js";var i=r(t(),1),a=r(e(),1),o=[`a[href]`,`button:not([disabled])`,`input:not([disabled])`,`select:not([disabled])`,`textarea:not([disabled])`,`[tabindex]:not([tabindex="-1"])`].join(`,`);function s(e){return Array.from(e.querySelectorAll(o)).filter(e=>e.offsetParent!==null)}function c({open:e,containerRef:t,onClose:n,initialFocusRef:r}){(0,a.useEffect)(()=>{if(!e)return;let n=document.activeElement;return(r?.current??t.current)?.focus({preventScroll:!0}),()=>{n?.focus?.({preventScroll:!0})}},[e]),(0,a.useEffect)(()=>{if(!e)return;function r(e){if(e.key===`Escape`){e.stopPropagation(),n();return}if(e.key!==`Tab`)return;let r=t.current;if(!r)return;let i=s(r);if(i.length===0)return;let a=i[0],o=i[i.length-1];e.shiftKey&&document.activeElement===a?(e.preventDefault(),o.focus()):!e.shiftKey&&document.activeElement===o&&(e.preventDefault(),a.focus())}return document.addEventListener(`keydown`,r,!0),()=>document.removeEventListener(`keydown`,r,!0)},[e,n])}var l={backdrop:`_backdrop_10r5p_1`,fade:`_fade_10r5p_1`,dialog:`_dialog_10r5p_13`,rise:`_rise_10r5p_1`,title:`_title_10r5p_25`,description:`_description_10r5p_36`},u=n();function d({open:e,onClose:t,titleId:n,title:r,description:o,children:s,initialFocusRef:d,widthClassName:f}){let p=(0,a.useRef)(null);return c({open:e,containerRef:p,onClose:t,initialFocusRef:d}),e?(0,i.createPortal)((0,u.jsx)(`div`,{className:l.backdrop,onMouseDown:e=>e.target===e.currentTarget&&t(),children:(0,u.jsxs)(`div`,{ref:p,className:`${l.dialog} ${f??``}`,role:`dialog`,"aria-modal":`true`,"aria-labelledby":n,tabIndex:-1,children:[(0,u.jsx)(`h2`,{id:n,className:l.title,children:r}),o?(0,u.jsx)(`p`,{className:l.description,children:o}):null,s]})}),document.body):null}export{d as t};
|
|
2
|
+
//# sourceMappingURL=Dialog-nK15L1M0.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Dialog-
|
|
1
|
+
{"version":3,"file":"Dialog-nK15L1M0.js","names":[],"sources":["../../../../../../src/web-ui/components/primitives/focus-trap.ts","../../../../../../src/web-ui/components/primitives/Dialog.module.css","../../../../../../src/web-ui/components/primitives/Dialog.tsx"],"sourcesContent":["/**\n * components/primitives/focus-trap.ts — the modal focus contract, extracted\n * from Dialog.tsx so Sheet.tsx (the slide-over overlay) gets exactly the same\n * behavior instead of a second, subtly different implementation:\n *\n * - focus moves into the overlay (or `initialFocusRef`) on open\n * - focus is trapped inside while open (Tab/Shift+Tab wrap)\n * - focus returns to the element that opened it on close\n * - Escape closes\n *\n * Dialog.tsx is unchanged in behavior — it now calls this instead of\n * inlining it. Any future overlay MUST use this rather than hand-rolling a\n * trap (DESIGN.md §3, \"primitives own their accessibility\").\n */\nimport { useEffect, type RefObject } from 'react';\n\nexport const FOCUSABLE_SELECTOR = [\n 'a[href]',\n 'button:not([disabled])',\n 'input:not([disabled])',\n 'select:not([disabled])',\n 'textarea:not([disabled])',\n '[tabindex]:not([tabindex=\"-1\"])',\n].join(',');\n\n/** Focusable descendants that are actually rendered (offsetParent is null for hidden nodes). */\nexport function focusableWithin(node: HTMLElement): HTMLElement[] {\n return Array.from(node.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTOR)).filter((el) => el.offsetParent !== null);\n}\n\nexport interface FocusTrapOptions {\n open: boolean;\n containerRef: RefObject<HTMLElement | null>;\n onClose: () => void;\n initialFocusRef?: RefObject<HTMLElement | null>;\n}\n\nexport function useFocusTrap({ open, containerRef, onClose, initialFocusRef }: FocusTrapOptions): void {\n useEffect(() => {\n if (!open) return;\n const previouslyFocused = document.activeElement as HTMLElement | null;\n const target = initialFocusRef?.current ?? containerRef.current;\n target?.focus({ preventScroll: true });\n\n return () => {\n previouslyFocused?.focus?.({ preventScroll: true });\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps -- the refs are refs (stable identity is not the point); only `open` should re-run this.\n }, [open]);\n\n useEffect(() => {\n if (!open) return;\n function onKeyDown(e: KeyboardEvent) {\n if (e.key === 'Escape') {\n e.stopPropagation();\n onClose();\n return;\n }\n if (e.key !== 'Tab') return;\n const node = containerRef.current;\n if (!node) return;\n const focusable = focusableWithin(node);\n if (focusable.length === 0) return;\n const first = focusable[0]!;\n const last = focusable[focusable.length - 1]!;\n if (e.shiftKey && document.activeElement === first) {\n e.preventDefault();\n last.focus();\n } else if (!e.shiftKey && document.activeElement === last) {\n e.preventDefault();\n first.focus();\n }\n }\n document.addEventListener('keydown', onKeyDown, true);\n return () => document.removeEventListener('keydown', onKeyDown, true);\n // eslint-disable-next-line react-hooks/exhaustive-deps -- containerRef is a ref; re-binding on every render would drop in-flight key handling.\n }, [open, onClose]);\n}\n",".backdrop {\n position: fixed;\n inset: 0;\n background: var(--bg-overlay);\n display: flex;\n align-items: flex-start;\n justify-content: center;\n padding-top: 12vh;\n z-index: var(--z-dialog);\n animation: fade var(--duration-fast) var(--ease-standard);\n}\n\n.dialog {\n width: min(480px, calc(100vw - var(--space-8)));\n max-height: 76vh;\n overflow: auto;\n background: var(--bg-surface-raised);\n border: 1px solid var(--border-default);\n border-radius: var(--radius-lg);\n box-shadow: var(--shadow-dialog);\n padding: var(--space-5);\n animation: rise var(--duration-base) var(--ease-decelerate);\n}\n\n.title {\n margin: 0 0 var(--space-4);\n font-family: var(--font-display);\n font-size: var(--text-md-size);\n line-height: var(--text-md-line);\n font-weight: var(--font-weight-semibold);\n letter-spacing: var(--tracking-display);\n}\n\n/* Sits between title and body: the title's bottom margin collapses into\n this one's top, so give the pair the same rhythm as a title alone. */\n.description {\n margin: calc(var(--space-4) * -1 + var(--space-1)) 0 var(--space-4);\n color: var(--text-secondary);\n font-size: var(--text-sm-size);\n line-height: var(--text-sm-line);\n}\n\n@keyframes fade {\n from {\n opacity: 0;\n }\n}\n\n@keyframes rise {\n from {\n opacity: 0;\n transform: translateY(4px);\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n :root:not([data-motion=\"full\"]) .backdrop,\n :root:not([data-motion=\"full\"]) .dialog {\n animation: none;\n }\n}\n\n:root[data-motion=\"reduce\"] .backdrop,\n:root[data-motion=\"reduce\"] .dialog {\n animation: none;\n}\n","/**\n * components/primitives/Dialog.tsx — focus-managed modal base. Used by\n * MutationTokenDialog, CommandPalette and the Verse confirm flows so all of\n * them get the same accessible behavior instead of each hand-rolling it:\n *\n * - focus moves to the dialog (or `initialFocusRef`) on open\n * - focus is trapped inside while open (Tab/Shift+Tab wrap)\n * - focus returns to the trigger element on close\n * - Escape closes\n * - a click on the backdrop closes\n *\n * The trap itself lives in ./focus-trap.ts, shared with Sheet.tsx — the\n * behavior here is unchanged, it just stopped being copy-pasteable.\n */\nimport { useRef, type ReactNode } from 'react';\nimport { createPortal } from 'react-dom';\nimport { useFocusTrap } from './focus-trap.js';\nimport styles from './Dialog.module.css';\n\nexport interface DialogProps {\n open: boolean;\n onClose: () => void;\n titleId: string;\n title: ReactNode;\n children: ReactNode;\n /** Optional one-line description under the title. */\n description?: ReactNode;\n initialFocusRef?: React.RefObject<HTMLElement | null>;\n widthClassName?: string;\n}\n\nexport function Dialog({\n open,\n onClose,\n titleId,\n title,\n description,\n children,\n initialFocusRef,\n widthClassName,\n}: DialogProps) {\n const dialogRef = useRef<HTMLDivElement>(null);\n\n useFocusTrap({ open, containerRef: dialogRef, onClose, initialFocusRef });\n\n if (!open) return null;\n\n return createPortal(\n <div className={styles.backdrop} onMouseDown={(e) => e.target === e.currentTarget && onClose()}>\n <div\n ref={dialogRef}\n className={`${styles.dialog} ${widthClassName ?? ''}`}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby={titleId}\n tabIndex={-1}\n >\n <h2 id={titleId} className={styles.title}>\n {title}\n </h2>\n {description ? <p className={styles.description}>{description}</p> : null}\n {children}\n </div>\n </div>,\n document.body,\n );\n}\n"],"mappings":"kFAgBa,EAAqB,CAChC,UACA,yBACA,wBACA,yBACA,2BACA,iCACF,CAAC,CAAC,KAAK,GAAG,EAGV,SAAgB,EAAgB,EAAkC,CAChE,OAAO,MAAM,KAAK,EAAK,iBAA8B,CAAkB,CAAC,CAAC,CAAC,OAAQ,GAAO,EAAG,eAAiB,IAAI,CACnH,CASA,SAAgB,EAAa,CAAE,OAAM,eAAc,UAAS,mBAA2C,EACrG,EAAA,EAAA,UAAA,KAAgB,CACd,GAAI,CAAC,EAAM,OACX,IAAM,EAAoB,SAAS,cAInC,OAHe,GAAiB,SAAW,EAAa,QAAA,EAChD,MAAM,CAAE,cAAe,EAAK,CAAC,MAExB,CACX,GAAmB,QAAQ,CAAE,cAAe,EAAK,CAAC,CACpD,CAEF,EAAG,CAAC,CAAI,CAAC,GAET,EAAA,EAAA,UAAA,KAAgB,CACd,GAAI,CAAC,EAAM,OACX,SAAS,EAAU,EAAkB,CACnC,GAAI,EAAE,MAAQ,SAAU,CACtB,EAAE,gBAAgB,EAClB,EAAQ,EACR,MACF,CACA,GAAI,EAAE,MAAQ,MAAO,OACrB,IAAM,EAAO,EAAa,QAC1B,GAAI,CAAC,EAAM,OACX,IAAM,EAAY,EAAgB,CAAI,EACtC,GAAI,EAAU,SAAW,EAAG,OAC5B,IAAM,EAAQ,EAAU,GAClB,EAAO,EAAU,EAAU,OAAS,GACtC,EAAE,UAAY,SAAS,gBAAkB,GAC3C,EAAE,eAAe,EACjB,EAAK,MAAM,GACF,CAAC,EAAE,UAAY,SAAS,gBAAkB,IACnD,EAAE,eAAe,EACjB,EAAM,MAAM,EAEhB,CAEA,OADA,SAAS,iBAAiB,UAAW,EAAW,EAAI,MACvC,SAAS,oBAAoB,UAAW,EAAW,EAAI,CAEtE,EAAG,CAAC,EAAM,CAAO,CAAC,CACpB,4KE9CA,SAAgB,EAAO,CACrB,OACA,UACA,UACA,QACA,cACA,WACA,kBACA,kBACc,CACd,IAAM,GAAA,EAAY,EAAA,OAAA,CAAuB,IAAI,EAM7C,OAJA,EAAa,CAAE,OAAM,aAAc,EAAW,UAAS,iBAAgB,CAAC,EAEnE,GAEL,EAAO,EAAA,aAAA,EACL,EAAA,EAAA,IAAA,CAAC,MAAD,CAAK,UAAW,EAAO,SAAU,YAAc,GAAM,EAAE,SAAW,EAAE,eAAiB,EAAQ,EAC3F,UAAA,EAAA,EAAA,KAAA,CAAC,MAAD,CACE,IAAK,EACL,UAAW,GAAG,EAAO,OAAO,GAAG,GAAkB,KACjD,KAAK,SACL,aAAW,OACX,kBAAiB,EACjB,SAAU,GANZ,SAAA,EAQE,EAAA,EAAA,IAAA,CAAC,KAAD,CAAI,GAAI,EAAS,UAAW,EAAO,MAChC,SAAA,CACC,CAAA,EACH,GAAc,EAAA,EAAA,IAAA,CAAC,IAAD,CAAG,UAAW,EAAO,YAAc,SAAA,CAAe,CAAA,EAAI,KACpE,CACE,GACF,CAAA,EACL,SAAS,IACX,EApBkB,IAqBpB"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import{a as e,n as t,o as n}from"./index-
|
|
1
|
+
import{a as e,n as t,o as n}from"./index-CXgVQHek.js";var r=n(e(),1),i=/^diff --git a\/(.+) b\/(.+)$/,a=/^--- (?:a\/(.+)|(\/dev\/null))\s*$/,o=/^\+\+\+ (?:b\/(.+)|(\/dev\/null))\s*$/,s=/^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@(.*)$/;function c(e){return e.replace(/^[ab]\//,``)}function l(e){let t=e.split(`
|
|
2
2
|
`),n=t.some(e=>i.test(e)),r=[];for(let e=0;e<t.length;e++){let o=t[e];(n?i.test(o):a.test(o))&&r.push(e)}if(r.length===0)return[];let o=[];for(let e=0;e<r.length;e++){let n=r[e],i=e+1<r.length?r[e+1]:t.length;o.push(t.slice(n,i).join(`
|
|
3
3
|
`))}return o}function u(e,t){let n=e.split(`
|
|
4
4
|
`);n.length>0&&n[n.length-1]===``&&n.pop();let r=null,l=null,u=-1;for(let e=0;e<n.length;e++){let t=n[e],s=a.exec(t);if(s){r=s[2]?null:c(s[1]);continue}let d=o.exec(t);if(d){l=d[2]?null:c(d[1]),u=e+1;break}let f=i.exec(t);f&&r===null&&l===null&&(r=f[1]??null,l=f[2]??null)}let d=`modified`;r===null&&l!==null?d=`added`:r!==null&&l===null?d=`deleted`:r!==null&&l!==null&&r!==l&&(d=`renamed`);let f=l??r??`(unknown file ${t+1})`,p=[],m=0,h=0,g=null;if(u===-1){let e=n.join(`
|
|
5
5
|
`).trim();return{id:`${t}:${f}`,oldPath:r,newPath:l,displayPath:f,status:d,hunks:[],additions:0,deletions:0,unparsedNotice:e||null}}let _=u;for(;_<n.length;){let e=s.exec(n[_]);if(!e){n[_].trim()&&(g=((g?g+`
|
|
6
6
|
`:``)+n[_]).trim()),_++;continue}let t=Number(e[1]),r=e[2]===void 0?1:Number(e[2]),i=Number(e[3]),a=e[4]===void 0?1:Number(e[4]),o={header:n[_],oldStart:t,oldLines:r,newStart:i,newLines:a,lines:[]};_++;let c=t,l=i;for(;_<n.length&&!s.test(n[_]);){let e=n[_];if(e.startsWith(`\\`)){_++;continue}let t=e[0],r=e.slice(1);t===`+`?(o.lines.push({kind:`add`,text:r,oldLineNo:null,newLineNo:l}),l++,m++):t===`-`?(o.lines.push({kind:`del`,text:r,oldLineNo:c,newLineNo:null}),c++,h++):(o.lines.push({kind:`context`,text:t===` `?r:e,oldLineNo:c,newLineNo:l}),c++,l++),_++}p.push(o)}return{id:`${t}:${f}`,oldPath:r,newPath:l,displayPath:f,status:d,hunks:p,additions:m,deletions:h,unparsedNotice:g}}function d(e){let t=e??``;if(!t.trim())return{files:[],malformed:!1};let n=l(t);return n.length===0?{malformed:!0,files:[{id:`raw`,oldPath:null,newPath:null,displayPath:`(unparsed diff)`,status:`modified`,hunks:[],additions:0,deletions:0,unparsedNotice:t}]}:{files:n.map((e,t)=>u(e,t)),malformed:!1}}function f(e){let t=[],n=0;for(;n<e.length;){let r=e[n];if(r.kind===`context`){t.push({left:r,right:r}),n++;continue}let i=[];for(;n<e.length&&e[n].kind===`del`;)i.push(e[n]),n++;let a=[];for(;n<e.length&&e[n].kind===`add`;)a.push(e[n]),n++;let o=Math.max(i.length,a.length);for(let e=0;e<o;e++)t.push({left:i[e]??null,right:a[e]??null})}return t}var p={ts:`c-like`,tsx:`c-like`,js:`c-like`,jsx:`c-like`,mjs:`c-like`,cjs:`c-like`,mts:`c-like`,cts:`c-like`,java:`c-like`,go:`c-like`,rs:`c-like`,c:`c-like`,h:`c-like`,cpp:`c-like`,hpp:`c-like`,cc:`c-like`,swift:`c-like`,kt:`c-like`,kts:`c-like`,py:`python`,pyi:`python`,sh:`shell`,bash:`shell`,zsh:`shell`,css:`css`,scss:`css`,less:`css`,json:`json`,jsonc:`json`,yml:`yaml`,yaml:`yaml`};function m(e){return e?p[e.split(`.`).pop()?.toLowerCase()??``]??`plain`:`plain`}var h={"c-like":new Set(`const.let.var.function.return.if.else.for.while.do.switch.case.break.continue.class.extends.implements.interface.type.enum.export.import.from.default.new.this.super.try.catch.finally.throw.async.await.yield.static.public.private.protected.readonly.abstract.null.undefined.true.false.void.typeof.instanceof.in.of.as.is.never.unknown.any.package.struct.fn.impl.mod.pub.use.trait.match.func.defer.chan`.split(`.`)),python:new Set(`def.class.return.if.elif.else.for.while.break.continue.import.from.as.with.try.except.finally.raise.pass.lambda.yield.async.await.None.True.False.and.or.not.in.is.global.nonlocal.self`.split(`.`)),shell:new Set([`if`,`then`,`else`,`elif`,`fi`,`for`,`while`,`do`,`done`,`case`,`esac`,`function`,`return`,`export`,`local`,`echo`,`set`,`in`,`break`,`continue`]),css:new Set,json:new Set,yaml:new Set,plain:new Set},g={"c-like":/(?<comment>\/\/.*$|\/\*.*?\*\/)|(?<string>"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'|`(?:[^`\\]|\\.)*`)|(?<number>\b\d+(?:\.\d+)?\b)|(?<word>[A-Za-z_$][A-Za-z0-9_$]*)/g,python:/(?<comment>#.*$)|(?<string>"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*')|(?<number>\b\d+(?:\.\d+)?\b)|(?<word>[A-Za-z_][A-Za-z0-9_]*)/g,shell:/(?<comment>#.*$)|(?<string>"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*')|(?<word>[A-Za-z_][A-Za-z0-9_]*)/g,css:/(?<comment>\/\*.*?\*\/)|(?<string>"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*')|(?<number>\b\d+(?:\.\d+)?\b)|(?<word>[A-Za-z_-][A-Za-z0-9_-]*)/g,json:/(?<string>"(?:[^"\\]|\\.)*")|(?<number>-?\b\d+(?:\.\d+)?\b)|(?<word>\btrue\b|\bfalse\b|\bnull\b)/g,yaml:/(?<comment>#.*$)|(?<string>"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*')/g};function _(e,t){let n=g[t];if(!n)return[{kind:`plain`,text:e}];let r=h[t],i=[],a=0;n.lastIndex=0;let o;for(;o=n.exec(e);){o.index>a&&i.push({kind:`plain`,text:e.slice(a,o.index)});let s=o.groups??{};if(s.comment!==void 0)i.push({kind:`comment`,text:o[0]});else if(s.string!==void 0)i.push({kind:`string`,text:o[0]});else if(s.number!==void 0)i.push({kind:`number`,text:o[0]});else if(s.word!==void 0){let e=t===`json`||r.has(s.word);i.push({kind:e?`keyword`:`plain`,text:o[0]})}else i.push({kind:`plain`,text:o[0]});a=o.index+o[0].length,o[0].length===0&&n.lastIndex++}return a<e.length&&i.push({kind:`plain`,text:e.slice(a)}),i}var v={viewer:`_viewer_gf84p_1`,malformedNotice:`_malformedNotice_gf84p_9`,empty:`_empty_gf84p_18`,tree:`_tree_gf84p_27`,treeItem:`_treeItem_gf84p_40`,treeItemActive:`_treeItemActive_gf84p_59`,statusDot:`_statusDot_gf84p_64`,status_added:`_status_added_gf84p_70`,status_deleted:`_status_deleted_gf84p_71`,status_modified:`_status_modified_gf84p_72`,status_renamed:`_status_renamed_gf84p_73`,treePath:`_treePath_gf84p_75`,treeStats:`_treeStats_gf84p_83`,add:`_add_gf84p_89`,del:`_del_gf84p_90`,body:`_body_gf84p_92`,fileHeader:`_fileHeader_gf84p_99`,filePath:`_filePath_gf84p_109`,modeToggle:`_modeToggle_gf84p_118`,modeActive:`_modeActive_gf84p_132`,notice:`_notice_gf84p_138`,emptyFile:`_emptyFile_gf84p_149`,hunkTable:`_hunkTable_gf84p_155`,hunkCaption:`_hunkCaption_gf84p_163`,lineNo:`_lineNo_gf84p_173`,lineMarker:`_lineMarker_gf84p_184`,lineText:`_lineText_gf84p_192`,hunkTableSplit:`_hunkTableSplit_gf84p_199`,rowBlank:`_rowBlank_gf84p_203`,row_add:`_row_add_gf84p_207`,row_del:`_row_del_gf84p_210`,row_context:`_row_context_gf84p_213`,code:`_code_gf84p_217`,tok_plain:`_tok_plain_gf84p_221`,tok_comment:`_tok_comment_gf84p_222`,tok_string:`_tok_string_gf84p_223`,tok_number:`_tok_number_gf84p_224`,tok_keyword:`_tok_keyword_gf84p_225`},y=t();function b({diff:e,proposalKind:t}){let n=(0,r.useMemo)(()=>d(e),[e]),[i,a]=(0,r.useState)(n.files[0]?.id??null),[o,s]=(0,r.useState)(`unified`);if((0,r.useEffect)(()=>{a(n.files[0]?.id??null)},[e]),n.files.length===0)return(0,y.jsxs)(`div`,{className:v.empty,children:[`No diff attached to this proposal (kind: `,t,`).`]});let c=n.files.find(e=>e.id===i)??n.files[0];function l(e){let t=n.files.findIndex(e=>e.id===c.id);e.key===`ArrowDown`?(e.preventDefault(),a(n.files[Math.min(t+1,n.files.length-1)].id)):e.key===`ArrowUp`&&(e.preventDefault(),a(n.files[Math.max(t-1,0)].id))}return(0,y.jsxs)(`div`,{className:v.viewer,children:[n.malformed?(0,y.jsx)(`p`,{className:v.malformedNotice,children:`Could not parse this as a unified diff — showing raw content.`}):null,(0,y.jsx)(`div`,{className:v.tree,role:`tree`,"aria-label":`Changed files`,onKeyDown:l,children:n.files.map(e=>(0,y.jsxs)(`button`,{type:`button`,role:`treeitem`,"aria-selected":e.id===c.id,tabIndex:e.id===c.id?0:-1,className:`${v.treeItem} ${e.id===c.id?v.treeItemActive:``}`,onClick:()=>a(e.id),children:[(0,y.jsx)(`span`,{className:`${v.statusDot} ${v[`status_${e.status}`]}`,"aria-hidden":`true`}),(0,y.jsx)(`span`,{className:v.treePath,title:e.displayPath,children:e.displayPath}),(0,y.jsxs)(`span`,{className:v.treeStats,children:[e.additions>0?(0,y.jsxs)(`span`,{className:v.add,children:[`+`,e.additions]}):null,e.deletions>0?(0,y.jsxs)(`span`,{className:v.del,children:[`-`,e.deletions]}):null]})]},e.id))}),(0,y.jsxs)(`div`,{className:v.body,children:[(0,y.jsxs)(`div`,{className:v.fileHeader,children:[(0,y.jsx)(`span`,{className:v.filePath,children:c.displayPath}),(0,y.jsxs)(`div`,{className:v.modeToggle,role:`group`,"aria-label":`Diff view mode`,children:[(0,y.jsx)(`button`,{type:`button`,"aria-pressed":o===`unified`,className:o===`unified`?v.modeActive:``,onClick:()=>s(`unified`),children:`Unified`}),(0,y.jsx)(`button`,{type:`button`,"aria-pressed":o===`split`,className:o===`split`?v.modeActive:``,onClick:()=>s(`split`),children:`Split`})]})]}),c.unparsedNotice?(0,y.jsx)(`pre`,{className:v.notice,children:c.unparsedNotice}):null,c.hunks.length===0&&!c.unparsedNotice?(0,y.jsx)(`p`,{className:v.emptyFile,children:`No line-level changes captured for this file.`}):c.hunks.map((e,t)=>(0,y.jsx)(x,{hunk:e,mode:o,path:c.displayPath},t))]})]})}function x({hunk:e,mode:t,path:n}){let r=m(n);if(t===`split`){let t=f(e.lines);return(0,y.jsxs)(`table`,{className:`${v.hunkTable} ${v.hunkTableSplit}`,children:[(0,y.jsx)(`caption`,{className:v.hunkCaption,children:e.header}),(0,y.jsx)(`tbody`,{children:t.map((e,t)=>(0,y.jsxs)(`tr`,{children:[(0,y.jsx)(`td`,{className:v.lineNo,children:e.left?.oldLineNo??``}),(0,y.jsx)(`td`,{className:`${v.lineText} ${e.left?v[`row_${e.left.kind}`]:v.rowBlank}`,children:e.left?(0,y.jsx)(S,{text:e.left.text,lang:r}):null}),(0,y.jsx)(`td`,{className:v.lineNo,children:e.right?.newLineNo??``}),(0,y.jsx)(`td`,{className:`${v.lineText} ${e.right?v[`row_${e.right.kind}`]:v.rowBlank}`,children:e.right?(0,y.jsx)(S,{text:e.right.text,lang:r}):null})]},t))})]})}return(0,y.jsxs)(`table`,{className:v.hunkTable,children:[(0,y.jsx)(`caption`,{className:v.hunkCaption,children:e.header}),(0,y.jsx)(`tbody`,{children:e.lines.map((e,t)=>(0,y.jsxs)(`tr`,{className:v[`row_${e.kind}`],children:[(0,y.jsx)(`td`,{className:v.lineNo,children:e.oldLineNo??``}),(0,y.jsx)(`td`,{className:v.lineNo,children:e.newLineNo??``}),(0,y.jsx)(`td`,{className:v.lineMarker,"aria-hidden":`true`,children:e.kind===`add`?`+`:e.kind===`del`?`-`:``}),(0,y.jsx)(`td`,{className:v.lineText,children:(0,y.jsx)(S,{text:e.text,lang:r})})]},t))})]})}function S({text:e,lang:t}){let n=_(e,t);return(0,y.jsx)(`code`,{className:v.code,children:n.map((e,t)=>(0,y.jsx)(`span`,{className:v[`tok_${e.kind}`],children:e.text},t))})}export{d as i,m as n,_ as r,b as t};
|
|
7
|
-
//# sourceMappingURL=DiffViewer-
|
|
7
|
+
//# sourceMappingURL=DiffViewer-D369eZe8.js.map
|
package/dist/core/web/public/next/assets/{DiffViewer-QKyisN2L.js.map → DiffViewer-D369eZe8.js.map}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DiffViewer-QKyisN2L.js","names":[],"sources":["../../../../../../src/web-ui/routes/inbox/diff-parser.ts","../../../../../../src/web-ui/routes/inbox/highlight.ts","../../../../../../src/web-ui/routes/inbox/DiffViewer.module.css","../../../../../../src/web-ui/routes/inbox/DiffViewer.tsx"],"sourcesContent":["/**\n * routes/inbox/diff-parser.ts — pure parsing of a unified diff string\n * (Proposal.diff, src/core/types.ts) into a per-file, per-hunk structure the\n * DiffViewer can render with a file tree and syntax highlighting, instead of\n * dumping the raw string into an escaped <pre> block (the old UI's\n * approach — \"not acceptable for a tool meant to be elite\" per the\n * operator-console brief). No dependency: hand-rolled parser over the\n * unified diff format, which is regular enough (`--- `/`+++ `/`@@ `/leading\n * `-`/`+`/` `) that a small line-scan state machine covers it completely.\n *\n * Pure functions only — no React, no DOM — so this is trivially unit\n * testable (see diff-parser.test.ts) independent of rendering.\n */\n\nexport type DiffLineKind = 'context' | 'add' | 'del';\n\nexport interface DiffLine {\n kind: DiffLineKind;\n text: string;\n oldLineNo: number | null;\n newLineNo: number | null;\n}\n\nexport interface DiffHunk {\n header: string;\n oldStart: number;\n oldLines: number;\n newStart: number;\n newLines: number;\n lines: DiffLine[];\n}\n\nexport type FileChangeStatus = 'added' | 'deleted' | 'renamed' | 'modified';\n\nexport interface DiffFile {\n /** Stable key for React lists / file-tree selection. */\n id: string;\n oldPath: string | null;\n newPath: string | null;\n /** The path to show — newPath, falling back to oldPath for deletes. */\n displayPath: string;\n status: FileChangeStatus;\n hunks: DiffHunk[];\n additions: number;\n deletions: number;\n /** Raw text that didn't parse as hunks (e.g. a \"Binary files differ\"\n * notice) — preserved verbatim so nothing is silently dropped. */\n unparsedNotice: string | null;\n}\n\nexport interface ParsedDiff {\n files: DiffFile[];\n /** True when the input didn't look like a unified diff at all (no\n * `--- `/`+++ ` pairs found) — the raw string is returned as one\n * unparsed \"file\" so the viewer can still show *something*. */\n malformed: boolean;\n}\n\nconst FILE_HEADER_RE = /^diff --git a\\/(.+) b\\/(.+)$/;\nconst OLD_PATH_RE = /^--- (?:a\\/(.+)|(\\/dev\\/null))\\s*$/;\nconst NEW_PATH_RE = /^\\+\\+\\+ (?:b\\/(.+)|(\\/dev\\/null))\\s*$/;\nconst HUNK_HEADER_RE = /^@@ -(\\d+)(?:,(\\d+))? \\+(\\d+)(?:,(\\d+))? @@(.*)$/;\n\nfunction stripPathPrefix(p: string): string {\n return p.replace(/^[ab]\\//, '');\n}\n\n/** Split raw text into per-file chunks: before each `diff --git` line if\n * any are present, otherwise before each `--- ` line. */\nfunction splitFileChunks(text: string): string[] {\n const lines = text.split('\\n');\n const hasGitHeaders = lines.some((l) => FILE_HEADER_RE.test(l));\n const starts: number[] = [];\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i]!;\n if (hasGitHeaders ? FILE_HEADER_RE.test(line) : OLD_PATH_RE.test(line)) starts.push(i);\n }\n if (starts.length === 0) return [];\n const chunks: string[] = [];\n for (let i = 0; i < starts.length; i++) {\n const from = starts[i]!;\n const to = i + 1 < starts.length ? starts[i + 1]! : lines.length;\n chunks.push(lines.slice(from, to).join('\\n'));\n }\n return chunks;\n}\n\nfunction parseFileChunk(chunk: string, index: number): DiffFile {\n const lines = chunk.split('\\n');\n // A trailing '\\n' in the source text produces one trailing empty array\n // element that is never real diff content — every genuine line (context/\n // add/del) carries at least its 1-char marker (' '/'+'/'-'), so an empty\n // string here can only be that artifact. Drop it before parsing so it\n // isn't mistaken for a blank context line inside the last hunk.\n if (lines.length > 0 && lines[lines.length - 1] === '') lines.pop();\n let oldPath: string | null = null;\n let newPath: string | null = null;\n let bodyStart = -1;\n\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i]!;\n const oldMatch = OLD_PATH_RE.exec(line);\n if (oldMatch) {\n oldPath = oldMatch[2] ? null : stripPathPrefix(oldMatch[1]!);\n continue;\n }\n const newMatch = NEW_PATH_RE.exec(line);\n if (newMatch) {\n newPath = newMatch[2] ? null : stripPathPrefix(newMatch[1]!);\n bodyStart = i + 1;\n break;\n }\n const gitHeader = FILE_HEADER_RE.exec(line);\n if (gitHeader && oldPath === null && newPath === null) {\n // Fallback path source used only if the chunk never reaches a proper\n // `+++` line (e.g. a binary-file notice with no hunks at all).\n oldPath = gitHeader[1] ?? null;\n newPath = gitHeader[2] ?? null;\n }\n }\n\n let status: FileChangeStatus = 'modified';\n if (oldPath === null && newPath !== null) status = 'added';\n else if (oldPath !== null && newPath === null) status = 'deleted';\n else if (oldPath !== null && newPath !== null && oldPath !== newPath) status = 'renamed';\n\n const displayPath = newPath ?? oldPath ?? `(unknown file ${index + 1})`;\n\n const hunks: DiffHunk[] = [];\n let additions = 0;\n let deletions = 0;\n let unparsedNotice: string | null = null;\n\n if (bodyStart === -1) {\n // Never found a `+++` line at all — likely a binary-file notice or a\n // chunk this parser doesn't understand. Preserve it verbatim.\n const trimmed = lines.join('\\n').trim();\n return {\n id: `${index}:${displayPath}`,\n oldPath,\n newPath,\n displayPath,\n status,\n hunks: [],\n additions: 0,\n deletions: 0,\n unparsedNotice: trimmed || null,\n };\n }\n\n let i = bodyStart;\n while (i < lines.length) {\n const header = HUNK_HEADER_RE.exec(lines[i]!);\n if (!header) {\n // Stray text between the file header and the first hunk (rare) —\n // fold into the notice rather than lose it.\n if (lines[i]!.trim()) {\n unparsedNotice = ((unparsedNotice ? unparsedNotice + '\\n' : '') + lines[i]).trim();\n }\n i++;\n continue;\n }\n const oldStart = Number(header[1]);\n const oldLines = header[2] !== undefined ? Number(header[2]) : 1;\n const newStart = Number(header[3]);\n const newLines = header[4] !== undefined ? Number(header[4]) : 1;\n const hunk: DiffHunk = { header: lines[i]!, oldStart, oldLines, newStart, newLines, lines: [] };\n i++;\n let oldNo = oldStart;\n let newNo = newStart;\n while (i < lines.length && !HUNK_HEADER_RE.test(lines[i]!)) {\n const raw = lines[i]!;\n if (raw.startsWith('\\\\')) {\n i++;\n continue; // \"\\"\n }\n const marker = raw[0];\n const text = raw.slice(1);\n if (marker === '+') {\n hunk.lines.push({ kind: 'add', text, oldLineNo: null, newLineNo: newNo });\n newNo++;\n additions++;\n } else if (marker === '-') {\n hunk.lines.push({ kind: 'del', text, oldLineNo: oldNo, newLineNo: null });\n oldNo++;\n deletions++;\n } else {\n // Context line (leading space), or an empty line inside a hunk.\n hunk.lines.push({ kind: 'context', text: marker === ' ' ? text : raw, oldLineNo: oldNo, newLineNo: newNo });\n oldNo++;\n newNo++;\n }\n i++;\n }\n hunks.push(hunk);\n }\n\n return { id: `${index}:${displayPath}`, oldPath, newPath, displayPath, status, hunks, additions, deletions, unparsedNotice };\n}\n\nexport function parseUnifiedDiff(raw: string | undefined | null): ParsedDiff {\n const text = raw ?? '';\n if (!text.trim()) return { files: [], malformed: false };\n const chunks = splitFileChunks(text);\n if (chunks.length === 0) {\n return {\n malformed: true,\n files: [\n {\n id: 'raw',\n oldPath: null,\n newPath: null,\n displayPath: '(unparsed diff)',\n status: 'modified',\n hunks: [],\n additions: 0,\n deletions: 0,\n unparsedNotice: text,\n },\n ],\n };\n }\n return { files: chunks.map((c, idx) => parseFileChunk(c, idx)), malformed: false };\n}\n\nexport interface SplitRow {\n left: DiffLine | null;\n right: DiffLine | null;\n}\n\n/** Zip a hunk's lines into side-by-side rows for the split view: a run of\n * consecutive deletions is paired with the following run of consecutive\n * additions (the usual shape of a changed block), padding the shorter side\n * with a blank cell. Pure context lines occupy both sides identically. */\nexport function toSplitRows(lines: DiffLine[]): SplitRow[] {\n const rows: SplitRow[] = [];\n let i = 0;\n while (i < lines.length) {\n const line = lines[i]!;\n if (line.kind === 'context') {\n rows.push({ left: line, right: line });\n i++;\n continue;\n }\n const dels: DiffLine[] = [];\n while (i < lines.length && lines[i]!.kind === 'del') {\n dels.push(lines[i]!);\n i++;\n }\n const adds: DiffLine[] = [];\n while (i < lines.length && lines[i]!.kind === 'add') {\n adds.push(lines[i]!);\n i++;\n }\n const n = Math.max(dels.length, adds.length);\n for (let j = 0; j < n; j++) rows.push({ left: dels[j] ?? null, right: adds[j] ?? null });\n }\n return rows;\n}\n","/**\n * routes/inbox/highlight.ts — minimal, dependency-free syntax highlighting\n * for the diff viewer. Per the operator-console brief: \"do NOT pull in a\n * heavy dependency without checking what's already available — hand-rolled\n * highlighting for common languages is acceptable and preferable to a large\n * runtime dep.\" This is a single-pass tokenizer per *line* (the diff viewer\n * already renders line-by-line) — cross-line state like multi-line block\n * comments is intentionally not tracked, so an occasional line inside a\n * block comment may be mis-colored. That's a fine trade for zero\n * dependencies and O(n) rendering with no dependency on the file's context\n * outside the visible hunk.\n */\n\nexport type TokenKind = 'plain' | 'comment' | 'string' | 'number' | 'keyword';\n\nexport interface Token {\n kind: TokenKind;\n text: string;\n}\n\nexport type LangFamily = 'c-like' | 'python' | 'shell' | 'css' | 'json' | 'yaml' | 'plain';\n\nconst EXT_TO_FAMILY: Record<string, LangFamily> = {\n ts: 'c-like', tsx: 'c-like', js: 'c-like', jsx: 'c-like', mjs: 'c-like', cjs: 'c-like', mts: 'c-like', cts: 'c-like',\n java: 'c-like', go: 'c-like', rs: 'c-like', c: 'c-like', h: 'c-like', cpp: 'c-like', hpp: 'c-like', cc: 'c-like',\n swift: 'c-like', kt: 'c-like', kts: 'c-like',\n py: 'python', pyi: 'python',\n sh: 'shell', bash: 'shell', zsh: 'shell',\n css: 'css', scss: 'css', less: 'css',\n json: 'json', jsonc: 'json',\n yml: 'yaml', yaml: 'yaml',\n};\n\nexport function languageForPath(path: string | null | undefined): LangFamily {\n if (!path) return 'plain';\n const ext = path.split('.').pop()?.toLowerCase() ?? '';\n return EXT_TO_FAMILY[ext] ?? 'plain';\n}\n\nconst KEYWORDS: Record<LangFamily, Set<string>> = {\n 'c-like': new Set([\n 'const', 'let', 'var', 'function', 'return', 'if', 'else', 'for', 'while', 'do', 'switch', 'case',\n 'break', 'continue', 'class', 'extends', 'implements', 'interface', 'type', 'enum', 'export', 'import',\n 'from', 'default', 'new', 'this', 'super', 'try', 'catch', 'finally', 'throw', 'async', 'await',\n 'yield', 'static', 'public', 'private', 'protected', 'readonly', 'abstract', 'null', 'undefined',\n 'true', 'false', 'void', 'typeof', 'instanceof', 'in', 'of', 'as', 'is', 'never', 'unknown', 'any',\n 'package', 'struct', 'fn', 'impl', 'mod', 'pub', 'use', 'trait', 'match', 'func', 'defer', 'chan',\n ]),\n python: new Set([\n 'def', 'class', 'return', 'if', 'elif', 'else', 'for', 'while', 'break', 'continue', 'import', 'from',\n 'as', 'with', 'try', 'except', 'finally', 'raise', 'pass', 'lambda', 'yield', 'async', 'await', 'None',\n 'True', 'False', 'and', 'or', 'not', 'in', 'is', 'global', 'nonlocal', 'self',\n ]),\n shell: new Set([\n 'if', 'then', 'else', 'elif', 'fi', 'for', 'while', 'do', 'done', 'case', 'esac', 'function', 'return',\n 'export', 'local', 'echo', 'set', 'in', 'break', 'continue',\n ]),\n css: new Set(),\n json: new Set(),\n yaml: new Set(),\n plain: new Set(),\n};\n\n// One combined rule regex per family, using named groups so a single\n// generic scan loop works for every family regardless of which groups it\n// defines. First match wins scanning left-to-right; the gaps between\n// matches become plain-text tokens verbatim.\nconst RULES: Partial<Record<LangFamily, RegExp>> = {\n 'c-like':\n /(?<comment>\\/\\/.*$|\\/\\*.*?\\*\\/)|(?<string>\"(?:[^\"\\\\]|\\\\.)*\"|'(?:[^'\\\\]|\\\\.)*'|`(?:[^`\\\\]|\\\\.)*`)|(?<number>\\b\\d+(?:\\.\\d+)?\\b)|(?<word>[A-Za-z_$][A-Za-z0-9_$]*)/g,\n python:\n /(?<comment>#.*$)|(?<string>\"(?:[^\"\\\\]|\\\\.)*\"|'(?:[^'\\\\]|\\\\.)*')|(?<number>\\b\\d+(?:\\.\\d+)?\\b)|(?<word>[A-Za-z_][A-Za-z0-9_]*)/g,\n shell: /(?<comment>#.*$)|(?<string>\"(?:[^\"\\\\]|\\\\.)*\"|'(?:[^'\\\\]|\\\\.)*')|(?<word>[A-Za-z_][A-Za-z0-9_]*)/g,\n css: /(?<comment>\\/\\*.*?\\*\\/)|(?<string>\"(?:[^\"\\\\]|\\\\.)*\"|'(?:[^'\\\\]|\\\\.)*')|(?<number>\\b\\d+(?:\\.\\d+)?\\b)|(?<word>[A-Za-z_-][A-Za-z0-9_-]*)/g,\n json: /(?<string>\"(?:[^\"\\\\]|\\\\.)*\")|(?<number>-?\\b\\d+(?:\\.\\d+)?\\b)|(?<word>\\btrue\\b|\\bfalse\\b|\\bnull\\b)/g,\n yaml: /(?<comment>#.*$)|(?<string>\"(?:[^\"\\\\]|\\\\.)*\"|'(?:[^'\\\\]|\\\\.)*')/g,\n};\n\nexport function tokenizeLine(text: string, family: LangFamily): Token[] {\n const rule = RULES[family];\n if (!rule) return [{ kind: 'plain', text }];\n const keywords = KEYWORDS[family];\n const tokens: Token[] = [];\n let last = 0;\n rule.lastIndex = 0;\n let match: RegExpExecArray | null;\n while ((match = rule.exec(text))) {\n if (match.index > last) tokens.push({ kind: 'plain', text: text.slice(last, match.index) });\n const g = match.groups ?? {};\n if (g.comment !== undefined) {\n tokens.push({ kind: 'comment', text: match[0] });\n } else if (g.string !== undefined) {\n tokens.push({ kind: 'string', text: match[0] });\n } else if (g.number !== undefined) {\n tokens.push({ kind: 'number', text: match[0] });\n } else if (g.word !== undefined) {\n const isKeyword = family === 'json' || keywords.has(g.word);\n tokens.push({ kind: isKeyword ? 'keyword' : 'plain', text: match[0] });\n } else {\n tokens.push({ kind: 'plain', text: match[0] });\n }\n last = match.index + match[0].length;\n if (match[0].length === 0) rule.lastIndex++; // guard against zero-width matches\n }\n if (last < text.length) tokens.push({ kind: 'plain', text: text.slice(last) });\n return tokens;\n}\n",".viewer {\n display: grid;\n grid-template-columns: minmax(180px, 260px) 1fr;\n gap: var(--space-4);\n align-items: start;\n min-height: 0;\n}\n\n.malformedNotice {\n grid-column: 1 / -1;\n padding: var(--space-2) var(--space-3);\n border-radius: var(--radius-sm);\n background: var(--status-warning-bg);\n color: var(--status-warning-fg);\n font-size: var(--text-xs-size);\n}\n\n.empty {\n padding: var(--space-6);\n text-align: center;\n color: var(--text-tertiary);\n font-size: var(--text-sm-size);\n border: 1px dashed var(--border-default);\n border-radius: var(--radius-md);\n}\n\n.tree {\n display: flex;\n flex-direction: column;\n gap: var(--space-1);\n border: 1px solid var(--border-subtle);\n border-radius: var(--radius-md);\n padding: var(--space-2);\n max-height: 480px;\n overflow-y: auto;\n position: sticky;\n top: 0;\n}\n\n.treeItem {\n display: flex;\n align-items: center;\n gap: var(--space-2);\n width: 100%;\n padding: var(--space-1) var(--space-2);\n border-radius: var(--radius-sm);\n background: transparent;\n border: none;\n text-align: left;\n font-size: var(--text-xs-size);\n color: var(--text-secondary);\n cursor: pointer;\n}\n\n.treeItem:hover {\n background: var(--bg-hover);\n}\n\n.treeItemActive {\n background: var(--bg-selected);\n color: var(--text-primary);\n}\n\n.statusDot {\n flex: 0 0 auto;\n width: 8px;\n height: 8px;\n border-radius: var(--radius-full);\n}\n.status_added { background: var(--status-success-solid); }\n.status_deleted { background: var(--status-danger-solid); }\n.status_modified { background: var(--status-info-solid); }\n.status_renamed { background: var(--status-warning-solid); }\n\n.treePath {\n flex: 1 1 auto;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-family: var(--font-mono);\n}\n\n.treeStats {\n flex: 0 0 auto;\n display: flex;\n gap: var(--space-1);\n font-variant-numeric: tabular-nums;\n}\n.add { color: var(--status-success-fg); }\n.del { color: var(--status-danger-fg); }\n\n.body {\n min-width: 0;\n border: 1px solid var(--border-subtle);\n border-radius: var(--radius-md);\n overflow: hidden;\n}\n\n.fileHeader {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--space-3);\n padding: var(--space-2) var(--space-3);\n border-bottom: 1px solid var(--border-subtle);\n background: var(--bg-canvas);\n}\n\n.filePath {\n font-family: var(--font-mono);\n font-size: var(--text-xs-size);\n color: var(--text-primary);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.modeToggle {\n flex: 0 0 auto;\n display: flex;\n gap: var(--space-1);\n}\n.modeToggle button {\n padding: var(--space-1) var(--space-2);\n font-size: var(--text-xs-size);\n border-radius: var(--radius-sm);\n border: 1px solid var(--border-default);\n background: var(--bg-surface);\n color: var(--text-secondary);\n cursor: pointer;\n}\n.modeActive {\n background: var(--accent-50);\n border-color: var(--accent-500) !important;\n color: var(--accent-600) !important;\n}\n\n.notice {\n margin: var(--space-3);\n padding: var(--space-2);\n background: var(--bg-code);\n border-radius: var(--radius-sm);\n font-family: var(--font-mono);\n font-size: var(--text-xs-size);\n white-space: pre-wrap;\n word-break: break-word;\n}\n\n.emptyFile {\n padding: var(--space-4);\n color: var(--text-tertiary);\n font-size: var(--text-sm-size);\n}\n\n.hunkTable {\n width: 100%;\n border-collapse: collapse;\n font-family: var(--font-mono);\n font-size: var(--text-xs-size);\n table-layout: fixed;\n}\n\n.hunkCaption {\n caption-side: top;\n text-align: left;\n padding: var(--space-1) var(--space-3);\n color: var(--text-tertiary);\n background: var(--bg-canvas);\n border-bottom: 1px solid var(--border-subtle);\n border-top: 1px solid var(--border-subtle);\n}\n\n.lineNo {\n width: 44px;\n min-width: 44px;\n padding: 0 var(--space-2);\n text-align: right;\n color: var(--text-disabled);\n user-select: none;\n vertical-align: top;\n font-variant-numeric: tabular-nums;\n}\n\n.lineMarker {\n width: 16px;\n min-width: 16px;\n text-align: center;\n user-select: none;\n vertical-align: top;\n}\n\n.lineText {\n padding: 0 var(--space-2);\n white-space: pre-wrap;\n word-break: break-word;\n vertical-align: top;\n}\n\n.hunkTableSplit .lineText {\n width: 50%;\n}\n\n.rowBlank {\n background: var(--bg-canvas);\n}\n\n.row_add {\n background: var(--status-success-bg);\n}\n.row_del {\n background: var(--status-danger-bg);\n}\n.row_context {\n background: transparent;\n}\n\n.code {\n font-family: inherit;\n}\n\n.tok_plain { color: var(--text-primary); }\n.tok_comment { color: var(--text-tertiary); font-style: italic; }\n.tok_string { color: var(--status-success-fg); }\n.tok_number { color: var(--status-warning-fg); }\n.tok_keyword { color: var(--accent-600); font-weight: var(--font-weight-medium); }\n","/**\n * routes/inbox/DiffViewer.tsx — the core review interaction (operator-\n * console brief item 2). A multi-file unified diff gets a file tree plus a\n * syntax-highlighted, split/unified-toggleable hunk view instead of one\n * escaped <pre> block (the old UI's approach). No diff/highlighting\n * dependency: diff-parser.ts and highlight.ts are both hand-rolled per the\n * foundation's \"keep new runtime deps minimal\" constraint — checked\n * package.json first; nothing suitable was already a dependency.\n */\nimport { useEffect, useMemo, useState, type ReactNode } from 'react';\nimport { parseUnifiedDiff, toSplitRows, type DiffHunk } from './diff-parser.js';\nimport { languageForPath, tokenizeLine, type LangFamily } from './highlight.js';\nimport styles from './DiffViewer.module.css';\n\ntype ViewMode = 'unified' | 'split';\n\nexport function DiffViewer({ diff, proposalKind }: { diff: string | undefined; proposalKind: string }) {\n const parsed = useMemo(() => parseUnifiedDiff(diff), [diff]);\n const [selectedId, setSelectedId] = useState<string | null>(parsed.files[0]?.id ?? null);\n const [mode, setMode] = useState<ViewMode>('unified');\n\n // Diff identity changed (a different proposal is now shown) — reset the\n // selected file rather than holding on to a stale id from the last one.\n useEffect(() => {\n setSelectedId(parsed.files[0]?.id ?? null);\n // eslint-disable-next-line react-hooks/exhaustive-deps -- reset on diff content change only\n }, [diff]);\n\n if (parsed.files.length === 0) {\n return (\n <div className={styles.empty}>No diff attached to this proposal (kind: {proposalKind}).</div>\n );\n }\n\n const selected = parsed.files.find((f) => f.id === selectedId) ?? parsed.files[0]!;\n\n function onTreeKeyDown(e: React.KeyboardEvent) {\n const idx = parsed.files.findIndex((f) => f.id === selected.id);\n if (e.key === 'ArrowDown') {\n e.preventDefault();\n setSelectedId(parsed.files[Math.min(idx + 1, parsed.files.length - 1)]!.id);\n } else if (e.key === 'ArrowUp') {\n e.preventDefault();\n setSelectedId(parsed.files[Math.max(idx - 1, 0)]!.id);\n }\n }\n\n return (\n <div className={styles.viewer}>\n {parsed.malformed ? (\n <p className={styles.malformedNotice}>Could not parse this as a unified diff — showing raw content.</p>\n ) : null}\n <div className={styles.tree} role=\"tree\" aria-label=\"Changed files\" onKeyDown={onTreeKeyDown}>\n {parsed.files.map((f) => (\n <button\n key={f.id}\n type=\"button\"\n role=\"treeitem\"\n aria-selected={f.id === selected.id}\n tabIndex={f.id === selected.id ? 0 : -1}\n className={`${styles.treeItem} ${f.id === selected.id ? styles.treeItemActive : ''}`}\n onClick={() => setSelectedId(f.id)}\n >\n <span className={`${styles.statusDot} ${styles[`status_${f.status}`]}`} aria-hidden=\"true\" />\n <span className={styles.treePath} title={f.displayPath}>\n {f.displayPath}\n </span>\n <span className={styles.treeStats}>\n {f.additions > 0 ? <span className={styles.add}>+{f.additions}</span> : null}\n {f.deletions > 0 ? <span className={styles.del}>-{f.deletions}</span> : null}\n </span>\n </button>\n ))}\n </div>\n\n <div className={styles.body}>\n <div className={styles.fileHeader}>\n <span className={styles.filePath}>{selected.displayPath}</span>\n <div className={styles.modeToggle} role=\"group\" aria-label=\"Diff view mode\">\n <button\n type=\"button\"\n aria-pressed={mode === 'unified'}\n className={mode === 'unified' ? styles.modeActive : ''}\n onClick={() => setMode('unified')}\n >\n Unified\n </button>\n <button\n type=\"button\"\n aria-pressed={mode === 'split'}\n className={mode === 'split' ? styles.modeActive : ''}\n onClick={() => setMode('split')}\n >\n Split\n </button>\n </div>\n </div>\n {selected.unparsedNotice ? <pre className={styles.notice}>{selected.unparsedNotice}</pre> : null}\n {selected.hunks.length === 0 && !selected.unparsedNotice ? (\n <p className={styles.emptyFile}>No line-level changes captured for this file.</p>\n ) : (\n selected.hunks.map((hunk, i) => <Hunk key={i} hunk={hunk} mode={mode} path={selected.displayPath} />)\n )}\n </div>\n </div>\n );\n}\n\nfunction Hunk({ hunk, mode, path }: { hunk: DiffHunk; mode: ViewMode; path: string }) {\n const lang = languageForPath(path);\n if (mode === 'split') {\n const rows = toSplitRows(hunk.lines);\n return (\n <table className={`${styles.hunkTable} ${styles.hunkTableSplit}`}>\n <caption className={styles.hunkCaption}>{hunk.header}</caption>\n <tbody>\n {rows.map((row, i) => (\n <tr key={i}>\n <td className={styles.lineNo}>{row.left?.oldLineNo ?? ''}</td>\n <td className={`${styles.lineText} ${row.left ? styles[`row_${row.left.kind}`] : styles.rowBlank}`}>\n {row.left ? <CodeLine text={row.left.text} lang={lang} /> : null}\n </td>\n <td className={styles.lineNo}>{row.right?.newLineNo ?? ''}</td>\n <td className={`${styles.lineText} ${row.right ? styles[`row_${row.right.kind}`] : styles.rowBlank}`}>\n {row.right ? <CodeLine text={row.right.text} lang={lang} /> : null}\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n );\n }\n return (\n <table className={styles.hunkTable}>\n <caption className={styles.hunkCaption}>{hunk.header}</caption>\n <tbody>\n {hunk.lines.map((line, i) => (\n <tr key={i} className={styles[`row_${line.kind}`]}>\n <td className={styles.lineNo}>{line.oldLineNo ?? ''}</td>\n <td className={styles.lineNo}>{line.newLineNo ?? ''}</td>\n <td className={styles.lineMarker} aria-hidden=\"true\">\n {line.kind === 'add' ? '+' : line.kind === 'del' ? '-' : ''}\n </td>\n <td className={styles.lineText}>\n <CodeLine text={line.text} lang={lang} />\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n );\n}\n\nfunction CodeLine({ text, lang }: { text: string; lang: LangFamily }): ReactNode {\n const tokens = tokenizeLine(text, lang);\n return (\n <code className={styles.code}>\n {tokens.map((t, i) => (\n <span key={i} className={styles[`tok_${t.kind}`]}>\n {t.text}\n </span>\n ))}\n </code>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"DiffViewer-D369eZe8.js","names":[],"sources":["../../../../../../src/web-ui/routes/inbox/diff-parser.ts","../../../../../../src/web-ui/routes/inbox/highlight.ts","../../../../../../src/web-ui/routes/inbox/DiffViewer.module.css","../../../../../../src/web-ui/routes/inbox/DiffViewer.tsx"],"sourcesContent":["/**\n * routes/inbox/diff-parser.ts — pure parsing of a unified diff string\n * (Proposal.diff, src/core/types.ts) into a per-file, per-hunk structure the\n * DiffViewer can render with a file tree and syntax highlighting, instead of\n * dumping the raw string into an escaped <pre> block (the old UI's\n * approach — \"not acceptable for a tool meant to be elite\" per the\n * operator-console brief). No dependency: hand-rolled parser over the\n * unified diff format, which is regular enough (`--- `/`+++ `/`@@ `/leading\n * `-`/`+`/` `) that a small line-scan state machine covers it completely.\n *\n * Pure functions only — no React, no DOM — so this is trivially unit\n * testable (see diff-parser.test.ts) independent of rendering.\n */\n\nexport type DiffLineKind = 'context' | 'add' | 'del';\n\nexport interface DiffLine {\n kind: DiffLineKind;\n text: string;\n oldLineNo: number | null;\n newLineNo: number | null;\n}\n\nexport interface DiffHunk {\n header: string;\n oldStart: number;\n oldLines: number;\n newStart: number;\n newLines: number;\n lines: DiffLine[];\n}\n\nexport type FileChangeStatus = 'added' | 'deleted' | 'renamed' | 'modified';\n\nexport interface DiffFile {\n /** Stable key for React lists / file-tree selection. */\n id: string;\n oldPath: string | null;\n newPath: string | null;\n /** The path to show — newPath, falling back to oldPath for deletes. */\n displayPath: string;\n status: FileChangeStatus;\n hunks: DiffHunk[];\n additions: number;\n deletions: number;\n /** Raw text that didn't parse as hunks (e.g. a \"Binary files differ\"\n * notice) — preserved verbatim so nothing is silently dropped. */\n unparsedNotice: string | null;\n}\n\nexport interface ParsedDiff {\n files: DiffFile[];\n /** True when the input didn't look like a unified diff at all (no\n * `--- `/`+++ ` pairs found) — the raw string is returned as one\n * unparsed \"file\" so the viewer can still show *something*. */\n malformed: boolean;\n}\n\nconst FILE_HEADER_RE = /^diff --git a\\/(.+) b\\/(.+)$/;\nconst OLD_PATH_RE = /^--- (?:a\\/(.+)|(\\/dev\\/null))\\s*$/;\nconst NEW_PATH_RE = /^\\+\\+\\+ (?:b\\/(.+)|(\\/dev\\/null))\\s*$/;\nconst HUNK_HEADER_RE = /^@@ -(\\d+)(?:,(\\d+))? \\+(\\d+)(?:,(\\d+))? @@(.*)$/;\n\nfunction stripPathPrefix(p: string): string {\n return p.replace(/^[ab]\\//, '');\n}\n\n/** Split raw text into per-file chunks: before each `diff --git` line if\n * any are present, otherwise before each `--- ` line. */\nfunction splitFileChunks(text: string): string[] {\n const lines = text.split('\\n');\n const hasGitHeaders = lines.some((l) => FILE_HEADER_RE.test(l));\n const starts: number[] = [];\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i]!;\n if (hasGitHeaders ? FILE_HEADER_RE.test(line) : OLD_PATH_RE.test(line)) starts.push(i);\n }\n if (starts.length === 0) return [];\n const chunks: string[] = [];\n for (let i = 0; i < starts.length; i++) {\n const from = starts[i]!;\n const to = i + 1 < starts.length ? starts[i + 1]! : lines.length;\n chunks.push(lines.slice(from, to).join('\\n'));\n }\n return chunks;\n}\n\nfunction parseFileChunk(chunk: string, index: number): DiffFile {\n const lines = chunk.split('\\n');\n // A trailing '\\n' in the source text produces one trailing empty array\n // element that is never real diff content — every genuine line (context/\n // add/del) carries at least its 1-char marker (' '/'+'/'-'), so an empty\n // string here can only be that artifact. Drop it before parsing so it\n // isn't mistaken for a blank context line inside the last hunk.\n if (lines.length > 0 && lines[lines.length - 1] === '') lines.pop();\n let oldPath: string | null = null;\n let newPath: string | null = null;\n let bodyStart = -1;\n\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i]!;\n const oldMatch = OLD_PATH_RE.exec(line);\n if (oldMatch) {\n oldPath = oldMatch[2] ? null : stripPathPrefix(oldMatch[1]!);\n continue;\n }\n const newMatch = NEW_PATH_RE.exec(line);\n if (newMatch) {\n newPath = newMatch[2] ? null : stripPathPrefix(newMatch[1]!);\n bodyStart = i + 1;\n break;\n }\n const gitHeader = FILE_HEADER_RE.exec(line);\n if (gitHeader && oldPath === null && newPath === null) {\n // Fallback path source used only if the chunk never reaches a proper\n // `+++` line (e.g. a binary-file notice with no hunks at all).\n oldPath = gitHeader[1] ?? null;\n newPath = gitHeader[2] ?? null;\n }\n }\n\n let status: FileChangeStatus = 'modified';\n if (oldPath === null && newPath !== null) status = 'added';\n else if (oldPath !== null && newPath === null) status = 'deleted';\n else if (oldPath !== null && newPath !== null && oldPath !== newPath) status = 'renamed';\n\n const displayPath = newPath ?? oldPath ?? `(unknown file ${index + 1})`;\n\n const hunks: DiffHunk[] = [];\n let additions = 0;\n let deletions = 0;\n let unparsedNotice: string | null = null;\n\n if (bodyStart === -1) {\n // Never found a `+++` line at all — likely a binary-file notice or a\n // chunk this parser doesn't understand. Preserve it verbatim.\n const trimmed = lines.join('\\n').trim();\n return {\n id: `${index}:${displayPath}`,\n oldPath,\n newPath,\n displayPath,\n status,\n hunks: [],\n additions: 0,\n deletions: 0,\n unparsedNotice: trimmed || null,\n };\n }\n\n let i = bodyStart;\n while (i < lines.length) {\n const header = HUNK_HEADER_RE.exec(lines[i]!);\n if (!header) {\n // Stray text between the file header and the first hunk (rare) —\n // fold into the notice rather than lose it.\n if (lines[i]!.trim()) {\n unparsedNotice = ((unparsedNotice ? unparsedNotice + '\\n' : '') + lines[i]).trim();\n }\n i++;\n continue;\n }\n const oldStart = Number(header[1]);\n const oldLines = header[2] !== undefined ? Number(header[2]) : 1;\n const newStart = Number(header[3]);\n const newLines = header[4] !== undefined ? Number(header[4]) : 1;\n const hunk: DiffHunk = { header: lines[i]!, oldStart, oldLines, newStart, newLines, lines: [] };\n i++;\n let oldNo = oldStart;\n let newNo = newStart;\n while (i < lines.length && !HUNK_HEADER_RE.test(lines[i]!)) {\n const raw = lines[i]!;\n if (raw.startsWith('\\\\')) {\n i++;\n continue; // \"\\"\n }\n const marker = raw[0];\n const text = raw.slice(1);\n if (marker === '+') {\n hunk.lines.push({ kind: 'add', text, oldLineNo: null, newLineNo: newNo });\n newNo++;\n additions++;\n } else if (marker === '-') {\n hunk.lines.push({ kind: 'del', text, oldLineNo: oldNo, newLineNo: null });\n oldNo++;\n deletions++;\n } else {\n // Context line (leading space), or an empty line inside a hunk.\n hunk.lines.push({ kind: 'context', text: marker === ' ' ? text : raw, oldLineNo: oldNo, newLineNo: newNo });\n oldNo++;\n newNo++;\n }\n i++;\n }\n hunks.push(hunk);\n }\n\n return { id: `${index}:${displayPath}`, oldPath, newPath, displayPath, status, hunks, additions, deletions, unparsedNotice };\n}\n\nexport function parseUnifiedDiff(raw: string | undefined | null): ParsedDiff {\n const text = raw ?? '';\n if (!text.trim()) return { files: [], malformed: false };\n const chunks = splitFileChunks(text);\n if (chunks.length === 0) {\n return {\n malformed: true,\n files: [\n {\n id: 'raw',\n oldPath: null,\n newPath: null,\n displayPath: '(unparsed diff)',\n status: 'modified',\n hunks: [],\n additions: 0,\n deletions: 0,\n unparsedNotice: text,\n },\n ],\n };\n }\n return { files: chunks.map((c, idx) => parseFileChunk(c, idx)), malformed: false };\n}\n\nexport interface SplitRow {\n left: DiffLine | null;\n right: DiffLine | null;\n}\n\n/** Zip a hunk's lines into side-by-side rows for the split view: a run of\n * consecutive deletions is paired with the following run of consecutive\n * additions (the usual shape of a changed block), padding the shorter side\n * with a blank cell. Pure context lines occupy both sides identically. */\nexport function toSplitRows(lines: DiffLine[]): SplitRow[] {\n const rows: SplitRow[] = [];\n let i = 0;\n while (i < lines.length) {\n const line = lines[i]!;\n if (line.kind === 'context') {\n rows.push({ left: line, right: line });\n i++;\n continue;\n }\n const dels: DiffLine[] = [];\n while (i < lines.length && lines[i]!.kind === 'del') {\n dels.push(lines[i]!);\n i++;\n }\n const adds: DiffLine[] = [];\n while (i < lines.length && lines[i]!.kind === 'add') {\n adds.push(lines[i]!);\n i++;\n }\n const n = Math.max(dels.length, adds.length);\n for (let j = 0; j < n; j++) rows.push({ left: dels[j] ?? null, right: adds[j] ?? null });\n }\n return rows;\n}\n","/**\n * routes/inbox/highlight.ts — minimal, dependency-free syntax highlighting\n * for the diff viewer. Per the operator-console brief: \"do NOT pull in a\n * heavy dependency without checking what's already available — hand-rolled\n * highlighting for common languages is acceptable and preferable to a large\n * runtime dep.\" This is a single-pass tokenizer per *line* (the diff viewer\n * already renders line-by-line) — cross-line state like multi-line block\n * comments is intentionally not tracked, so an occasional line inside a\n * block comment may be mis-colored. That's a fine trade for zero\n * dependencies and O(n) rendering with no dependency on the file's context\n * outside the visible hunk.\n */\n\nexport type TokenKind = 'plain' | 'comment' | 'string' | 'number' | 'keyword';\n\nexport interface Token {\n kind: TokenKind;\n text: string;\n}\n\nexport type LangFamily = 'c-like' | 'python' | 'shell' | 'css' | 'json' | 'yaml' | 'plain';\n\nconst EXT_TO_FAMILY: Record<string, LangFamily> = {\n ts: 'c-like', tsx: 'c-like', js: 'c-like', jsx: 'c-like', mjs: 'c-like', cjs: 'c-like', mts: 'c-like', cts: 'c-like',\n java: 'c-like', go: 'c-like', rs: 'c-like', c: 'c-like', h: 'c-like', cpp: 'c-like', hpp: 'c-like', cc: 'c-like',\n swift: 'c-like', kt: 'c-like', kts: 'c-like',\n py: 'python', pyi: 'python',\n sh: 'shell', bash: 'shell', zsh: 'shell',\n css: 'css', scss: 'css', less: 'css',\n json: 'json', jsonc: 'json',\n yml: 'yaml', yaml: 'yaml',\n};\n\nexport function languageForPath(path: string | null | undefined): LangFamily {\n if (!path) return 'plain';\n const ext = path.split('.').pop()?.toLowerCase() ?? '';\n return EXT_TO_FAMILY[ext] ?? 'plain';\n}\n\nconst KEYWORDS: Record<LangFamily, Set<string>> = {\n 'c-like': new Set([\n 'const', 'let', 'var', 'function', 'return', 'if', 'else', 'for', 'while', 'do', 'switch', 'case',\n 'break', 'continue', 'class', 'extends', 'implements', 'interface', 'type', 'enum', 'export', 'import',\n 'from', 'default', 'new', 'this', 'super', 'try', 'catch', 'finally', 'throw', 'async', 'await',\n 'yield', 'static', 'public', 'private', 'protected', 'readonly', 'abstract', 'null', 'undefined',\n 'true', 'false', 'void', 'typeof', 'instanceof', 'in', 'of', 'as', 'is', 'never', 'unknown', 'any',\n 'package', 'struct', 'fn', 'impl', 'mod', 'pub', 'use', 'trait', 'match', 'func', 'defer', 'chan',\n ]),\n python: new Set([\n 'def', 'class', 'return', 'if', 'elif', 'else', 'for', 'while', 'break', 'continue', 'import', 'from',\n 'as', 'with', 'try', 'except', 'finally', 'raise', 'pass', 'lambda', 'yield', 'async', 'await', 'None',\n 'True', 'False', 'and', 'or', 'not', 'in', 'is', 'global', 'nonlocal', 'self',\n ]),\n shell: new Set([\n 'if', 'then', 'else', 'elif', 'fi', 'for', 'while', 'do', 'done', 'case', 'esac', 'function', 'return',\n 'export', 'local', 'echo', 'set', 'in', 'break', 'continue',\n ]),\n css: new Set(),\n json: new Set(),\n yaml: new Set(),\n plain: new Set(),\n};\n\n// One combined rule regex per family, using named groups so a single\n// generic scan loop works for every family regardless of which groups it\n// defines. First match wins scanning left-to-right; the gaps between\n// matches become plain-text tokens verbatim.\nconst RULES: Partial<Record<LangFamily, RegExp>> = {\n 'c-like':\n /(?<comment>\\/\\/.*$|\\/\\*.*?\\*\\/)|(?<string>\"(?:[^\"\\\\]|\\\\.)*\"|'(?:[^'\\\\]|\\\\.)*'|`(?:[^`\\\\]|\\\\.)*`)|(?<number>\\b\\d+(?:\\.\\d+)?\\b)|(?<word>[A-Za-z_$][A-Za-z0-9_$]*)/g,\n python:\n /(?<comment>#.*$)|(?<string>\"(?:[^\"\\\\]|\\\\.)*\"|'(?:[^'\\\\]|\\\\.)*')|(?<number>\\b\\d+(?:\\.\\d+)?\\b)|(?<word>[A-Za-z_][A-Za-z0-9_]*)/g,\n shell: /(?<comment>#.*$)|(?<string>\"(?:[^\"\\\\]|\\\\.)*\"|'(?:[^'\\\\]|\\\\.)*')|(?<word>[A-Za-z_][A-Za-z0-9_]*)/g,\n css: /(?<comment>\\/\\*.*?\\*\\/)|(?<string>\"(?:[^\"\\\\]|\\\\.)*\"|'(?:[^'\\\\]|\\\\.)*')|(?<number>\\b\\d+(?:\\.\\d+)?\\b)|(?<word>[A-Za-z_-][A-Za-z0-9_-]*)/g,\n json: /(?<string>\"(?:[^\"\\\\]|\\\\.)*\")|(?<number>-?\\b\\d+(?:\\.\\d+)?\\b)|(?<word>\\btrue\\b|\\bfalse\\b|\\bnull\\b)/g,\n yaml: /(?<comment>#.*$)|(?<string>\"(?:[^\"\\\\]|\\\\.)*\"|'(?:[^'\\\\]|\\\\.)*')/g,\n};\n\nexport function tokenizeLine(text: string, family: LangFamily): Token[] {\n const rule = RULES[family];\n if (!rule) return [{ kind: 'plain', text }];\n const keywords = KEYWORDS[family];\n const tokens: Token[] = [];\n let last = 0;\n rule.lastIndex = 0;\n let match: RegExpExecArray | null;\n while ((match = rule.exec(text))) {\n if (match.index > last) tokens.push({ kind: 'plain', text: text.slice(last, match.index) });\n const g = match.groups ?? {};\n if (g.comment !== undefined) {\n tokens.push({ kind: 'comment', text: match[0] });\n } else if (g.string !== undefined) {\n tokens.push({ kind: 'string', text: match[0] });\n } else if (g.number !== undefined) {\n tokens.push({ kind: 'number', text: match[0] });\n } else if (g.word !== undefined) {\n const isKeyword = family === 'json' || keywords.has(g.word);\n tokens.push({ kind: isKeyword ? 'keyword' : 'plain', text: match[0] });\n } else {\n tokens.push({ kind: 'plain', text: match[0] });\n }\n last = match.index + match[0].length;\n if (match[0].length === 0) rule.lastIndex++; // guard against zero-width matches\n }\n if (last < text.length) tokens.push({ kind: 'plain', text: text.slice(last) });\n return tokens;\n}\n",".viewer {\n display: grid;\n grid-template-columns: minmax(180px, 260px) 1fr;\n gap: var(--space-4);\n align-items: start;\n min-height: 0;\n}\n\n.malformedNotice {\n grid-column: 1 / -1;\n padding: var(--space-2) var(--space-3);\n border-radius: var(--radius-sm);\n background: var(--status-warning-bg);\n color: var(--status-warning-fg);\n font-size: var(--text-xs-size);\n}\n\n.empty {\n padding: var(--space-6);\n text-align: center;\n color: var(--text-tertiary);\n font-size: var(--text-sm-size);\n border: 1px dashed var(--border-default);\n border-radius: var(--radius-md);\n}\n\n.tree {\n display: flex;\n flex-direction: column;\n gap: var(--space-1);\n border: 1px solid var(--border-subtle);\n border-radius: var(--radius-md);\n padding: var(--space-2);\n max-height: 480px;\n overflow-y: auto;\n position: sticky;\n top: 0;\n}\n\n.treeItem {\n display: flex;\n align-items: center;\n gap: var(--space-2);\n width: 100%;\n padding: var(--space-1) var(--space-2);\n border-radius: var(--radius-sm);\n background: transparent;\n border: none;\n text-align: left;\n font-size: var(--text-xs-size);\n color: var(--text-secondary);\n cursor: pointer;\n}\n\n.treeItem:hover {\n background: var(--bg-hover);\n}\n\n.treeItemActive {\n background: var(--bg-selected);\n color: var(--text-primary);\n}\n\n.statusDot {\n flex: 0 0 auto;\n width: 8px;\n height: 8px;\n border-radius: var(--radius-full);\n}\n.status_added { background: var(--status-success-solid); }\n.status_deleted { background: var(--status-danger-solid); }\n.status_modified { background: var(--status-info-solid); }\n.status_renamed { background: var(--status-warning-solid); }\n\n.treePath {\n flex: 1 1 auto;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-family: var(--font-mono);\n}\n\n.treeStats {\n flex: 0 0 auto;\n display: flex;\n gap: var(--space-1);\n font-variant-numeric: tabular-nums;\n}\n.add { color: var(--status-success-fg); }\n.del { color: var(--status-danger-fg); }\n\n.body {\n min-width: 0;\n border: 1px solid var(--border-subtle);\n border-radius: var(--radius-md);\n overflow: hidden;\n}\n\n.fileHeader {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--space-3);\n padding: var(--space-2) var(--space-3);\n border-bottom: 1px solid var(--border-subtle);\n background: var(--bg-canvas);\n}\n\n.filePath {\n font-family: var(--font-mono);\n font-size: var(--text-xs-size);\n color: var(--text-primary);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.modeToggle {\n flex: 0 0 auto;\n display: flex;\n gap: var(--space-1);\n}\n.modeToggle button {\n padding: var(--space-1) var(--space-2);\n font-size: var(--text-xs-size);\n border-radius: var(--radius-sm);\n border: 1px solid var(--border-default);\n background: var(--bg-surface);\n color: var(--text-secondary);\n cursor: pointer;\n}\n.modeActive {\n background: var(--accent-50);\n border-color: var(--accent-500) !important;\n color: var(--accent-600) !important;\n}\n\n.notice {\n margin: var(--space-3);\n padding: var(--space-2);\n background: var(--bg-code);\n border-radius: var(--radius-sm);\n font-family: var(--font-mono);\n font-size: var(--text-xs-size);\n white-space: pre-wrap;\n word-break: break-word;\n}\n\n.emptyFile {\n padding: var(--space-4);\n color: var(--text-tertiary);\n font-size: var(--text-sm-size);\n}\n\n.hunkTable {\n width: 100%;\n border-collapse: collapse;\n font-family: var(--font-mono);\n font-size: var(--text-xs-size);\n table-layout: fixed;\n}\n\n.hunkCaption {\n caption-side: top;\n text-align: left;\n padding: var(--space-1) var(--space-3);\n color: var(--text-tertiary);\n background: var(--bg-canvas);\n border-bottom: 1px solid var(--border-subtle);\n border-top: 1px solid var(--border-subtle);\n}\n\n.lineNo {\n width: 44px;\n min-width: 44px;\n padding: 0 var(--space-2);\n text-align: right;\n color: var(--text-disabled);\n user-select: none;\n vertical-align: top;\n font-variant-numeric: tabular-nums;\n}\n\n.lineMarker {\n width: 16px;\n min-width: 16px;\n text-align: center;\n user-select: none;\n vertical-align: top;\n}\n\n.lineText {\n padding: 0 var(--space-2);\n white-space: pre-wrap;\n word-break: break-word;\n vertical-align: top;\n}\n\n.hunkTableSplit .lineText {\n width: 50%;\n}\n\n.rowBlank {\n background: var(--bg-canvas);\n}\n\n.row_add {\n background: var(--status-success-bg);\n}\n.row_del {\n background: var(--status-danger-bg);\n}\n.row_context {\n background: transparent;\n}\n\n.code {\n font-family: inherit;\n}\n\n.tok_plain { color: var(--text-primary); }\n.tok_comment { color: var(--text-tertiary); font-style: italic; }\n.tok_string { color: var(--status-success-fg); }\n.tok_number { color: var(--status-warning-fg); }\n.tok_keyword { color: var(--accent-600); font-weight: var(--font-weight-medium); }\n","/**\n * routes/inbox/DiffViewer.tsx — the core review interaction (operator-\n * console brief item 2). A multi-file unified diff gets a file tree plus a\n * syntax-highlighted, split/unified-toggleable hunk view instead of one\n * escaped <pre> block (the old UI's approach). No diff/highlighting\n * dependency: diff-parser.ts and highlight.ts are both hand-rolled per the\n * foundation's \"keep new runtime deps minimal\" constraint — checked\n * package.json first; nothing suitable was already a dependency.\n */\nimport { useEffect, useMemo, useState, type ReactNode } from 'react';\nimport { parseUnifiedDiff, toSplitRows, type DiffHunk } from './diff-parser.js';\nimport { languageForPath, tokenizeLine, type LangFamily } from './highlight.js';\nimport styles from './DiffViewer.module.css';\n\ntype ViewMode = 'unified' | 'split';\n\nexport function DiffViewer({ diff, proposalKind }: { diff: string | undefined; proposalKind: string }) {\n const parsed = useMemo(() => parseUnifiedDiff(diff), [diff]);\n const [selectedId, setSelectedId] = useState<string | null>(parsed.files[0]?.id ?? null);\n const [mode, setMode] = useState<ViewMode>('unified');\n\n // Diff identity changed (a different proposal is now shown) — reset the\n // selected file rather than holding on to a stale id from the last one.\n useEffect(() => {\n setSelectedId(parsed.files[0]?.id ?? null);\n // eslint-disable-next-line react-hooks/exhaustive-deps -- reset on diff content change only\n }, [diff]);\n\n if (parsed.files.length === 0) {\n return (\n <div className={styles.empty}>No diff attached to this proposal (kind: {proposalKind}).</div>\n );\n }\n\n const selected = parsed.files.find((f) => f.id === selectedId) ?? parsed.files[0]!;\n\n function onTreeKeyDown(e: React.KeyboardEvent) {\n const idx = parsed.files.findIndex((f) => f.id === selected.id);\n if (e.key === 'ArrowDown') {\n e.preventDefault();\n setSelectedId(parsed.files[Math.min(idx + 1, parsed.files.length - 1)]!.id);\n } else if (e.key === 'ArrowUp') {\n e.preventDefault();\n setSelectedId(parsed.files[Math.max(idx - 1, 0)]!.id);\n }\n }\n\n return (\n <div className={styles.viewer}>\n {parsed.malformed ? (\n <p className={styles.malformedNotice}>Could not parse this as a unified diff — showing raw content.</p>\n ) : null}\n <div className={styles.tree} role=\"tree\" aria-label=\"Changed files\" onKeyDown={onTreeKeyDown}>\n {parsed.files.map((f) => (\n <button\n key={f.id}\n type=\"button\"\n role=\"treeitem\"\n aria-selected={f.id === selected.id}\n tabIndex={f.id === selected.id ? 0 : -1}\n className={`${styles.treeItem} ${f.id === selected.id ? styles.treeItemActive : ''}`}\n onClick={() => setSelectedId(f.id)}\n >\n <span className={`${styles.statusDot} ${styles[`status_${f.status}`]}`} aria-hidden=\"true\" />\n <span className={styles.treePath} title={f.displayPath}>\n {f.displayPath}\n </span>\n <span className={styles.treeStats}>\n {f.additions > 0 ? <span className={styles.add}>+{f.additions}</span> : null}\n {f.deletions > 0 ? <span className={styles.del}>-{f.deletions}</span> : null}\n </span>\n </button>\n ))}\n </div>\n\n <div className={styles.body}>\n <div className={styles.fileHeader}>\n <span className={styles.filePath}>{selected.displayPath}</span>\n <div className={styles.modeToggle} role=\"group\" aria-label=\"Diff view mode\">\n <button\n type=\"button\"\n aria-pressed={mode === 'unified'}\n className={mode === 'unified' ? styles.modeActive : ''}\n onClick={() => setMode('unified')}\n >\n Unified\n </button>\n <button\n type=\"button\"\n aria-pressed={mode === 'split'}\n className={mode === 'split' ? styles.modeActive : ''}\n onClick={() => setMode('split')}\n >\n Split\n </button>\n </div>\n </div>\n {selected.unparsedNotice ? <pre className={styles.notice}>{selected.unparsedNotice}</pre> : null}\n {selected.hunks.length === 0 && !selected.unparsedNotice ? (\n <p className={styles.emptyFile}>No line-level changes captured for this file.</p>\n ) : (\n selected.hunks.map((hunk, i) => <Hunk key={i} hunk={hunk} mode={mode} path={selected.displayPath} />)\n )}\n </div>\n </div>\n );\n}\n\nfunction Hunk({ hunk, mode, path }: { hunk: DiffHunk; mode: ViewMode; path: string }) {\n const lang = languageForPath(path);\n if (mode === 'split') {\n const rows = toSplitRows(hunk.lines);\n return (\n <table className={`${styles.hunkTable} ${styles.hunkTableSplit}`}>\n <caption className={styles.hunkCaption}>{hunk.header}</caption>\n <tbody>\n {rows.map((row, i) => (\n <tr key={i}>\n <td className={styles.lineNo}>{row.left?.oldLineNo ?? ''}</td>\n <td className={`${styles.lineText} ${row.left ? styles[`row_${row.left.kind}`] : styles.rowBlank}`}>\n {row.left ? <CodeLine text={row.left.text} lang={lang} /> : null}\n </td>\n <td className={styles.lineNo}>{row.right?.newLineNo ?? ''}</td>\n <td className={`${styles.lineText} ${row.right ? styles[`row_${row.right.kind}`] : styles.rowBlank}`}>\n {row.right ? <CodeLine text={row.right.text} lang={lang} /> : null}\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n );\n }\n return (\n <table className={styles.hunkTable}>\n <caption className={styles.hunkCaption}>{hunk.header}</caption>\n <tbody>\n {hunk.lines.map((line, i) => (\n <tr key={i} className={styles[`row_${line.kind}`]}>\n <td className={styles.lineNo}>{line.oldLineNo ?? ''}</td>\n <td className={styles.lineNo}>{line.newLineNo ?? ''}</td>\n <td className={styles.lineMarker} aria-hidden=\"true\">\n {line.kind === 'add' ? '+' : line.kind === 'del' ? '-' : ''}\n </td>\n <td className={styles.lineText}>\n <CodeLine text={line.text} lang={lang} />\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n );\n}\n\nfunction CodeLine({ text, lang }: { text: string; lang: LangFamily }): ReactNode {\n const tokens = tokenizeLine(text, lang);\n return (\n <code className={styles.code}>\n {tokens.map((t, i) => (\n <span key={i} className={styles[`tok_${t.kind}`]}>\n {t.text}\n </span>\n ))}\n </code>\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
._section_mum7s_11{background:var(--bg-canvas);flex-direction:column;height:100%;min-height:0;display:flex}._body_mum7s_19{min-height:0;padding:var(--space-5);gap:var(--space-6);flex-direction:column;flex:1;display:flex;overflow-y:auto}._panel_mum7s_29{gap:var(--space-3);flex-direction:column;display:flex}._panelHead_mum7s_35{gap:var(--space-1);padding-bottom:var(--space-2);border-bottom:1px solid var(--border-subtle);flex-direction:column;display:flex}._panelTitle_mum7s_43{font-family:var(--font-display);font-size:var(--text-md-size);font-weight:var(--font-weight-semibold);color:var(--text-primary);margin:0}._panelNote_mum7s_51,._muted_mum7s_52{font-size:var(--text-sm-size);color:var(--text-secondary);margin:0;line-height:1.5}._alert_mum7s_61{gap:var(--space-1);padding:var(--space-3) var(--space-4);border:1px solid var(--border-warning,var(--border-strong));border-radius:var(--radius-sm);background:var(--bg-subtle);border-left-width:3px;flex-direction:column;display:flex}._alertTitle_mum7s_72{font-weight:var(--font-weight-semibold);color:var(--text-primary);font-size:var(--text-sm-size)}._seatList_mum7s_78,._rowList_mum7s_79{flex-direction:column;gap:0;display:flex}._seatRow_mum7s_85,._healthRow_mum7s_86{gap:var(--space-2);padding:var(--space-3) 0;border-bottom:1px solid var(--border-subtle);flex-direction:column;display:flex}._seatHead_mum7s_94{align-items:baseline;gap:var(--space-2);flex-wrap:wrap;display:flex}._seatLabel_mum7s_101{font-weight:var(--font-weight-semibold);color:var(--text-primary)}._engineTag_mum7s_106,._code_mum7s_107{font-family:var(--font-mono);font-size:var(--text-xs-size);color:var(--text-tertiary,var(--text-secondary))}._code_mum7s_107{letter-spacing:0}._count_mum7s_118{font-size:var(--text-sm-size);color:var(--text-secondary)}._disclosure_mum7s_124{gap:var(--space-2);padding:var(--space-3) var(--space-4);border:1px solid var(--border-subtle);border-radius:var(--radius-sm);background:var(--bg-subtle);flex-direction:column;display:flex}._commandLine_mum7s_134{font-family:var(--font-mono);font-size:var(--text-sm-size);color:var(--text-primary);word-break:break-all;white-space:pre-wrap;margin:0}._envList_mum7s_143{gap:var(--space-2);flex-wrap:wrap;margin:0;padding:0;list-style:none;display:flex}._envItem_mum7s_152{font-family:var(--font-mono);font-size:var(--text-xs-size);padding:2px var(--space-2);border:1px solid var(--border-subtle);border-radius:var(--radius-sm);color:var(--text-secondary)}._warnList_mum7s_161{padding-left:var(--space-4);gap:var(--space-1);font-size:var(--text-sm-size);color:var(--text-primary);flex-direction:column;margin:0;display:flex}._digest_mum7s_171{font-family:var(--font-mono);font-size:var(--text-xs-size);color:var(--text-tertiary,var(--text-secondary));word-break:break-all}._actions_mum7s_178{gap:var(--space-2);flex-wrap:wrap;align-items:center;display:flex}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{n as e}from"./index-CXgVQHek.js";import{l as t,p as n,s as r,v as i}from"./global-DMWakEhP.js";var a={name:`name`,command:`command`,args:`args`,env:`env`,sourceRef:`sourceRef`},o={seatId:`seatId`,label:`label`,engine:`engine`,accountId:`accountId`,servers:`servers`,reason:`reason`,configFile:`configFile`,configFormat:`configFormat`,notes:`notes`},s={servers:`servers`,configured:`configured`,note:`note`},c={sampledAt:`sampledAt`,seats:`seats`,machine:`machine`,scope:`scope`,notes:`notes`},l={accountId:`accountId`,label:`label`,provider:`provider`,authentication:`authentication`,state:`state`,planType:`planType`,reason:`reason`,version:`version`,pinnedVersion:`pinnedVersion`,versionState:`versionState`,usageBlockedByPin:`usageBlockedByPin`,notes:`notes`},u={sampledAt:`sampledAt`,accounts:`accounts`,driftDetected:`driftDetected`,notes:`notes`},d={available:`available`,pinned:`pinned`,aliasRef:`aliasRef`,tenantRef:`tenantRef`,principalRef:`principalRef`,reason:`reason`};function f(e){return typeof e==`object`&&e&&!Array.isArray(e)?e:null}function p(e){return typeof e==`string`?e:null}function m(e){return Array.isArray(e)?e.filter(e=>typeof e==`string`):[]}function h(e){return e===!0}function g(e){let t=f(e);if(t===null)return null;let n=p(t[a.name]),r=p(t[a.command]);if(n===null||r===null)return null;let i=null,o=f(t[a.env]);if(o!==null){let e=Object.keys(o);if(e.length>0){i={};for(let t of e)i[t]=`<set>`}}return{name:n,command:r,args:m(t[a.args]),env:i,sourceRef:p(t[a.sourceRef])??`(unknown source)`}}function _(e){let t=f(e);if(t===null)return null;let n=p(t[o.seatId]);if(n===null)return null;let r=Array.isArray(t[o.servers])?t[o.servers].map(g).filter(e=>e!==null):[];return{seatId:n,label:p(t[o.label])??n,engine:p(t[o.engine])??`unknown`,accountId:p(t[o.accountId])??n,servers:r,reason:p(t[o.reason])??`unknown`,configFile:p(t[o.configFile]),configFormat:p(t[o.configFormat]),notes:m(t[o.notes])}}function v(e){let t=f(e);return t===null?null:{available:h(t[d.available]),pinned:h(t[d.pinned]),aliasRef:p(t[d.aliasRef]),tenantRef:p(t[d.tenantRef]),principalRef:p(t[d.principalRef]),reason:p(t[d.reason])??`unknown`}}function y(e){let t=f(e);if(t===null||!Array.isArray(t[c.seats]))return null;let n=f(t[c.machine]),r=n!==null&&Array.isArray(n[s.servers])?n[s.servers].map(g).filter(e=>e!==null):[];return{sampledAt:p(t[c.sampledAt]),seats:t[c.seats].map(_).filter(e=>e!==null),machine:{servers:r,configured:n===null?r.length>0:h(n[s.configured]),note:(n===null?null:p(n[s.note]))??``},scope:v(t[c.scope]),notes:m(t[c.notes])}}function b(e){let t=f(e);if(t===null)return null;let n=p(t[l.accountId]);return n===null?null:{accountId:n,label:p(t[l.label])??n,provider:p(t[l.provider])??`unknown`,authentication:p(t[l.authentication])??`unknown`,state:p(t[l.state])??`unknown`,planType:p(t[l.planType]),reason:p(t[l.reason])??`unknown`,version:p(t[l.version]),pinnedVersion:p(t[l.pinnedVersion]),versionState:p(t[l.versionState])??`unverified`,usageBlockedByPin:h(t[l.usageBlockedByPin]),notes:m(t[l.notes])}}function x(e){let t=f(e);if(t===null||!Array.isArray(t[u.accounts]))return null;let n=t[u.accounts].map(b).filter(e=>e!==null);return{sampledAt:p(t[u.sampledAt]),accounts:n,driftDetected:h(t[u.driftDetected])||n.some(e=>e.versionState===`drift`),notes:m(t[u.notes])}}var S={"mcp-seat-isolated-by-adapter":`Verse launches every turn on this seat with an empty MCP configuration, so it loads no MCP servers at all — whatever is configured elsewhere on this machine.`,"mcp-account-config-absent":`This account has no MCP configuration file of its own, so it starts with none.`,"mcp-account-config-not-json":`This account keeps its MCP servers in a TOML config that the provider CLI reads directly. Hub does not parse or rewrite it, so it cannot list what is in there.`,"mcp-account-config-read":`Read from this account's own private configuration.`,"mcp-account-config-unreadable":`This account's configuration could not be read, so nothing here is a reading.`,"mcp-account-profile-unresolved":`This account's private profile could not be located, so Hub cannot say what it would load.`},C={"mcp-scope-locus-unavailable":`Locus is not reachable on this machine, so writes are not attributable to a tenant.`,"mcp-scope-unpinned":`Locus is running but nothing is pinned, so there is no tenant to scope a write to.`,"mcp-scope-seal-unverified":`Locus reports a pin whose seal did not verify. Fix the pin before writing anything.`,"mcp-scope-expired":`The Locus pin has expired.`,"mcp-scope-pinned":`Writes are attributed to the pinned Locus tenant shown here.`};function w(e,t,n){return e[t]??n}var T={section:`_section_mum7s_11`,body:`_body_mum7s_19`,panel:`_panel_mum7s_29`,panelHead:`_panelHead_mum7s_35`,panelTitle:`_panelTitle_mum7s_43`,panelNote:`_panelNote_mum7s_51`,muted:`_muted_mum7s_52`,alert:`_alert_mum7s_61`,alertTitle:`_alertTitle_mum7s_72`,seatList:`_seatList_mum7s_78`,rowList:`_rowList_mum7s_79`,seatRow:`_seatRow_mum7s_85`,healthRow:`_healthRow_mum7s_86`,seatHead:`_seatHead_mum7s_94`,seatLabel:`_seatLabel_mum7s_101`,engineTag:`_engineTag_mum7s_106`,code:`_code_mum7s_107`,count:`_count_mum7s_118`,disclosure:`_disclosure_mum7s_124`,commandLine:`_commandLine_mum7s_134`,envList:`_envList_mum7s_143`,envItem:`_envItem_mum7s_152`,warnList:`_warnList_mum7s_161`,digest:`_digest_mum7s_171`,actions:`_actions_mum7s_178`},E=e();function D({server:e,heading:t}){let n=e.env===null?[]:Object.keys(e.env);return(0,E.jsxs)(`div`,{className:T.disclosure,children:[t===void 0?null:(0,E.jsx)(`p`,{className:T.alertTitle,children:t}),(0,E.jsxs)(`p`,{className:T.commandLine,children:[e.command,e.args.length>0?` ${e.args.join(` `)}`:``]}),n.length===0?(0,E.jsx)(`p`,{className:T.muted,children:`No environment variables are passed to this server.`}):(0,E.jsxs)(E.Fragment,{children:[(0,E.jsxs)(`p`,{className:T.muted,children:[n.length,` environment variable`,n.length===1?``:`s`,` passed to this server. Values are never displayed.`]}),(0,E.jsx)(`ul`,{className:T.envList,children:n.map(e=>(0,E.jsxs)(`li`,{className:T.envItem,children:[e,`=<set>`]},e))})]}),(0,E.jsxs)(`p`,{className:T.code,children:[`configured in `,e.sourceRef]})]})}var O=`verse-mcp-snapshot`,k=`verse-mcp-cli-health`;function A(e,t){return t instanceof r?t.status===404?`This server does not expose ${e}, so this panel has no source.`:`${e} answered HTTP ${t.status}.`:`${e} could not be reached.`}function j(e){return async n=>{try{return{raw:await t(e,n),available:!0,reason:null}}catch(t){if(t instanceof r&&t.status===401||t instanceof DOMException&&t.name===`AbortError`)throw t;return{raw:null,available:!1,reason:A(e,t)}}}}var M={key:O,fetch:j(`/api/verse/mcp`)},N={key:k,fetch:j(`/api/verse/mcp/cli-health`)};function P(e){switch(e){case`drift`:return`warning`;case`matches-pin`:return`success`;case`reported`:return`info`;default:return`unknown`}}function F(e){return e.versionState===`drift`?`version drift`:e.version===null?`version not read`:e.version}function I({seat:e}){let t=w(S,e.reason,`Hub cannot say what this seat would load.`);return(0,E.jsxs)(`div`,{className:T.seatRow,children:[(0,E.jsxs)(`div`,{className:T.seatHead,children:[(0,E.jsx)(`span`,{className:T.seatLabel,children:e.label}),(0,E.jsx)(`span`,{className:T.engineTag,children:e.engine}),(0,E.jsx)(n,{status:e.servers.length>0?`ready`:`neutral`,tone:e.servers.length>0?`info`:`neutral`,children:e.servers.length===0?`loads no MCP servers`:`loads ${e.servers.length} server${e.servers.length===1?``:`s`}`})]}),(0,E.jsx)(`p`,{className:T.panelNote,children:t}),(0,E.jsx)(`p`,{className:T.code,children:e.reason}),e.servers.map(e=>(0,E.jsx)(D,{server:e,heading:e.name},e.name))]})}function L(){let e=i(M),t=i(N),r=e.data?.available===!0?y(e.data.raw):null,a=t.data?.available===!0?x(t.data.raw):null,o=e.data?.available===!1?e.data.reason:null;return(0,E.jsx)(`section`,{className:T.section,"aria-labelledby":`verse-mcp-heading`,children:(0,E.jsxs)(`div`,{className:T.body,children:[(0,E.jsxs)(`div`,{className:T.panelHead,children:[(0,E.jsx)(`h2`,{id:`verse-mcp-heading`,className:T.panelTitle,children:`MCP and CLI`}),(0,E.jsx)(`p`,{className:T.panelNote,children:`An MCP server is arbitrary code that runs with the agent’s privileges. This page shows what each seat would actually load — not what is configured somewhere on this machine.`})]}),o===null?null:(0,E.jsx)(`p`,{className:T.muted,children:o}),r!==null&&r.notes.length>0?(0,E.jsxs)(`div`,{className:T.alert,role:`note`,children:[(0,E.jsx)(`span`,{className:T.alertTitle,children:`Configured, but unused`}),r.notes.map(e=>(0,E.jsx)(`p`,{className:T.panelNote,children:e},e)),(0,E.jsx)(`p`,{className:T.panelNote,children:r.machine.note})]}):null,a!==null&&a.driftDetected?(0,E.jsxs)(`div`,{className:T.alert,role:`alert`,children:[(0,E.jsx)(`span`,{className:T.alertTitle,children:`Version drift`}),a.notes.map(e=>(0,E.jsx)(`p`,{className:T.panelNote,children:e},e))]}):null,(0,E.jsxs)(`section`,{className:T.panel,"aria-labelledby":`verse-mcp-seats`,children:[(0,E.jsxs)(`div`,{className:T.panelHead,children:[(0,E.jsx)(`h3`,{id:`verse-mcp-seats`,className:T.panelTitle,children:`Per seat`}),(0,E.jsx)(`p`,{className:T.panelNote,children:`A server configured for one account does not apply to another. Each seat below is answered from its own configuration.`})]}),(0,E.jsxs)(`div`,{className:T.alert,role:`note`,"aria-labelledby":`verse-mcp-enrollment-caveat`,children:[(0,E.jsx)(`span`,{id:`verse-mcp-enrollment-caveat`,className:T.alertTitle,children:`MCP writes can be refused on a folder that is not enrolled`}),(0,E.jsx)(`p`,{className:T.panelNote,children:`Claude and local seats are unaffected. Verse launches every turn on them with an empty MCP configuration under a strict flag, so they load no MCP servers at all and cannot route an edit through one.`}),(0,E.jsxs)(`p`,{className:T.code,children:[`--strict-mcp-config --mcp-config `,`{"mcpServers":{}}`]}),(0,E.jsx)(`p`,{className:T.panelNote,children:`Codex and Grok seats are affected. Their adapters add no MCP flag, so each inherits whatever its own native profile config declares. If a chat on one of those seats routes an edit through an ashlr__ MCP write tool and the chat’s folder is not enrolled, Ashlr’s sandbox gate refuses the write — the turn fails on policy, not on the model, and the refusal reads like a tool error. Enrol the folder first, or keep that chat on a Claude or local seat.`})]}),r===null?(0,E.jsx)(`p`,{className:T.muted,children:e.status===`loading`?`Reading each seat’s configuration…`:`No per-seat reading is available.`}):(0,E.jsx)(`div`,{className:T.seatList,children:r.seats.map(e=>(0,E.jsx)(I,{seat:e},e.seatId))})]}),(0,E.jsxs)(`section`,{className:T.panel,"aria-labelledby":`verse-mcp-cli`,children:[(0,E.jsxs)(`div`,{className:T.panelHead,children:[(0,E.jsx)(`h3`,{id:`verse-mcp-cli`,className:T.panelTitle,children:`Per account`}),(0,E.jsx)(`p`,{className:T.panelNote,children:`Authentication, plan and CLI version. A version is only shown when something actually read one.`})]}),a===null?(0,E.jsx)(`p`,{className:T.muted,children:t.data?.available===!1?t.data.reason:`No per-account reading is available.`}):(0,E.jsx)(`div`,{className:T.rowList,children:a.accounts.map(e=>(0,E.jsxs)(`div`,{className:T.healthRow,children:[(0,E.jsxs)(`div`,{className:T.seatHead,children:[(0,E.jsx)(`span`,{className:T.seatLabel,children:e.label}),(0,E.jsx)(`span`,{className:T.engineTag,children:e.provider}),(0,E.jsx)(n,{status:e.authentication,tone:e.authentication===`signed-in`?`success`:e.authentication===`signed-out`?`danger`:`unknown`,children:e.authentication}),(0,E.jsx)(n,{status:e.versionState,tone:P(e.versionState),children:F(e)}),e.planType===null?null:(0,E.jsxs)(`span`,{className:T.count,children:[`plan: `,e.planType]})]}),e.notes.map(e=>(0,E.jsx)(`p`,{className:T.panelNote,children:e},e)),e.pinnedVersion===null?null:(0,E.jsxs)(`p`,{className:T.code,children:[`usage probe pinned to `,e.pinnedVersion,e.usageBlockedByPin?` · ${e.reason}`:``]})]},e.accountId))})]}),(0,E.jsxs)(`section`,{className:T.panel,"aria-labelledby":`verse-mcp-scope`,children:[(0,E.jsxs)(`div`,{className:T.panelHead,children:[(0,E.jsx)(`h3`,{id:`verse-mcp-scope`,className:T.panelTitle,children:`Scope`}),(0,E.jsx)(`p`,{className:T.panelNote,children:`Scope is owned by Locus, not by Hub. Hub holds the reference Locus publishes and nothing else.`})]}),r?.scope==null?(0,E.jsx)(`p`,{className:T.muted,children:`No scope reading is available.`}):(0,E.jsxs)(E.Fragment,{children:[(0,E.jsx)(`p`,{className:T.panelNote,children:w(C,r.scope.reason,`Locus did not report a usable scope.`)}),r.scope.tenantRef===null?null:(0,E.jsxs)(`p`,{className:T.code,children:[`tenant `,r.scope.tenantRef,r.scope.aliasRef===null?``:` · alias ${r.scope.aliasRef}`]})]})]})]})})}export{L as McpSection};
|
|
2
|
+
//# sourceMappingURL=McpSection-DVgiSYG1.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"McpSection-DVgiSYG1.js","names":[],"sources":["../../../../../../src/web-ui/routes/verse/mcp/mcp-contract.ts","../../../../../../src/web-ui/routes/verse/mcp/mcp.module.css","../../../../../../src/web-ui/routes/verse/mcp/McpServerDisclosure.tsx","../../../../../../src/web-ui/routes/verse/mcp/mcp-queries.ts","../../../../../../src/web-ui/routes/verse/sections/McpSection.tsx"],"sourcesContent":["/**\n * routes/verse/mcp/mcp-contract.ts — the wire seam for MCP management.\n *\n * Same posture as routes/verse/usage/usage-contract.ts, for the same two\n * reasons:\n *\n * 1. EVERY import from src/core here MUST be type-only. `core/verse/\n * mcp-seat-view.ts` pulls `node:fs` and `node:os`; a VALUE import of even\n * one constant drags that graph into the browser bundle and `vite build`\n * fails. Type-only imports are erased, so the seam guards below cost\n * nothing at runtime.\n * 2. Nothing is CAST. Every read goes through a structural narrower, so a\n * field rename in src/core degrades this panel to an honest \"unknown\"\n * instead of throwing at render time — and the `WIRE_*_KEYS` blocks make\n * that rename fail `npm run typecheck:web` here, at the seam, first.\n *\n * SECURITY: the server already redacts every env value to '<set>' via\n * `redactEnv` and publishes config files as home-relative refs. This module\n * is the client-side backstop on top of that: {@link narrowServer} FORCES\n * every env value to '<set>' whatever arrived, so a server that ever\n * regressed could not print a secret through this panel.\n *\n * Pure: no React, no I/O.\n */\nimport type {\n VerseMcpMachineRegistry,\n VerseMcpSeatView,\n VerseMcpServerView,\n VerseMcpSnapshot,\n} from '../../../../core/verse/mcp-seat-view.js';\nimport type { VerseMcpScope } from '../../../../core/verse/mcp-scope.js';\nimport type {\n VerseAccountCliHealth,\n VerseCliHealthSnapshot,\n} from '../../../../core/verse/mcp-cli-health.js';\n\n// ---------------------------------------------------------------------------\n// Seam contract — compile-time only, zero runtime cost.\n// ---------------------------------------------------------------------------\n\nconst WIRE_SERVER_KEYS = {\n name: 'name',\n command: 'command',\n args: 'args',\n env: 'env',\n sourceRef: 'sourceRef',\n} as const satisfies Record<string, keyof VerseMcpServerView>;\n\nconst WIRE_SEAT_KEYS = {\n seatId: 'seatId',\n label: 'label',\n engine: 'engine',\n accountId: 'accountId',\n servers: 'servers',\n reason: 'reason',\n configFile: 'configFile',\n configFormat: 'configFormat',\n notes: 'notes',\n} as const satisfies Record<string, keyof VerseMcpSeatView>;\n\nconst WIRE_MACHINE_KEYS = {\n servers: 'servers',\n configured: 'configured',\n note: 'note',\n} as const satisfies Record<string, keyof VerseMcpMachineRegistry>;\n\nconst WIRE_SNAPSHOT_KEYS = {\n sampledAt: 'sampledAt',\n seats: 'seats',\n machine: 'machine',\n scope: 'scope',\n notes: 'notes',\n} as const satisfies Record<string, keyof VerseMcpSnapshot>;\n\nconst WIRE_HEALTH_KEYS = {\n accountId: 'accountId',\n label: 'label',\n provider: 'provider',\n authentication: 'authentication',\n state: 'state',\n planType: 'planType',\n reason: 'reason',\n version: 'version',\n pinnedVersion: 'pinnedVersion',\n versionState: 'versionState',\n usageBlockedByPin: 'usageBlockedByPin',\n notes: 'notes',\n} as const satisfies Record<string, keyof VerseAccountCliHealth>;\n\nconst WIRE_HEALTH_SNAPSHOT_KEYS = {\n sampledAt: 'sampledAt',\n accounts: 'accounts',\n driftDetected: 'driftDetected',\n notes: 'notes',\n} as const satisfies Record<string, keyof VerseCliHealthSnapshot>;\n\nconst WIRE_SCOPE_KEYS = {\n available: 'available',\n pinned: 'pinned',\n aliasRef: 'aliasRef',\n tenantRef: 'tenantRef',\n principalRef: 'principalRef',\n reason: 'reason',\n} as const satisfies Record<string, keyof VerseMcpScope>;\n\n// ---------------------------------------------------------------------------\n// Display shapes\n// ---------------------------------------------------------------------------\n\nexport interface McpServer {\n name: string;\n command: string;\n args: string[];\n /** Keys only. Every value is '<set>' — forced here, not merely trusted. */\n env: Record<string, string> | null;\n sourceRef: string;\n}\n\nexport interface McpSeat {\n seatId: string;\n label: string;\n engine: string;\n accountId: string;\n servers: McpServer[];\n reason: string;\n configFile: string | null;\n configFormat: string | null;\n notes: string[];\n}\n\nexport interface McpScope {\n available: boolean;\n pinned: boolean;\n aliasRef: string | null;\n tenantRef: string | null;\n principalRef: string | null;\n reason: string;\n}\n\nexport interface McpSnapshot {\n sampledAt: string | null;\n seats: McpSeat[];\n machine: { servers: McpServer[]; configured: boolean; note: string };\n scope: McpScope | null;\n notes: string[];\n}\n\nexport interface CliHealthRow {\n accountId: string;\n label: string;\n provider: string;\n authentication: string;\n state: string;\n planType: string | null;\n reason: string;\n version: string | null;\n pinnedVersion: string | null;\n versionState: string;\n usageBlockedByPin: boolean;\n notes: string[];\n}\n\nexport interface CliHealthSnapshot {\n sampledAt: string | null;\n accounts: CliHealthRow[];\n driftDetected: boolean;\n notes: string[];\n}\n\n// ---------------------------------------------------------------------------\n// Narrowers\n// ---------------------------------------------------------------------------\n\nfunction record(value: unknown): Record<string, unknown> | null {\n return value !== null && typeof value === 'object' && !Array.isArray(value)\n ? (value as Record<string, unknown>)\n : null;\n}\n\nfunction text(value: unknown): string | null {\n return typeof value === 'string' ? value : null;\n}\n\nfunction strings(value: unknown): string[] {\n return Array.isArray(value) ? value.filter((item): item is string => typeof item === 'string') : [];\n}\n\nfunction flag(value: unknown): boolean {\n return value === true;\n}\n\n/**\n * Narrow one server, forcing every env value to '<set>'.\n *\n * The redaction is re-applied rather than trusted. The server already did it;\n * doing it again here means no future server bug can turn this panel into a\n * secret printer, and it costs one map over at most a handful of keys.\n */\nexport function narrowServer(raw: unknown): McpServer | null {\n const row = record(raw);\n if (row === null) return null;\n const name = text(row[WIRE_SERVER_KEYS.name]);\n const command = text(row[WIRE_SERVER_KEYS.command]);\n if (name === null || command === null) return null;\n\n let env: Record<string, string> | null = null;\n const rawEnv = record(row[WIRE_SERVER_KEYS.env]);\n if (rawEnv !== null) {\n const keys = Object.keys(rawEnv);\n if (keys.length > 0) {\n env = {};\n for (const key of keys) env[key] = '<set>';\n }\n }\n\n return {\n name,\n command,\n args: strings(row[WIRE_SERVER_KEYS.args]),\n env,\n sourceRef: text(row[WIRE_SERVER_KEYS.sourceRef]) ?? '(unknown source)',\n };\n}\n\nfunction narrowSeat(raw: unknown): McpSeat | null {\n const row = record(raw);\n if (row === null) return null;\n const seatId = text(row[WIRE_SEAT_KEYS.seatId]);\n if (seatId === null) return null;\n const servers = Array.isArray(row[WIRE_SEAT_KEYS.servers])\n ? (row[WIRE_SEAT_KEYS.servers] as unknown[]).map(narrowServer).filter((s): s is McpServer => s !== null)\n : [];\n return {\n seatId,\n label: text(row[WIRE_SEAT_KEYS.label]) ?? seatId,\n engine: text(row[WIRE_SEAT_KEYS.engine]) ?? 'unknown',\n accountId: text(row[WIRE_SEAT_KEYS.accountId]) ?? seatId,\n servers,\n reason: text(row[WIRE_SEAT_KEYS.reason]) ?? 'unknown',\n configFile: text(row[WIRE_SEAT_KEYS.configFile]),\n configFormat: text(row[WIRE_SEAT_KEYS.configFormat]),\n notes: strings(row[WIRE_SEAT_KEYS.notes]),\n };\n}\n\nfunction narrowScope(raw: unknown): McpScope | null {\n const row = record(raw);\n if (row === null) return null;\n return {\n available: flag(row[WIRE_SCOPE_KEYS.available]),\n pinned: flag(row[WIRE_SCOPE_KEYS.pinned]),\n aliasRef: text(row[WIRE_SCOPE_KEYS.aliasRef]),\n tenantRef: text(row[WIRE_SCOPE_KEYS.tenantRef]),\n principalRef: text(row[WIRE_SCOPE_KEYS.principalRef]),\n reason: text(row[WIRE_SCOPE_KEYS.reason]) ?? 'unknown',\n };\n}\n\n/** Narrow GET /api/verse/mcp. Null when the body is not this shape at all. */\nexport function projectMcpSnapshot(raw: unknown): McpSnapshot | null {\n const row = record(raw);\n if (row === null) return null;\n if (!Array.isArray(row[WIRE_SNAPSHOT_KEYS.seats])) return null;\n\n const machine = record(row[WIRE_SNAPSHOT_KEYS.machine]);\n const machineServers = machine !== null && Array.isArray(machine[WIRE_MACHINE_KEYS.servers])\n ? (machine[WIRE_MACHINE_KEYS.servers] as unknown[]).map(narrowServer).filter((s): s is McpServer => s !== null)\n : [];\n\n return {\n sampledAt: text(row[WIRE_SNAPSHOT_KEYS.sampledAt]),\n seats: (row[WIRE_SNAPSHOT_KEYS.seats] as unknown[])\n .map(narrowSeat)\n .filter((s): s is McpSeat => s !== null),\n machine: {\n servers: machineServers,\n configured: machine !== null ? flag(machine[WIRE_MACHINE_KEYS.configured]) : machineServers.length > 0,\n note: (machine !== null ? text(machine[WIRE_MACHINE_KEYS.note]) : null) ?? '',\n },\n scope: narrowScope(row[WIRE_SNAPSHOT_KEYS.scope]),\n notes: strings(row[WIRE_SNAPSHOT_KEYS.notes]),\n };\n}\n\nfunction narrowHealthRow(raw: unknown): CliHealthRow | null {\n const row = record(raw);\n if (row === null) return null;\n const accountId = text(row[WIRE_HEALTH_KEYS.accountId]);\n if (accountId === null) return null;\n return {\n accountId,\n label: text(row[WIRE_HEALTH_KEYS.label]) ?? accountId,\n provider: text(row[WIRE_HEALTH_KEYS.provider]) ?? 'unknown',\n authentication: text(row[WIRE_HEALTH_KEYS.authentication]) ?? 'unknown',\n state: text(row[WIRE_HEALTH_KEYS.state]) ?? 'unknown',\n planType: text(row[WIRE_HEALTH_KEYS.planType]),\n reason: text(row[WIRE_HEALTH_KEYS.reason]) ?? 'unknown',\n // A missing version stays NULL. It is never coerced to a string, because\n // \"no reading\" and \"some version\" are different facts.\n version: text(row[WIRE_HEALTH_KEYS.version]),\n pinnedVersion: text(row[WIRE_HEALTH_KEYS.pinnedVersion]),\n versionState: text(row[WIRE_HEALTH_KEYS.versionState]) ?? 'unverified',\n usageBlockedByPin: flag(row[WIRE_HEALTH_KEYS.usageBlockedByPin]),\n notes: strings(row[WIRE_HEALTH_KEYS.notes]),\n };\n}\n\n/** Narrow GET /api/verse/mcp/cli-health. */\nexport function projectCliHealth(raw: unknown): CliHealthSnapshot | null {\n const row = record(raw);\n if (row === null) return null;\n if (!Array.isArray(row[WIRE_HEALTH_SNAPSHOT_KEYS.accounts])) return null;\n const accounts = (row[WIRE_HEALTH_SNAPSHOT_KEYS.accounts] as unknown[])\n .map(narrowHealthRow)\n .filter((a): a is CliHealthRow => a !== null);\n return {\n sampledAt: text(row[WIRE_HEALTH_SNAPSHOT_KEYS.sampledAt]),\n accounts,\n // Derived locally as well as read, so a server that forgot the flag still\n // renders the banner rather than silently hiding a drift.\n driftDetected:\n flag(row[WIRE_HEALTH_SNAPSHOT_KEYS.driftDetected]) ||\n accounts.some((account) => account.versionState === 'drift'),\n notes: strings(row[WIRE_HEALTH_SNAPSHOT_KEYS.notes]),\n };\n}\n\n// ---------------------------------------------------------------------------\n// Copy for the machine-readable reasons this surface renders\n// ---------------------------------------------------------------------------\n\n/**\n * Plain language for each seat reason. The CODE is still shown next to the\n * sentence — it is the thing to search for — but it is never the sentence.\n * Same split `usage/accounts-model.ts` uses for probe reasons.\n */\nexport const SEAT_REASON_COPY: Record<string, string> = {\n 'mcp-seat-isolated-by-adapter':\n 'Verse launches every turn on this seat with an empty MCP configuration, so it loads no MCP '\n + 'servers at all — whatever is configured elsewhere on this machine.',\n 'mcp-account-config-absent':\n 'This account has no MCP configuration file of its own, so it starts with none.',\n 'mcp-account-config-not-json':\n 'This account keeps its MCP servers in a TOML config that the provider CLI reads directly. '\n + 'Hub does not parse or rewrite it, so it cannot list what is in there.',\n 'mcp-account-config-read':\n 'Read from this account\\'s own private configuration.',\n 'mcp-account-config-unreadable':\n 'This account\\'s configuration could not be read, so nothing here is a reading.',\n 'mcp-account-profile-unresolved':\n 'This account\\'s private profile could not be located, so Hub cannot say what it would load.',\n};\n\nexport const SCOPE_REASON_COPY: Record<string, string> = {\n 'mcp-scope-locus-unavailable':\n 'Locus is not reachable on this machine, so writes are not attributable to a tenant.',\n 'mcp-scope-unpinned':\n 'Locus is running but nothing is pinned, so there is no tenant to scope a write to.',\n 'mcp-scope-seal-unverified':\n 'Locus reports a pin whose seal did not verify. Fix the pin before writing anything.',\n 'mcp-scope-expired': 'The Locus pin has expired.',\n 'mcp-scope-pinned': 'Writes are attributed to the pinned Locus tenant shown here.',\n};\n\n/** Prose for a code, or the caller's own sentence. NEVER the raw code alone. */\nexport function reasonSentence(\n copy: Record<string, string>,\n reason: string,\n fallback: string,\n): string {\n return copy[reason] ?? fallback;\n}\n","/**\n * routes/verse/mcp/mcp.module.css — MCP + CLI management styling.\n *\n * Follows docs/VERSE-DESIGN-V2.md, same as usage.module.css: monochrome\n * ground, 1px hairlines and whitespace for separation (no shadows, no nested\n * rounded boxes), display face for headings, mono for anything the operator\n * has to read character-by-character — which here is the whole point, because\n * the command line is the thing being disclosed.\n */\n\n.section {\n display: flex;\n flex-direction: column;\n min-height: 0;\n height: 100%;\n background: var(--bg-canvas);\n}\n\n.body {\n flex: 1;\n min-height: 0;\n overflow-y: auto;\n padding: var(--space-5);\n display: flex;\n flex-direction: column;\n gap: var(--space-6);\n}\n\n.panel {\n display: flex;\n flex-direction: column;\n gap: var(--space-3);\n}\n\n.panelHead {\n display: flex;\n flex-direction: column;\n gap: var(--space-1);\n padding-bottom: var(--space-2);\n border-bottom: 1px solid var(--border-subtle);\n}\n\n.panelTitle {\n margin: 0;\n font-family: var(--font-display);\n font-size: var(--text-md-size);\n font-weight: var(--font-weight-semibold);\n color: var(--text-primary);\n}\n\n.panelNote,\n.muted {\n margin: 0;\n font-size: var(--text-sm-size);\n color: var(--text-secondary);\n line-height: 1.5;\n}\n\n/* A fact that changes what the operator should do. Never colour alone: it\n carries a word (\"Version drift\") as well as the rule. */\n.alert {\n display: flex;\n flex-direction: column;\n gap: var(--space-1);\n padding: var(--space-3) var(--space-4);\n border: 1px solid var(--border-warning, var(--border-strong));\n border-left-width: 3px;\n border-radius: var(--radius-sm);\n background: var(--bg-subtle);\n}\n\n.alertTitle {\n font-weight: var(--font-weight-semibold);\n color: var(--text-primary);\n font-size: var(--text-sm-size);\n}\n\n.seatList,\n.rowList {\n display: flex;\n flex-direction: column;\n gap: 0;\n}\n\n.seatRow,\n.healthRow {\n display: flex;\n flex-direction: column;\n gap: var(--space-2);\n padding: var(--space-3) 0;\n border-bottom: 1px solid var(--border-subtle);\n}\n\n.seatHead {\n display: flex;\n align-items: baseline;\n gap: var(--space-2);\n flex-wrap: wrap;\n}\n\n.seatLabel {\n font-weight: var(--font-weight-semibold);\n color: var(--text-primary);\n}\n\n.engineTag,\n.code {\n font-family: var(--font-mono);\n font-size: var(--text-xs-size);\n color: var(--text-tertiary, var(--text-secondary));\n}\n\n/* The machine-readable code is shown BESIDE the sentence, never as it. */\n.code {\n letter-spacing: 0;\n}\n\n.count {\n font-size: var(--text-sm-size);\n color: var(--text-secondary);\n}\n\n/* The disclosure: everything the operator must read before confirming. */\n.disclosure {\n display: flex;\n flex-direction: column;\n gap: var(--space-2);\n padding: var(--space-3) var(--space-4);\n border: 1px solid var(--border-subtle);\n border-radius: var(--radius-sm);\n background: var(--bg-subtle);\n}\n\n.commandLine {\n font-family: var(--font-mono);\n font-size: var(--text-sm-size);\n color: var(--text-primary);\n word-break: break-all;\n white-space: pre-wrap;\n margin: 0;\n}\n\n.envList {\n display: flex;\n flex-wrap: wrap;\n gap: var(--space-2);\n margin: 0;\n padding: 0;\n list-style: none;\n}\n\n.envItem {\n font-family: var(--font-mono);\n font-size: var(--text-xs-size);\n padding: 2px var(--space-2);\n border: 1px solid var(--border-subtle);\n border-radius: var(--radius-sm);\n color: var(--text-secondary);\n}\n\n.warnList {\n margin: 0;\n padding-left: var(--space-4);\n display: flex;\n flex-direction: column;\n gap: var(--space-1);\n font-size: var(--text-sm-size);\n color: var(--text-primary);\n}\n\n.digest {\n font-family: var(--font-mono);\n font-size: var(--text-xs-size);\n color: var(--text-tertiary, var(--text-secondary));\n word-break: break-all;\n}\n\n.actions {\n display: flex;\n gap: var(--space-2);\n align-items: center;\n flex-wrap: wrap;\n}\n","/**\n * routes/verse/mcp/McpServerDisclosure.tsx — what an MCP server IS, rendered\n * so it has to be read.\n *\n * Used in two places and identical in both: listing a server a seat already\n * loads, and previewing one that is about to be written. That sameness is the\n * point — the operator reads the same card either way, so \"what will run\" is\n * never presented differently from \"what runs\".\n *\n * Three rules this component enforces at the render layer:\n *\n * 1. The COMMAND and ARGS are shown in full, in mono, wrapped rather than\n * truncated. A command with an ellipsis in the middle is exactly the\n * one worth reading.\n * 2. Environment variables are listed BY KEY, with the value shown as the\n * literal `<set>` that `redactEnv` produced. The count is stated, so a\n * server handing over six credentials cannot look like one handing over\n * none.\n * 3. The source is a home-relative ref, never an absolute path.\n */\nimport type { ReactNode } from 'react';\nimport type { McpServer } from './mcp-contract.js';\nimport styles from './mcp.module.css';\n\nexport function McpServerDisclosure({\n server,\n heading,\n}: {\n server: McpServer;\n heading?: string;\n}): ReactNode {\n const envKeys = server.env === null ? [] : Object.keys(server.env);\n\n return (\n <div className={styles.disclosure}>\n {heading === undefined ? null : <p className={styles.alertTitle}>{heading}</p>}\n\n <p className={styles.commandLine}>\n {server.command}\n {server.args.length > 0 ? ` ${server.args.join(' ')}` : ''}\n </p>\n\n {envKeys.length === 0 ? (\n <p className={styles.muted}>No environment variables are passed to this server.</p>\n ) : (\n <>\n <p className={styles.muted}>\n {envKeys.length} environment variable{envKeys.length === 1 ? '' : 's'} passed to this\n server. Values are never displayed.\n </p>\n <ul className={styles.envList}>\n {envKeys.map((key) => (\n <li key={key} className={styles.envItem}>\n {key}=<set>\n </li>\n ))}\n </ul>\n </>\n )}\n\n <p className={styles.code}>configured in {server.sourceRef}</p>\n </div>\n );\n}\n","/**\n * routes/verse/mcp/mcp-queries.ts — the reads and writes behind the MCP panel.\n *\n * Reads use the non-throwing `optionalGet` shape the Usage section\n * established: a server without these routes answers 404, and a 404 means\n * \"this panel is degraded\", not \"Verse is broken\". 401 still propagates,\n * because an expired read session is an unauthorized state for the whole\n * surface, not a degraded one.\n *\n * Writes pull the held mutation token from auth-store and touch the hold on\n * success, exactly like `autonomy/control-queries.ts`. There is one addition\n * this surface needs that the others do not: `applyMcpServer` CANNOT be\n * called without a digest that `proposeMcpServer` returned, because the\n * server refuses without it. That is deliberate and it is why there is no\n * single `installMcpServer()` helper here — a one-call install is exactly the\n * shape docs/VERSE-WORKSPACES.md §3 rules out, and it should be impossible to\n * write by accident at this layer too.\n */\nimport { getMutationToken, touchMutationHold } from '../../../data/auth-store.js';\nimport { ApiError, apiGet, apiPost } from '../../../data/client.js';\nimport { invalidate } from '../../../data/cache.js';\nimport type { QueryDef } from '../../../data/queries.js';\n\nexport const MCP_SNAPSHOT_KEY = 'verse-mcp-snapshot';\nexport const MCP_CLI_HEALTH_KEY = 'verse-mcp-cli-health';\n\n/** A read that is allowed to be absent. `raw` is null when it was. */\nexport interface OptionalRead {\n raw: unknown;\n available: boolean;\n reason: string | null;\n}\n\nfunction describeFailure(path: string, err: unknown): string {\n if (err instanceof ApiError) {\n if (err.status === 404) {\n return `This server does not expose ${path}, so this panel has no source.`;\n }\n return `${path} answered HTTP ${err.status}.`;\n }\n return `${path} could not be reached.`;\n}\n\nfunction optionalGet(path: string): (signal?: AbortSignal) => Promise<OptionalRead> {\n return async (signal) => {\n try {\n const raw = await apiGet<unknown>(path, signal);\n return { raw, available: true, reason: null };\n } catch (err) {\n if (err instanceof ApiError && err.status === 401) throw err;\n if (err instanceof DOMException && err.name === 'AbortError') throw err;\n return { raw: null, available: false, reason: describeFailure(path, err) };\n }\n };\n}\n\n/** GET /api/verse/mcp — which servers each seat would load. */\nexport const mcpSnapshotQuery: QueryDef<OptionalRead> = {\n key: MCP_SNAPSHOT_KEY,\n fetch: optionalGet('/api/verse/mcp'),\n};\n\n/** GET /api/verse/mcp/cli-health — auth, plan and version drift per account. */\nexport const mcpCliHealthQuery: QueryDef<OptionalRead> = {\n key: MCP_CLI_HEALTH_KEY,\n fetch: optionalGet('/api/verse/mcp/cli-health'),\n};\n\n// ---------------------------------------------------------------------------\n// Writes\n// ---------------------------------------------------------------------------\n\nexport class McpLockedError extends Error {\n constructor() {\n super('Unlock actions with the mutation token before changing MCP configuration.');\n this.name = 'McpLockedError';\n }\n}\n\nasync function post<T>(path: string, body: unknown): Promise<T> {\n const token = getMutationToken();\n if (!token) throw new McpLockedError();\n const result = await apiPost<T>(path, body, token);\n touchMutationHold();\n return result;\n}\n\n/** The spec an operator typed. Env VALUES are real here and only here. */\nexport interface McpServerInput {\n name: string;\n command: string;\n args?: string[];\n env?: Record<string, string>;\n}\n\n/**\n * POST /api/verse/mcp/proposal — validate and disclose. Writes nothing.\n *\n * The response carries the digest `applyMcpServer` demands back. A refusal is\n * a 409 with a full body, which `apiPost` surfaces as an ApiError whose\n * detail carries the `note`.\n */\nexport function proposeMcpServer(target: string, server: McpServerInput): Promise<unknown> {\n return post<unknown>('/api/verse/mcp/proposal', { target, server });\n}\n\n/**\n * POST /api/verse/mcp/apply — perform the write the digest confirms.\n *\n * `digest` is not optional and has no default. It must come from a proposal\n * the operator actually saw.\n */\nexport async function applyMcpServer(\n target: string,\n server: McpServerInput,\n digest: string,\n): Promise<unknown> {\n const result = await post<unknown>('/api/verse/mcp/apply', {\n target,\n server,\n digest,\n confirm: true,\n });\n invalidate(MCP_SNAPSHOT_KEY);\n return result;\n}\n\n/**\n * POST /api/verse/mcp/cli-probe — read the installed CLI versions.\n *\n * A POST because it launches the provider CLIs. Nothing about it is a read.\n */\nexport async function probeCliVersions(): Promise<unknown> {\n const result = await post<unknown>('/api/verse/mcp/cli-probe', {});\n invalidate(MCP_CLI_HEALTH_KEY);\n return result;\n}\n","/**\n * routes/verse/sections/McpSection.tsx — the MCP and per-account CLI panel.\n *\n * WHY THIS FILE IS HERE AND NOT IN `../mcp/`. The shell mounts rail sections\n * through `import.meta.glob('./sections/*Section.tsx')` (VerseApp.tsx). While\n * this component lived at `mcp/McpSection.tsx` the glob could not see it, so\n * a finished, tested, fully-served feature was unreachable from the app —\n * every other rail section already follows the same split (`sections/\n * UsageSection.tsx` importing `../usage/`, `sections/AutonomySection.tsx`\n * importing `../autonomy/`), and this one was the only surface that did not.\n * Moving it was preferred over teaching the shell a second mounting\n * mechanism: one glob that every section satisfies cannot silently skip a\n * section again. Its model, queries, styles and the shared disclosure card\n * all stay in `../mcp/`.\n *\n * Read-only by construction. There is no install form in this component and\n * no call to `applyMcpServer`: this section answers \"what would each seat\n * load, and is any CLI drifting\", which is the whole of what\n * docs/VERSE-WORKSPACES.md §3 asks for before mutation.\n *\n * The write path exists on the server and in `mcp-queries.ts`\n * (`proposeMcpServer` → `applyMcpServer`, two calls, digest-bound). It is\n * deliberately not wired to a button here. A one-click \"Add server\" control\n * is the exact affordance §3 rules out, and the disclosure card it would need\n * is already built and shared — `McpServerDisclosure` — so the remaining work\n * is a form plus a confirm step that shows that card, not a re-think.\n *\n * Three things this panel refuses to soften:\n *\n * - When every seat loads nothing and the machine has servers configured,\n * it SAYS SO, at the top, as the first thing on the page. That is the\n * single most surprising fact about this machine's MCP setup and burying\n * it under a list of servers would be the same as hiding it.\n * - A version drift is an alert with the pinned version quoted, not a grey\n * \"unknown\". The fix is a one-line constant bump and the operator cannot\n * make it without being told.\n * - The seats do not all behave the same way, and the difference bites. A\n * Claude or local seat loads NO MCP servers (the adapter pins an empty\n * `--mcp-config` under `--strict-mcp-config`, src/core/verse/adapters/\n * claude.ts). A Codex or Grok seat inherits its own native profile\n * config, so a turn there CAN reach an `ashlr__*` MCP write tool — which\n * `assertMayMutate` (src/core/sandbox/policy.ts) refuses when the chat's\n * folder is not enrolled. That refusal surfaces as a tool error mid-turn\n * and nothing else in Verse mentions it, so this panel states it.\n */\nimport type { ReactNode } from 'react';\nimport { useQuery } from '../../../data/hooks.js';\nimport { StatusBadge, type Tone } from '../../../components/primitives/StatusBadge.js';\nimport {\n SCOPE_REASON_COPY,\n SEAT_REASON_COPY,\n projectCliHealth,\n projectMcpSnapshot,\n reasonSentence,\n type CliHealthRow,\n type McpSeat,\n type McpSnapshot,\n} from '../mcp/mcp-contract.js';\nimport { McpServerDisclosure } from '../mcp/McpServerDisclosure.js';\nimport { mcpCliHealthQuery, mcpSnapshotQuery } from '../mcp/mcp-queries.js';\nimport styles from '../mcp/mcp.module.css';\n\nfunction versionTone(state: string): Tone {\n switch (state) {\n case 'drift':\n return 'warning';\n case 'matches-pin':\n return 'success';\n case 'reported':\n return 'info';\n default:\n return 'unknown';\n }\n}\n\nfunction versionLabel(row: CliHealthRow): string {\n if (row.versionState === 'drift') return 'version drift';\n if (row.version !== null) return row.version;\n return 'version not read';\n}\n\n// ---------------------------------------------------------------------------\n// Seats\n// ---------------------------------------------------------------------------\n\nfunction SeatRow({ seat }: { seat: McpSeat }): ReactNode {\n const sentence = reasonSentence(\n SEAT_REASON_COPY,\n seat.reason,\n 'Hub cannot say what this seat would load.',\n );\n\n return (\n <div className={styles.seatRow}>\n <div className={styles.seatHead}>\n <span className={styles.seatLabel}>{seat.label}</span>\n <span className={styles.engineTag}>{seat.engine}</span>\n <StatusBadge\n status={seat.servers.length > 0 ? 'ready' : 'neutral'}\n tone={seat.servers.length > 0 ? 'info' : 'neutral'}\n >\n {seat.servers.length === 0\n ? 'loads no MCP servers'\n : `loads ${seat.servers.length} server${seat.servers.length === 1 ? '' : 's'}`}\n </StatusBadge>\n </div>\n\n <p className={styles.panelNote}>{sentence}</p>\n {/* The machine code sits beside the sentence — it is what you search\n for in the logs — but it is never the sentence itself. */}\n <p className={styles.code}>{seat.reason}</p>\n\n {seat.servers.map((server) => (\n <McpServerDisclosure key={server.name} server={server} heading={server.name} />\n ))}\n </div>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Section\n// ---------------------------------------------------------------------------\n\nexport function McpSection(): ReactNode {\n const snapshotRead = useQuery(mcpSnapshotQuery);\n const healthRead = useQuery(mcpCliHealthQuery);\n\n const snapshot: McpSnapshot | null =\n snapshotRead.data?.available === true ? projectMcpSnapshot(snapshotRead.data.raw) : null;\n const health =\n healthRead.data?.available === true ? projectCliHealth(healthRead.data.raw) : null;\n\n const unreachable = snapshotRead.data?.available === false ? snapshotRead.data.reason : null;\n\n return (\n <section className={styles.section} aria-labelledby=\"verse-mcp-heading\">\n <div className={styles.body}>\n <div className={styles.panelHead}>\n <h2 id=\"verse-mcp-heading\" className={styles.panelTitle}>\n MCP and CLI\n </h2>\n <p className={styles.panelNote}>\n An MCP server is arbitrary code that runs with the agent’s privileges. This page\n shows what each seat would actually load — not what is configured somewhere on this\n machine.\n </p>\n </div>\n\n {unreachable !== null ? <p className={styles.muted}>{unreachable}</p> : null}\n\n {/* The loudest fact first, when it is true. */}\n {snapshot !== null && snapshot.notes.length > 0 ? (\n <div className={styles.alert} role=\"note\">\n <span className={styles.alertTitle}>Configured, but unused</span>\n {snapshot.notes.map((note) => (\n <p key={note} className={styles.panelNote}>\n {note}\n </p>\n ))}\n <p className={styles.panelNote}>{snapshot.machine.note}</p>\n </div>\n ) : null}\n\n {/* Version drift, quoted. */}\n {health !== null && health.driftDetected ? (\n <div className={styles.alert} role=\"alert\">\n <span className={styles.alertTitle}>Version drift</span>\n {health.notes.map((note) => (\n <p key={note} className={styles.panelNote}>\n {note}\n </p>\n ))}\n </div>\n ) : null}\n\n {/* ── Per-seat view ───────────────────────────────────────────── */}\n <section className={styles.panel} aria-labelledby=\"verse-mcp-seats\">\n <div className={styles.panelHead}>\n <h3 id=\"verse-mcp-seats\" className={styles.panelTitle}>\n Per seat\n </h3>\n <p className={styles.panelNote}>\n A server configured for one account does not apply to another. Each seat below is\n answered from its own configuration.\n </p>\n </div>\n\n {/*\n A STANDING caveat, not a conditional alert — it is true on every\n machine, whatever the reads below say, and nothing else in Verse\n says it anywhere. It sits inside \"Per seat\" because that is the\n question it answers: the seats differ, and the difference has a\n consequence the operator meets as a failed turn otherwise.\n */}\n <div className={styles.alert} role=\"note\" aria-labelledby=\"verse-mcp-enrollment-caveat\">\n <span id=\"verse-mcp-enrollment-caveat\" className={styles.alertTitle}>\n MCP writes can be refused on a folder that is not enrolled\n </span>\n <p className={styles.panelNote}>\n Claude and local seats are unaffected. Verse launches every turn on them with an\n empty MCP configuration under a strict flag, so they load no MCP servers at all and\n cannot route an edit through one.\n </p>\n <p className={styles.code}>--strict-mcp-config --mcp-config {'{\"mcpServers\":{}}'}</p>\n <p className={styles.panelNote}>\n Codex and Grok seats are affected. Their adapters add no MCP flag, so each inherits\n whatever its own native profile config declares. If a chat on one of those seats\n routes an edit through an ashlr__ MCP write tool and the chat’s folder is not\n enrolled, Ashlr’s sandbox gate refuses the write — the turn fails on policy,\n not on the model, and the refusal reads like a tool error. Enrol the folder first,\n or keep that chat on a Claude or local seat.\n </p>\n </div>\n\n {snapshot === null ? (\n <p className={styles.muted}>\n {snapshotRead.status === 'loading'\n ? 'Reading each seat’s configuration…'\n : 'No per-seat reading is available.'}\n </p>\n ) : (\n <div className={styles.seatList}>\n {snapshot.seats.map((seat) => (\n <SeatRow key={seat.seatId} seat={seat} />\n ))}\n </div>\n )}\n </section>\n\n {/* ── Per-account CLI health ──────────────────────────────────── */}\n <section className={styles.panel} aria-labelledby=\"verse-mcp-cli\">\n <div className={styles.panelHead}>\n <h3 id=\"verse-mcp-cli\" className={styles.panelTitle}>\n Per account\n </h3>\n <p className={styles.panelNote}>\n Authentication, plan and CLI version. A version is only shown when something actually\n read one.\n </p>\n </div>\n\n {health === null ? (\n <p className={styles.muted}>\n {healthRead.data?.available === false\n ? healthRead.data.reason\n : 'No per-account reading is available.'}\n </p>\n ) : (\n <div className={styles.rowList}>\n {health.accounts.map((row) => (\n <div key={row.accountId} className={styles.healthRow}>\n <div className={styles.seatHead}>\n <span className={styles.seatLabel}>{row.label}</span>\n <span className={styles.engineTag}>{row.provider}</span>\n <StatusBadge status={row.authentication} tone={\n row.authentication === 'signed-in' ? 'success'\n : row.authentication === 'signed-out' ? 'danger' : 'unknown'\n }>\n {row.authentication}\n </StatusBadge>\n <StatusBadge status={row.versionState} tone={versionTone(row.versionState)}>\n {versionLabel(row)}\n </StatusBadge>\n {row.planType === null ? null : (\n <span className={styles.count}>plan: {row.planType}</span>\n )}\n </div>\n\n {row.notes.map((note) => (\n <p key={note} className={styles.panelNote}>\n {note}\n </p>\n ))}\n\n {row.pinnedVersion === null ? null : (\n <p className={styles.code}>\n usage probe pinned to {row.pinnedVersion}\n {row.usageBlockedByPin ? ` · ${row.reason}` : ''}\n </p>\n )}\n </div>\n ))}\n </div>\n )}\n </section>\n\n {/* ── Scope ───────────────────────────────────────────────────── */}\n <section className={styles.panel} aria-labelledby=\"verse-mcp-scope\">\n <div className={styles.panelHead}>\n <h3 id=\"verse-mcp-scope\" className={styles.panelTitle}>\n Scope\n </h3>\n <p className={styles.panelNote}>\n Scope is owned by Locus, not by Hub. Hub holds the reference Locus publishes and\n nothing else.\n </p>\n </div>\n {snapshot?.scope == null ? (\n <p className={styles.muted}>No scope reading is available.</p>\n ) : (\n <>\n <p className={styles.panelNote}>\n {reasonSentence(\n SCOPE_REASON_COPY,\n snapshot.scope.reason,\n 'Locus did not report a usable scope.',\n )}\n </p>\n {snapshot.scope.tenantRef === null ? null : (\n <p className={styles.code}>\n tenant {snapshot.scope.tenantRef}\n {snapshot.scope.aliasRef === null ? '' : ` · alias ${snapshot.scope.aliasRef}`}\n </p>\n )}\n </>\n )}\n </section>\n </div>\n </section>\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
._wrapper_6cgsw_4{display:contents}._bubble_6cgsw_12{z-index:calc(var(--z-toast) + 1);align-items:center;gap:var(--space-2);max-width:260px;padding:var(--space-1) var(--space-2);border:1px solid var(--border-default);border-radius:var(--radius-xs);background:var(--bg-surface-raised);color:var(--text-primary);box-shadow:var(--shadow-menu);font-family:var(--font-ui);font-size:var(--text-xs-size);line-height:var(--text-xs-line);font-weight:var(--font-weight-regular);white-space:normal;text-wrap:pretty;pointer-events:none;animation:_tooltip-in_6cgsw_1 var(--duration-fast) var(--ease-standard);display:flex;position:fixed}._label_6cgsw_44{min-width:0}._shortcut_6cgsw_49{border:1px solid var(--border-subtle);border-radius:var(--radius-xs);background:var(--bg-code);color:var(--text-secondary);font-family:var(--font-mono);font-size:var(--text-2xs-size,11px);white-space:nowrap;flex:none;padding:0 4px;line-height:1.5}@keyframes _tooltip-in_6cgsw_1{0%{opacity:0}}@media (prefers-reduced-motion:reduce){:root:not([data-motion=full]) ._bubble_6cgsw_12{animation:none}}:root[data-motion=reduce] ._bubble_6cgsw_12{animation:none}._wrapper_6cgsw_4[data-disabled-trigger=true]{display:inline-flex}._wrap_ti026_1{gap:var(--space-1);flex-direction:column;min-width:0;display:flex}._lineWrap_ti026_8{width:100%}._head_ti026_12{justify-content:space-between;align-items:baseline;gap:var(--space-2);display:flex}._label_ti026_19{font-size:var(--text-xs-size);line-height:var(--text-xs-line);color:var(--text-secondary);text-overflow:ellipsis;white-space:nowrap;overflow:hidden}._value_ti026_28{font-family:var(--font-display);font-variant-numeric:tabular-nums;font-size:var(--text-xs-size);letter-spacing:var(--tracking-display);color:var(--text-secondary);flex:none}._unknown_ti026_37{font-family:var(--font-ui);color:var(--status-unknown-fg);font-style:italic}._track_ti026_43{background:var(--bg-active);width:100%;overflow:hidden}._bar_ti026_49{border-radius:var(--radius-full);height:4px}._line_ti026_8{background:var(--border-subtle);border-radius:0;height:2px}._fill_ti026_61{height:100%;transition:width var(--duration-base) var(--ease-standard)}._accent_ti026_66{background:var(--accent-500)}._neutral_ti026_69{background:var(--status-neutral-solid)}._running_ti026_72{background:var(--status-running-solid)}._warning_ti026_75{background:var(--status-warning-solid)}._danger_ti026_78{background:var(--status-danger-solid)}._engine_ti026_81{background:var(--status-neutral-solid)}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{a as e,i as t,n,o as r}from"./index-CXgVQHek.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]})}export{m as n,v as t};
|
|
2
|
+
//# sourceMappingURL=Meter-Cmo1pLbk.js.map
|