@ashlr/hub 3.8.0 → 3.9.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.
Files changed (147) hide show
  1. package/CHANGELOG.md +201 -0
  2. package/dist/build-identity.json +2 -2
  3. package/dist/cli/resource-profile.js +95 -21
  4. package/dist/cli/resource-profile.js.map +1 -1
  5. package/dist/core/resources/native-profile.d.ts +56 -0
  6. package/dist/core/resources/native-profile.js +368 -6
  7. package/dist/core/resources/native-profile.js.map +1 -1
  8. package/dist/core/run/model-catalog.js +8 -1
  9. package/dist/core/run/model-catalog.js.map +1 -1
  10. package/dist/core/universe/builtins/preparation/manifest.json +1 -1
  11. package/dist/core/universe/builtins/preparation/preparation-bridge.mjs +9 -2
  12. package/dist/core/universe/builtins/preparation/preparation-verification-fixtures.mjs +8 -1
  13. package/dist/core/verse/adapters/claude.d.ts +49 -2
  14. package/dist/core/verse/adapters/claude.js +317 -56
  15. package/dist/core/verse/adapters/claude.js.map +1 -1
  16. package/dist/core/verse/adapters/codex.d.ts +81 -5
  17. package/dist/core/verse/adapters/codex.js +409 -47
  18. package/dist/core/verse/adapters/codex.js.map +1 -1
  19. package/dist/core/verse/adapters/grok.d.ts +13 -1
  20. package/dist/core/verse/adapters/grok.js +28 -2
  21. package/dist/core/verse/adapters/grok.js.map +1 -1
  22. package/dist/core/verse/adapters/index.d.ts +32 -0
  23. package/dist/core/verse/adapters/index.js +2 -0
  24. package/dist/core/verse/adapters/index.js.map +1 -1
  25. package/dist/core/verse/codex-rollout.d.ts +284 -0
  26. package/dist/core/verse/codex-rollout.js +786 -0
  27. package/dist/core/verse/codex-rollout.js.map +1 -0
  28. package/dist/core/verse/context-fit.d.ts +63 -0
  29. package/dist/core/verse/context-fit.js +355 -0
  30. package/dist/core/verse/context-fit.js.map +1 -0
  31. package/dist/core/verse/context-math.d.ts +215 -0
  32. package/dist/core/verse/context-math.js +365 -0
  33. package/dist/core/verse/context-math.js.map +1 -0
  34. package/dist/core/verse/local-models.d.ts +137 -1
  35. package/dist/core/verse/local-models.js +209 -5
  36. package/dist/core/verse/local-models.js.map +1 -1
  37. package/dist/core/verse/model-windows.d.ts +217 -0
  38. package/dist/core/verse/model-windows.js +616 -0
  39. package/dist/core/verse/model-windows.js.map +1 -0
  40. package/dist/core/verse/preferences.d.ts +123 -0
  41. package/dist/core/verse/preferences.js +403 -0
  42. package/dist/core/verse/preferences.js.map +1 -0
  43. package/dist/core/verse/project-memory.d.ts +112 -0
  44. package/dist/core/verse/project-memory.js +295 -0
  45. package/dist/core/verse/project-memory.js.map +1 -0
  46. package/dist/core/verse/seats.d.ts +106 -4
  47. package/dist/core/verse/seats.js +291 -89
  48. package/dist/core/verse/seats.js.map +1 -1
  49. package/dist/core/verse/session-engine.d.ts +76 -2
  50. package/dist/core/verse/session-engine.js +684 -63
  51. package/dist/core/verse/session-engine.js.map +1 -1
  52. package/dist/core/verse/session-handoff.d.ts +94 -0
  53. package/dist/core/verse/session-handoff.js +551 -0
  54. package/dist/core/verse/session-handoff.js.map +1 -0
  55. package/dist/core/verse/session-search.d.ts +63 -0
  56. package/dist/core/verse/session-search.js +213 -0
  57. package/dist/core/verse/session-search.js.map +1 -0
  58. package/dist/core/verse/session-store.d.ts +16 -2
  59. package/dist/core/verse/session-store.js +80 -6
  60. package/dist/core/verse/session-store.js.map +1 -1
  61. package/dist/core/verse/types.d.ts +333 -1
  62. package/dist/core/verse/types.js +54 -2
  63. package/dist/core/verse/types.js.map +1 -1
  64. package/dist/core/verse/verse-api.d.ts +40 -5
  65. package/dist/core/verse/verse-api.js +724 -25
  66. package/dist/core/verse/verse-api.js.map +1 -1
  67. package/dist/core/web/api.d.ts +14 -4
  68. package/dist/core/web/api.js +26 -11
  69. package/dist/core/web/api.js.map +1 -1
  70. package/dist/core/web/public/next/assets/{App-BsbJFA2j.js → App-XURmiAAp.js} +5 -5
  71. package/dist/core/web/public/next/assets/{App-BsbJFA2j.js.map → App-XURmiAAp.js.map} +1 -1
  72. package/dist/core/web/public/next/assets/{ApprovalsSection-2CCSoBTJ.js → ApprovalsSection-BriUzoZc.js} +2 -2
  73. package/dist/core/web/public/next/assets/{ApprovalsSection-2CCSoBTJ.js.map → ApprovalsSection-BriUzoZc.js.map} +1 -1
  74. package/dist/core/web/public/next/assets/AutonomySection-C4GBsQVc.js +2 -0
  75. package/dist/core/web/public/next/assets/{AutonomySection-yeNNUPur.js.map → AutonomySection-C4GBsQVc.js.map} +1 -1
  76. package/dist/core/web/public/next/assets/ChatSection-BpOBOUvg.css +1 -0
  77. package/dist/core/web/public/next/assets/ChatSection-hlgPBRco.js +84 -0
  78. package/dist/core/web/public/next/assets/ChatSection-hlgPBRco.js.map +1 -0
  79. package/dist/core/web/public/next/assets/ConfirmDialog-BdQYI-Ng.js +2 -0
  80. package/dist/core/web/public/next/assets/ConfirmDialog-BdQYI-Ng.js.map +1 -0
  81. package/dist/core/web/public/next/assets/{Dialog-nK15L1M0.js → Dialog-BxdIfmFH.js} +2 -2
  82. package/dist/core/web/public/next/assets/{Dialog-nK15L1M0.js.map → Dialog-BxdIfmFH.js.map} +1 -1
  83. package/dist/core/web/public/next/assets/{DiffViewer-D369eZe8.js → DiffViewer-DmeRWuc4.js} +2 -2
  84. package/dist/core/web/public/next/assets/{DiffViewer-D369eZe8.js.map → DiffViewer-DmeRWuc4.js.map} +1 -1
  85. package/dist/core/web/public/next/assets/{McpSection-DVgiSYG1.js → McpSection-q34T7Tho.js} +2 -2
  86. package/dist/core/web/public/next/assets/{McpSection-DVgiSYG1.js.map → McpSection-q34T7Tho.js.map} +1 -1
  87. package/dist/core/web/public/next/assets/{MutationTokenDialog-B9I4PAcp.js → MutationTokenDialog-CFAkwOEt.js} +2 -2
  88. package/dist/core/web/public/next/assets/{MutationTokenDialog-B9I4PAcp.js.map → MutationTokenDialog-CFAkwOEt.js.map} +1 -1
  89. package/dist/core/web/public/next/assets/{ResourcePoolConsoleApp-z6tJj3s9.js → ResourcePoolConsoleApp-DHc8GFOY.js} +4 -4
  90. package/dist/core/web/public/next/assets/{ResourcePoolConsoleApp-z6tJj3s9.js.map → ResourcePoolConsoleApp-DHc8GFOY.js.map} +1 -1
  91. package/dist/core/web/public/next/assets/{RouteErrorBoundary-OSajL45F.js → RouteErrorBoundary-ZL9F4qUn.js} +2 -2
  92. package/dist/core/web/public/next/assets/{RouteErrorBoundary-OSajL45F.js.map → RouteErrorBoundary-ZL9F4qUn.js.map} +1 -1
  93. package/dist/core/web/public/next/assets/{Meter-CLcr-4wS.css → Segmented-8cf0Fypn.css} +1 -1
  94. package/dist/core/web/public/next/assets/{Meter-Cmo1pLbk.js → Segmented-DbRjVye3.js} +2 -2
  95. package/dist/core/web/public/next/assets/Segmented-DbRjVye3.js.map +1 -0
  96. package/dist/core/web/public/next/assets/SettingsSection-nJGGGYqv.js +2 -0
  97. package/dist/core/web/public/next/assets/{SettingsSection-B3YH2Npi.js.map → SettingsSection-nJGGGYqv.js.map} +1 -1
  98. package/dist/core/web/public/next/assets/UniverseConsoleApp-6GJydgo4.js +2 -0
  99. package/dist/core/web/public/next/assets/{UniverseConsoleApp-wEKIzl1M.js.map → UniverseConsoleApp-6GJydgo4.js.map} +1 -1
  100. package/dist/core/web/public/next/assets/{UniverseView-CQVpJqqI.js → UniverseView-DLDtNYL8.js} +2 -2
  101. package/dist/core/web/public/next/assets/{UniverseView-CQVpJqqI.js.map → UniverseView-DLDtNYL8.js.map} +1 -1
  102. package/dist/core/web/public/next/assets/UsageSection-Cf9d9iFH.js +2 -0
  103. package/dist/core/web/public/next/assets/UsageSection-Cf9d9iFH.js.map +1 -0
  104. package/dist/core/web/public/next/assets/VerseConsoleApp-DsdKQoTH.css +1 -0
  105. package/dist/core/web/public/next/assets/VerseConsoleApp-y0uOHixd.js +3 -0
  106. package/dist/core/web/public/next/assets/VerseConsoleApp-y0uOHixd.js.map +1 -0
  107. package/dist/core/web/public/next/assets/{charts-BopViIha.js → charts-CVooqJWN.js} +2 -2
  108. package/dist/core/web/public/next/assets/{charts-BopViIha.js.map → charts-CVooqJWN.js.map} +1 -1
  109. package/dist/core/web/public/next/assets/context-model-Bqv8DVuH.js +2 -0
  110. package/dist/core/web/public/next/assets/context-model-Bqv8DVuH.js.map +1 -0
  111. package/dist/core/web/public/next/assets/global-eaY9GyX3.js +2 -0
  112. package/dist/core/web/public/next/assets/global-eaY9GyX3.js.map +1 -0
  113. package/dist/core/web/public/next/assets/{icons-L06pQH6j.js → icons-vex8rclC.js} +2 -2
  114. package/dist/core/web/public/next/assets/{icons-L06pQH6j.js.map → icons-vex8rclC.js.map} +1 -1
  115. package/dist/core/web/public/next/assets/{index-CXgVQHek.js → index-D2Eik-zt.js} +3 -3
  116. package/dist/core/web/public/next/assets/{index-CXgVQHek.js.map → index-D2Eik-zt.js.map} +1 -1
  117. package/dist/core/web/public/next/assets/{queries-D3sSlan1.js → queries-bAYU0VNy.js} +2 -2
  118. package/dist/core/web/public/next/assets/{queries-D3sSlan1.js.map → queries-bAYU0VNy.js.map} +1 -1
  119. package/dist/core/web/public/next/assets/use-guarded-action-C7QrP7Yp.js +2 -0
  120. package/dist/core/web/public/next/assets/{use-guarded-action-BxfJ9E-n.js.map → use-guarded-action-C7QrP7Yp.js.map} +1 -1
  121. package/dist/core/web/public/next/assets/verse-model-TfDk9cky.js +2 -0
  122. package/dist/core/web/public/next/assets/verse-model-TfDk9cky.js.map +1 -0
  123. package/dist/core/web/public/next/assets/{workspace-model-v0a1gl-C.js → workspace-model-CHol5RVp.js} +2 -2
  124. package/dist/core/web/public/next/assets/{workspace-model-v0a1gl-C.js.map → workspace-model-CHol5RVp.js.map} +1 -1
  125. package/dist/core/web/public/next/index.html +1 -1
  126. package/dist/release-dependency-inventory.json +1 -1
  127. package/docs/README.md +1 -0
  128. package/package.json +1 -1
  129. package/dist/core/web/public/next/assets/AutonomySection-yeNNUPur.js +0 -2
  130. package/dist/core/web/public/next/assets/ChatSection-BfnLe5dT.css +0 -1
  131. package/dist/core/web/public/next/assets/ChatSection-DwDDnCZQ.js +0 -80
  132. package/dist/core/web/public/next/assets/ChatSection-DwDDnCZQ.js.map +0 -1
  133. package/dist/core/web/public/next/assets/ConfirmDialog-C363ty_c.js +0 -2
  134. package/dist/core/web/public/next/assets/ConfirmDialog-C363ty_c.js.map +0 -1
  135. package/dist/core/web/public/next/assets/Meter-Cmo1pLbk.js.map +0 -1
  136. package/dist/core/web/public/next/assets/SettingsSection-B3YH2Npi.js +0 -2
  137. package/dist/core/web/public/next/assets/UniverseConsoleApp-wEKIzl1M.js +0 -2
  138. package/dist/core/web/public/next/assets/UsageSection-dToBThXe.js +0 -2
  139. package/dist/core/web/public/next/assets/UsageSection-dToBThXe.js.map +0 -1
  140. package/dist/core/web/public/next/assets/VerseConsoleApp-CMkCizvL.css +0 -1
  141. package/dist/core/web/public/next/assets/VerseConsoleApp-DHU32JCQ.js +0 -3
  142. package/dist/core/web/public/next/assets/VerseConsoleApp-DHU32JCQ.js.map +0 -1
  143. package/dist/core/web/public/next/assets/global-DMWakEhP.js +0 -2
  144. package/dist/core/web/public/next/assets/global-DMWakEhP.js.map +0 -1
  145. package/dist/core/web/public/next/assets/use-guarded-action-BxfJ9E-n.js +0 -2
  146. package/dist/core/web/public/next/assets/verse-model-CvtK6vP6.js +0 -2
  147. package/dist/core/web/public/next/assets/verse-model-CvtK6vP6.js.map +0 -1
@@ -0,0 +1,2 @@
1
+ import{a as e,n as t,o as n}from"./index-D2Eik-zt.js";import{L as r,ct as i,j as a,st as o,u as s}from"./global-eaY9GyX3.js";import{t as c}from"./Dialog-BxdIfmFH.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=a();if(!n)throw new l;let i=await s(e,t,n);return r(),i}async function d(){await u(`/api/fleet/pause`,{}),o(`dashboard-snapshot`),o(`control-snapshot`),o(`fleet`),o(`fleet-activity`)}async function f(){await u(`/api/fleet/resume`,{}),o(`dashboard-snapshot`),o(`control-snapshot`),o(`fleet`),o(`fleet-activity`)}async function p(e){await u(`/api/inbox/${encodeURIComponent(e)}/approve`,{}),o(`inbox`),o(`dashboard-snapshot`),o(`proposal-detail-${e}`),i(`inbox-list:`)}async function m(e){await u(`/api/inbox/${encodeURIComponent(e)}/reject`,{}),o(`inbox`),o(`dashboard-snapshot`),o(`proposal-detail-${e}`),i(`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-BdQYI-Ng.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ConfirmDialog-BdQYI-Ng.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 maps that code-less\n * 404 to DispatchDisabledError (a 404 carrying a `code` stays an ApiError).\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":"sKAiBA,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-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
+ import{a as e,i as t,n,o as r}from"./index-D2Eik-zt.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-BxdIfmFH.js.map
@@ -1 +1 @@
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
+ {"version":3,"file":"Dialog-BxdIfmFH.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-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(`
1
+ import{a as e,n as t,o as n}from"./index-D2Eik-zt.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-D369eZe8.js.map
7
+ //# sourceMappingURL=DiffViewer-DmeRWuc4.js.map
@@ -1 +1 @@
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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"}
1
+ {"version":3,"file":"DiffViewer-DmeRWuc4.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,2 +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
1
+ import{n as e}from"./index-D2Eik-zt.js";import{l as t,p as n,s as r,v as i}from"./global-eaY9GyX3.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-q34T7Tho.js.map