@opetope/devtools 0.1.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 (106) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/LICENSE +21 -0
  3. package/README.md +105 -0
  4. package/README.ru.md +105 -0
  5. package/dist/activity-export.d.ts +4 -0
  6. package/dist/activity.d.ts +63 -0
  7. package/dist/conformance-activity.d.ts +8 -0
  8. package/dist/conformance.d.ts +37 -0
  9. package/dist/control-subject.d.ts +15 -0
  10. package/dist/control-ui-DMmVy9Ob.js +16 -0
  11. package/dist/control-ui-DMmVy9Ob.js.map +1 -0
  12. package/dist/control.d.ts +45 -0
  13. package/dist/corpus.d.ts +33 -0
  14. package/dist/decode-activity.d.ts +4 -0
  15. package/dist/decode-shapes.d.ts +32 -0
  16. package/dist/decode-values.d.ts +36 -0
  17. package/dist/decode.d.ts +41 -0
  18. package/dist/digest.d.ts +17 -0
  19. package/dist/export-run-DIQZax_d.js +2 -0
  20. package/dist/export-run-DIQZax_d.js.map +1 -0
  21. package/dist/export-snapshot-Buw-c6ZG.js +2 -0
  22. package/dist/export-snapshot-Buw-c6ZG.js.map +1 -0
  23. package/dist/export-snapshot.d.ts +34 -0
  24. package/dist/extension-points.d.ts +45 -0
  25. package/dist/fixture-plan.d.ts +62 -0
  26. package/dist/fixture-runtime.d.ts +37 -0
  27. package/dist/fixture.d.ts +23 -0
  28. package/dist/fold-snapshot-DjiURn8v.js +2 -0
  29. package/dist/fold-snapshot-DjiURn8v.js.map +1 -0
  30. package/dist/fold-snapshot.d.ts +15 -0
  31. package/dist/graph-index.d.ts +104 -0
  32. package/dist/graph-store.d.ts +77 -0
  33. package/dist/graph.d.ts +211 -0
  34. package/dist/index.d.ts +33 -0
  35. package/dist/index.js +2 -0
  36. package/dist/index.js.map +1 -0
  37. package/dist/inspector.d.ts +70 -0
  38. package/dist/install.d.ts +44 -0
  39. package/dist/install.js +2 -0
  40. package/dist/install.js.map +1 -0
  41. package/dist/integration.d.ts +42 -0
  42. package/dist/integration.js +2 -0
  43. package/dist/integration.js.map +1 -0
  44. package/dist/legend-DT7j0_pD.js +2 -0
  45. package/dist/legend-DT7j0_pD.js.map +1 -0
  46. package/dist/node-facts.d.ts +20 -0
  47. package/dist/node-state.d.ts +152 -0
  48. package/dist/optional.d.ts +9 -0
  49. package/dist/panel-5jvisQBr.js +367 -0
  50. package/dist/panel-5jvisQBr.js.map +1 -0
  51. package/dist/producer-ids.d.ts +15 -0
  52. package/dist/protocol-joKTjlMj.js +2 -0
  53. package/dist/protocol-joKTjlMj.js.map +1 -0
  54. package/dist/protocol.d.ts +238 -0
  55. package/dist/react/activity-rows.d.ts +12 -0
  56. package/dist/react/activity-view.d.ts +9 -0
  57. package/dist/react/control-locale.d.ts +25 -0
  58. package/dist/react/control-styles.d.ts +7 -0
  59. package/dist/react/control-ui.d.ts +37 -0
  60. package/dist/react/edge-filter.d.ts +36 -0
  61. package/dist/react/edge-filters.d.ts +17 -0
  62. package/dist/react/export-delivery.d.ts +33 -0
  63. package/dist/react/export-run.d.ts +20 -0
  64. package/dist/react/format.d.ts +17 -0
  65. package/dist/react/glyph.d.ts +20 -0
  66. package/dist/react/graph-layout.d.ts +67 -0
  67. package/dist/react/graph-view.d.ts +30 -0
  68. package/dist/react/graph-viewport.d.ts +48 -0
  69. package/dist/react/icons.d.ts +12 -0
  70. package/dist/react/inspector-view.d.ts +29 -0
  71. package/dist/react/label.d.ts +28 -0
  72. package/dist/react/lazy-module.d.ts +38 -0
  73. package/dist/react/legend-locale.d.ts +23 -0
  74. package/dist/react/legend.d.ts +10 -0
  75. package/dist/react/locale-context.d.ts +17 -0
  76. package/dist/react/locale.d.ts +115 -0
  77. package/dist/react/main-column.d.ts +22 -0
  78. package/dist/react/panel-header.d.ts +20 -0
  79. package/dist/react/panel-mode.d.ts +3 -0
  80. package/dist/react/panel-status.d.ts +19 -0
  81. package/dist/react/panel.d.ts +41 -0
  82. package/dist/react/search-results.d.ts +18 -0
  83. package/dist/react/selection.d.ts +15 -0
  84. package/dist/react/styles.d.ts +13 -0
  85. package/dist/react/theme.d.ts +39 -0
  86. package/dist/react/timeline-locale.d.ts +20 -0
  87. package/dist/react/timeline-view.d.ts +14 -0
  88. package/dist/react/tree.d.ts +26 -0
  89. package/dist/react/use-store.d.ts +11 -0
  90. package/dist/react.d.ts +16 -0
  91. package/dist/react.js +2 -0
  92. package/dist/react.js.map +1 -0
  93. package/dist/search-CC7avy6I.js +2 -0
  94. package/dist/search-CC7avy6I.js.map +1 -0
  95. package/dist/search.d.ts +20 -0
  96. package/dist/session-qjKT0E69.js +2 -0
  97. package/dist/session-qjKT0E69.js.map +1 -0
  98. package/dist/session.d.ts +59 -0
  99. package/dist/testing.d.ts +10 -0
  100. package/dist/testing.js +4 -0
  101. package/dist/testing.js.map +1 -0
  102. package/dist/timeline-view-ByPB1a7-.js +2 -0
  103. package/dist/timeline-view-ByPB1a7-.js.map +1 -0
  104. package/dist/timeline.d.ts +57 -0
  105. package/dist/visual-axes.d.ts +35 -0
  106. package/package.json +107 -0
@@ -0,0 +1,41 @@
1
+ /**
2
+ * The hostile boundary of `docs/devtools.md` §7 and §8: a snapshot or a frame that arrives as
3
+ * untrusted data is decoded under explicit budgets, and anything malformed is rejected instead of partially applied.
4
+ *
5
+ * This is the only place in the package that treats protocol values as untrusted. The in-process observation port is
6
+ * a trusted seam and does not need it; an export replayed from a file, a fixture from a bug report, or any future
7
+ * transport does. Budgets are per record count and per byte, because a payload can be well-formed and still be an
8
+ * attack on the panel's memory.
9
+ */
10
+ import { DevtoolsDecodeError } from './decode-values.js';
11
+ import type { RuntimeGraphFrame, RuntimeGraphSnapshot } from './graph.js';
12
+ type DecodeLimits = Readonly<{
13
+ /**
14
+ * UTF-8 size of the incoming text, checked before `JSON.parse` so a huge payload never becomes a huge object
15
+ * graph. Bytes, not code units: a limit named in bytes that counts UTF-16 units lets any non-ASCII payload
16
+ * through at up to three times its budget.
17
+ */
18
+ maxBytes: number;
19
+ maxEdges: number;
20
+ maxNodes: number;
21
+ maxOperations: number;
22
+ maxStringLength: number;
23
+ }>;
24
+ declare const defaultDecodeLimits: DecodeLimits;
25
+ /**
26
+ * Decodes an untrusted snapshot. The schema revision is checked first: a payload from another revision is rejected
27
+ * whole, because a decoder that guesses at an unknown shape is how a debugger starts showing a graph nobody has.
28
+ */
29
+ declare function decodeSnapshot(value: unknown, limits?: DecodeLimits): RuntimeGraphSnapshot;
30
+ /** Decodes an untrusted frame. Bounds and continuity are the store's job; shape and budget are this one's. */
31
+ declare function decodeFrame(value: unknown, limits?: DecodeLimits): RuntimeGraphFrame;
32
+ /**
33
+ * Enforces the byte budget before parsing, so an oversized payload never becomes an object graph at all.
34
+ *
35
+ * Two bounds keep the exact count off the hot path: one code unit is never less than one byte and never more than
36
+ * three, so a text longer than the budget is over it, and a text three times shorter is under it. Only the band
37
+ * between them is counted unit by unit.
38
+ */
39
+ declare function parseProtocolText(text: string, limits?: DecodeLimits): unknown;
40
+ export { decodeFrame, decodeSnapshot, defaultDecodeLimits, DevtoolsDecodeError, parseProtocolText };
41
+ export type { DecodeLimits };
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Canonical form of a protocol value, and a digest over it.
3
+ *
4
+ * The protocol is data-only by construction (§2.6), so a snapshot has exactly one canonical text: object keys sorted,
5
+ * array order preserved because it is meaningful, absent keys dropped so `{ a: undefined }` and `{}` compare equal.
6
+ * That gives the deterministic digest §6 asks of the 20k/60k corpus, and it gives the conformance kit a way to
7
+ * compare two snapshots without importing a test framework's deep-equality.
8
+ *
9
+ * Digest is FNV-1a over the canonical text: it is not a cryptographic hash and is never used as one — it answers
10
+ * "did these two runs produce the same graph", nothing else.
11
+ */
12
+ /** One text per graph, independent of the order in which the producer happened to build its records. */
13
+ declare function canonicalJson(value: unknown): string;
14
+ declare function digestOf(value: unknown): string;
15
+ /** Whether two protocol values are the same data, ignoring key order and absent optional fields. */
16
+ declare function sameProtocolValue(left: unknown, right: unknown): boolean;
17
+ export { canonicalJson, digestOf, sameProtocolValue };
@@ -0,0 +1,2 @@
1
+ import{e as s}from"./export-snapshot-Buw-c6ZG.js";function p(o,d){return`opetope-devtools-${o.replaceAll(/[^\w-]/g,"-")}-${d}.json`}function u(o,d){const e=new Blob([o],{type:"application/json"}),n=URL.createObjectURL(e),t=document.createElement("a");t.download=d,t.href=n,t.rel="noopener",document.body.append(t),t.click(),t.remove(),URL.revokeObjectURL(n)}function f(){var e,n;const o=(n=(e=globalThis.navigator)==null?void 0:e.clipboard)==null?void 0:n.writeText.bind(globalThis.navigator.clipboard);return{...typeof(URL==null?void 0:URL.createObjectURL)=="function"&&typeof globalThis.Blob=="function"?{download:u}:{},...o===void 0?{}:{writeText:o}}}async function w(o,d,e){if(e.writeText!==void 0)try{return await e.writeText(o),{kind:"copied"}}catch(n){if(e.download===void 0)return{detail:String(n),kind:"failed"}}if(e.download===void 0)return{kind:"failed"};try{return e.download(o,d),{kind:"downloaded"}}catch(n){return{detail:String(n),kind:"failed"}}}const x={exportCopied:"Snapshot JSON copied to the clipboard.",exportDownloaded:"Snapshot JSON saved as a file.",exportFailed:"The snapshot could not be exported.",exportHandled:"Snapshot handed to the host."},b={exportCopied:"JSON \u0441\u043D\u0438\u043C\u043A\u0430 \u0441\u043A\u043E\u043F\u0438\u0440\u043E\u0432\u0430\u043D \u0432 \u0431\u0443\u0444\u0435\u0440 \u043E\u0431\u043C\u0435\u043D\u0430.",exportDownloaded:"JSON \u0441\u043D\u0438\u043C\u043A\u0430 \u0441\u043E\u0445\u0440\u0430\u043D\u0451\u043D \u0444\u0430\u0439\u043B\u043E\u043C.",exportFailed:"\u0421\u043D\u0438\u043C\u043E\u043A \u0432\u044B\u0433\u0440\u0443\u0437\u0438\u0442\u044C \u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C.",exportHandled:"\u0421\u043D\u0438\u043C\u043E\u043A \u043F\u0435\u0440\u0435\u0434\u0430\u043D \u0445\u043E\u0441\u0442\u0443."},h={copied:"exportCopied",downloaded:"exportDownloaded",failed:"exportFailed",handled:"exportHandled"};async function g({host:o,locale:d,onExport:e,store:n}){const t=d==="ru"?b:x,r=s(n);if(e!==void 0)return e(r),t.exportHandled;const l=JSON.stringify(r,void 0,2),c=p(r.status.sessionId,r.status.lastSequence),a=await w(l,c,o??f()),i=t[h[a.kind]];return a.detail===void 0?i:`${i} ${a.detail}`}export{g as runExport};
2
+ //# sourceMappingURL=export-run-DIQZax_d.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"export-run-DIQZax_d.js","sources":["../src/react/export-delivery.ts","../src/react/export-run.ts"],"sourcesContent":["/**\n * Delivery of a snapshot export to wherever the host can actually receive it.\n *\n * §2.6 allows no network transport, so there are exactly two destinations: the clipboard, and a file the browser saves\n * locally. Which one is available depends on the host — the clipboard needs a secure context and a permission the\n * user may refuse — so both are tried in order and the outcome is reported rather than assumed. A button that\n * silently did nothing is the defect this replaces.\n */\n\ntype ExportOutcome = Readonly<{ detail?: string; kind: 'copied' | 'downloaded' | 'failed' | 'handled' }>;\n\n/**\n * The capabilities delivery needs, named one by one so a test can offer a host with only some of them, and so no\n * global is read outside `browserExportHost`.\n */\ntype ExportHost = Readonly<{\n download?: (text: string, filename: string) => void;\n writeText?: (text: string) => PromiseLike<void>;\n}>;\n\n/** Ids carry dots and colons; a filename should carry neither. */\nfunction exportFilename(sessionId: string, sequence: number): string {\n return `opetope-devtools-${sessionId.replaceAll(/[^\\w-]/g, '-')}-${sequence}.json`;\n}\n\nfunction browserDownload(text: string, filename: string): void {\n const blob = new Blob([text], { type: 'application/json' });\n const url = URL.createObjectURL(blob);\n const anchor = document.createElement('a');\n\n anchor.download = filename;\n anchor.href = url;\n anchor.rel = 'noopener';\n document.body.append(anchor);\n anchor.click();\n anchor.remove();\n URL.revokeObjectURL(url);\n}\n\n/** What this browser can do, decided by feature detection rather than by a user-agent guess. */\nfunction browserExportHost(): ExportHost {\n const writeText = globalThis.navigator?.clipboard?.writeText.bind(globalThis.navigator.clipboard);\n const canDownload = typeof URL?.createObjectURL === 'function' && typeof globalThis.Blob === 'function';\n\n return {\n ...(canDownload ? { download: browserDownload } : {}),\n ...(writeText === undefined ? {} : { writeText }),\n };\n}\n\n/**\n * Clipboard first, because the developer is usually about to paste it into a report; a saved file second, because a\n * large graph is easier to keep than to hold. Failure is a reported outcome, never a thrown error: an export that\n * could not be delivered must not take the panel down with it.\n */\nasync function deliverExport(text: string, filename: string, host: ExportHost): Promise<ExportOutcome> {\n if (host.writeText !== undefined) {\n try {\n await host.writeText(text);\n\n return { kind: 'copied' };\n } catch (error) {\n if (host.download === undefined) return { detail: String(error), kind: 'failed' };\n }\n }\n\n // No detail: the translated outcome already says it could not be exported, and prose here would not be translated.\n if (host.download === undefined) return { kind: 'failed' };\n\n try {\n host.download(text, filename);\n\n return { kind: 'downloaded' };\n } catch (error) {\n return { detail: String(error), kind: 'failed' };\n }\n}\n\n/** The outcome is reported in the panel's language, so the key is resolved against the dictionary, not hard-coded. */\nexport { browserExportHost, deliverExport, exportFilename };\nexport type { ExportHost, ExportOutcome };\n","/**\n * Running an export, and the words that report where it went (`docs/devtools.md` §2.6).\n *\n * The panel keeps the button; this module is what the button loads. Building a redacted snapshot, canonicalising it\n * and reaching a clipboard is a page of code and four sentences of prose that a session which never exports has no\n * reason to carry, so both travel behind the click.\n */\n\nimport type { DevtoolsExport } from '../export-snapshot';\nimport { exportSnapshot } from '../export-snapshot';\n\nimport type { ExportHost, ExportOutcome } from './export-delivery';\nimport { browserExportHost, deliverExport, exportFilename } from './export-delivery';\nimport type { DevtoolsLocale } from './locale';\n\nconst en = {\n exportCopied: 'Snapshot JSON copied to the clipboard.',\n exportDownloaded: 'Snapshot JSON saved as a file.',\n exportFailed: 'The snapshot could not be exported.',\n exportHandled: 'Snapshot handed to the host.',\n};\n\nconst ru: typeof en = {\n exportCopied: 'JSON снимка скопирован в буфер обмена.',\n exportDownloaded: 'JSON снимка сохранён файлом.',\n exportFailed: 'Снимок выгрузить не удалось.',\n exportHandled: 'Снимок передан хосту.',\n};\n\nconst messageKeys: Readonly<Record<ExportOutcome['kind'], keyof typeof en>> = {\n copied: 'exportCopied',\n downloaded: 'exportDownloaded',\n failed: 'exportFailed',\n handled: 'exportHandled',\n};\n\ntype ExportRequest = Readonly<{\n host: ExportHost | undefined;\n locale: DevtoolsLocale;\n onExport: ((payload: DevtoolsExport) => void) | undefined;\n store: Parameters<typeof exportSnapshot>[0];\n}>;\n\n/** §2.6: no network transport. The clipboard and a local file are the only destinations delivery may use. */\nasync function runExport({ host, locale, onExport, store }: ExportRequest): Promise<string> {\n const strings = locale === 'ru' ? ru : en;\n const payload = exportSnapshot(store);\n\n if (onExport !== undefined) {\n onExport(payload);\n\n return strings.exportHandled;\n }\n\n const json = JSON.stringify(payload, undefined, 2);\n const filename = exportFilename(payload.status.sessionId, payload.status.lastSequence);\n const outcome = await deliverExport(json, filename, host ?? browserExportHost());\n const message = strings[messageKeys[outcome.kind]];\n\n return outcome.detail === undefined ? message : `${message} ${outcome.detail}`;\n}\n\nexport { runExport };\n"],"names":["exportFilename","sessionId","sequence","browserDownload","text","filename","blob","url","anchor","browserExportHost","writeText","_b","_a","deliverExport","host","error","en","ru","messageKeys","runExport","locale","onExport","store","strings","payload","exportSnapshot","json","outcome","message"],"mappings":"kDAqBA,SAASA,EAAeC,EAAmBC,EAAgB,CACzD,MAAO,oBAAoBD,EAAU,WAAW,UAAW,GAAG,CAAC,IAAIC,CAAQ,OAC7E,CAEA,SAASC,EAAgBC,EAAcC,EAAgB,CACrD,MAAMC,EAAO,IAAI,KAAK,CAACF,CAAI,EAAG,CAAE,KAAM,mBAAoB,EACpDG,EAAM,IAAI,gBAAgBD,CAAI,EAC9BE,EAAS,SAAS,cAAc,GAAG,EAEzCA,EAAO,SAAWH,EAClBG,EAAO,KAAOD,EACdC,EAAO,IAAM,WACb,SAAS,KAAK,OAAOA,CAAM,EAC3BA,EAAO,MAAK,EACZA,EAAO,OAAM,EACb,IAAI,gBAAgBD,CAAG,CACzB,CAGA,SAASE,GAAiB,SACxB,MAAMC,GAAYC,GAAAC,EAAA,WAAW,YAAX,YAAAA,EAAsB,YAAtB,YAAAD,EAAiC,UAAU,KAAK,WAAW,UAAU,WAGvF,MAAO,CACL,GAHkB,OAAO,qBAAK,kBAAoB,YAAc,OAAO,WAAW,MAAS,WAGzE,CAAE,SAAUR,CAAe,EAAK,CAAA,EAClD,GAAIO,IAAc,OAAY,GAAK,CAAE,UAAAA,CAAS,EAElD,CAOA,eAAeG,EAAcT,EAAcC,EAAkBS,EAAgB,CAC3E,GAAIA,EAAK,YAAc,OACrB,GAAI,CACF,aAAMA,EAAK,UAAUV,CAAI,EAElB,CAAE,KAAM,QAAQ,CACzB,OAASW,EAAO,CACd,GAAID,EAAK,WAAa,OAAW,MAAO,CAAE,OAAQ,OAAOC,CAAK,EAAG,KAAM,QAAQ,CACjF,CAIF,GAAID,EAAK,WAAa,OAAW,MAAO,CAAE,KAAM,QAAQ,EAExD,GAAI,CACF,OAAAA,EAAK,SAASV,EAAMC,CAAQ,EAErB,CAAE,KAAM,YAAY,CAC7B,OAASU,EAAO,CACd,MAAO,CAAE,OAAQ,OAAOA,CAAK,EAAG,KAAM,QAAQ,CAChD,CACF,CC7DA,MAAMC,EAAK,CACT,aAAc,yCACd,iBAAkB,iCAClB,aAAc,sCACd,cAAe,gCAGXC,EAAgB,CACpB,aAAc,qLACd,iBAAkB,mIAClB,aAAc,uJACd,cAAe,mHAGXC,EAAwE,CAC5E,OAAQ,eACR,WAAY,mBACZ,OAAQ,eACR,QAAS,iBAWX,eAAeC,EAAU,CAAE,KAAAL,EAAM,OAAAM,EAAQ,SAAAC,EAAU,MAAAC,CAAK,EAAiB,CACvE,MAAMC,EAAUH,IAAW,KAAOH,EAAKD,EACjCQ,EAAUC,EAAeH,CAAK,EAEpC,GAAID,IAAa,OACf,OAAAA,EAASG,CAAO,EAETD,EAAQ,cAGjB,MAAMG,EAAO,KAAK,UAAUF,EAAS,OAAW,CAAC,EAC3CnB,EAAWL,EAAewB,EAAQ,OAAO,UAAWA,EAAQ,OAAO,YAAY,EAC/EG,EAAU,MAAMd,EAAca,EAAMrB,EAAUS,GAAQL,GAAmB,EACzEmB,EAAUL,EAAQL,EAAYS,EAAQ,IAAI,CAAC,EAEjD,OAAOA,EAAQ,SAAW,OAAYC,EAAU,GAAGA,CAAO,IAAID,EAAQ,MAAM,EAC9E"}
@@ -0,0 +1,2 @@
1
+ function a(t,n){let e=n,s=!1;const o=[];for(const r of t){if(e===0){s=!0;break}e-=1;let c=r.loads;if(c!=="unknown"){const i=c.length;c=c.slice(0,e),e-=c.length,s||(s=c.length!==i)}o.push({...r,loads:c})}return{resources:o,truncated:s}}function l(t,n){const e=t.features.slice(0,n),s=t.calls.slice(0,n-e.length),o=a(t.resources,n-e.length-s.length),r=t.truncated||o.truncated||e.length!==t.features.length||s.length!==t.calls.length;return{...t,calls:s,features:e,resources:o.resources,truncated:r}}const d={maxEdges:2e4,maxNodes:5e3,maxTimelineEntries:1e3};function u(t){for(const n of["maxEdges","maxNodes","maxTimelineEntries"]){const e=t[n];if(!Number.isSafeInteger(e)||e<0)throw new RangeError(`Export ${n} must be a non-negative safe integer.`)}}function f(t,n){const e=[],s=new Set;let o=!1;for(const r of t.nodes()){if(e.length>=n){o=!0;break}e.push(r),s.add(r.id)}return{nodeIds:s,nodes:e,truncated:o}}function g(t,n,e){const s=[];let o=!1;for(const r of t.edges()){if(s.length>=e){o=!0;break}if(!n.has(r.from)||!n.has(r.to)){o=!0;continue}s.push(r)}return{edges:s,truncated:o}}function m(t,n=d){u(n);const e=t.graph,s=f(e,n.maxNodes),o=g(e,s.nodeIds,n.maxEdges),r=t.timeline.list();return{...t.activity===void 0?{}:{activity:l(t.activity,n.maxNodes)},edges:o.edges,exportRevision:"opetope.devtools-export/1",nodes:s.nodes,plan:e.plan,status:t.status,timeline:n.maxTimelineEntries===0?[]:r.slice(-n.maxTimelineEntries),truncated:{edges:o.truncated,nodes:s.truncated,timeline:r.length>n.maxTimelineEntries}}}export{m as e};
2
+ //# sourceMappingURL=export-snapshot-Buw-c6ZG.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"export-snapshot-Buw-c6ZG.js","sources":["../src/activity-export.ts","../src/export-snapshot.ts"],"sourcesContent":["import type { ResourceActivity, RuntimeActivitySnapshot } from './activity';\n\nfunction clipResources(entries: readonly ResourceActivity[], limit: number) {\n let remaining = limit;\n let truncated = false;\n const resources: ResourceActivity[] = [];\n\n for (const resource of entries) {\n if (remaining === 0) {\n truncated = true;\n break;\n }\n\n remaining -= 1;\n let loads = resource.loads;\n\n if (loads !== 'unknown') {\n const length = loads.length;\n loads = loads.slice(0, remaining);\n remaining -= loads.length;\n truncated ||= loads.length !== length;\n }\n\n resources.push({ ...resource, loads });\n }\n\n return { resources, truncated };\n}\n\n/** Export caps are independent from the live producer's recording cap. */\nfunction clipActivity(snapshot: RuntimeActivitySnapshot, limit: number): RuntimeActivitySnapshot {\n const features = snapshot.features.slice(0, limit);\n const calls = snapshot.calls.slice(0, limit - features.length);\n const clipped = clipResources(snapshot.resources, limit - features.length - calls.length);\n const truncated =\n snapshot.truncated ||\n clipped.truncated ||\n features.length !== snapshot.features.length ||\n calls.length !== snapshot.calls.length;\n\n return { ...snapshot, calls, features, resources: clipped.resources, truncated };\n}\n\nexport { clipActivity };\n","/**\n * Redacted export, per `docs/devtools.md` §2.6: canonical data-only JSON, hard record limits, and\n * no network transport of any kind. There is no redaction pass to write for messages and stacks because the protocol\n * never carried them — the export is a copy of records that were already export-safe, plus explicit truncation marks\n * so a clipped export cannot be mistaken for a small graph.\n */\n\nimport type { RuntimeActivitySnapshot } from './activity';\nimport { clipActivity } from './activity-export';\nimport type { DiagnosticNode } from './graph';\nimport type { DiagnosticGraphIndex } from './graph-index';\nimport type { DevtoolsGraphStore } from './graph-store';\nimport type { DiagnosticEdge } from './protocol';\nimport type { TimelineEntry } from './timeline';\n\ntype ExportLimits = Readonly<{\n /** Caps the exported graph projection. The immutable compiled plan is included separately in full. */\n maxEdges: number;\n maxNodes: number;\n maxTimelineEntries: number;\n}>;\n\nconst defaultExportLimits: ExportLimits = {\n maxEdges: 20_000,\n maxNodes: 5_000,\n maxTimelineEntries: 1_000,\n};\n\ntype DevtoolsExport = Readonly<{\n activity?: RuntimeActivitySnapshot;\n edges: readonly DiagnosticEdge[];\n exportRevision: 'opetope.devtools-export/1';\n nodes: readonly DiagnosticNode[];\n plan: DevtoolsGraphStore['graph']['plan'];\n status: DevtoolsGraphStore['status'];\n timeline: readonly TimelineEntry[];\n truncated: Readonly<{\n edges: boolean;\n nodes: boolean;\n timeline: boolean;\n }>;\n}>;\n\nfunction validateLimits(limits: ExportLimits): void {\n for (const name of ['maxEdges', 'maxNodes', 'maxTimelineEntries'] as const) {\n const limit = limits[name];\n\n if (!Number.isSafeInteger(limit) || limit < 0) {\n throw new RangeError(`Export ${name} must be a non-negative safe integer.`);\n }\n }\n}\n\nfunction collectNodes(index: DiagnosticGraphIndex, limit: number) {\n const nodes: DiagnosticNode[] = [];\n const nodeIds = new Set<string>();\n let truncated = false;\n\n for (const node of index.nodes()) {\n if (nodes.length >= limit) {\n truncated = true;\n\n break;\n }\n\n nodes.push(node);\n nodeIds.add(node.id);\n }\n\n return { nodeIds, nodes, truncated };\n}\n\nfunction collectEdges(index: DiagnosticGraphIndex, nodeIds: ReadonlySet<string>, limit: number) {\n const edges: DiagnosticEdge[] = [];\n let truncated = false;\n\n for (const edge of index.edges()) {\n if (edges.length >= limit) {\n truncated = true;\n\n break;\n }\n\n if (!nodeIds.has(edge.from) || !nodeIds.has(edge.to)) {\n truncated = true;\n\n continue;\n }\n\n edges.push(edge);\n }\n\n return { edges, truncated };\n}\n\nfunction exportSnapshot(store: DevtoolsGraphStore, limits: ExportLimits = defaultExportLimits): DevtoolsExport {\n validateLimits(limits);\n\n const index = store.graph;\n const selected = collectNodes(index, limits.maxNodes);\n const relations = collectEdges(index, selected.nodeIds, limits.maxEdges);\n const timeline = store.timeline.list();\n\n return {\n ...(store.activity === undefined ? {} : { activity: clipActivity(store.activity, limits.maxNodes) }),\n edges: relations.edges,\n exportRevision: 'opetope.devtools-export/1',\n nodes: selected.nodes,\n plan: index.plan,\n status: store.status,\n timeline: limits.maxTimelineEntries === 0 ? [] : timeline.slice(-limits.maxTimelineEntries),\n truncated: {\n edges: relations.truncated,\n nodes: selected.truncated,\n timeline: timeline.length > limits.maxTimelineEntries,\n },\n };\n}\n\nexport { exportSnapshot };\nexport type { DevtoolsExport, ExportLimits };\n"],"names":["clipResources","entries","limit","remaining","truncated","resources","resource","loads","length","clipActivity","snapshot","features","calls","clipped","defaultExportLimits","validateLimits","limits","name","collectNodes","index","nodes","nodeIds","node","collectEdges","edges","edge","exportSnapshot","store","selected","relations","timeline"],"mappings":"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"}
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Redacted export, per `docs/devtools.md` §2.6: canonical data-only JSON, hard record limits, and
3
+ * no network transport of any kind. There is no redaction pass to write for messages and stacks because the protocol
4
+ * never carried them — the export is a copy of records that were already export-safe, plus explicit truncation marks
5
+ * so a clipped export cannot be mistaken for a small graph.
6
+ */
7
+ import type { RuntimeActivitySnapshot } from './activity.js';
8
+ import type { DiagnosticNode } from './graph.js';
9
+ import type { DevtoolsGraphStore } from './graph-store.js';
10
+ import type { DiagnosticEdge } from './protocol.js';
11
+ import type { TimelineEntry } from './timeline.js';
12
+ type ExportLimits = Readonly<{
13
+ /** Caps the exported graph projection. The immutable compiled plan is included separately in full. */
14
+ maxEdges: number;
15
+ maxNodes: number;
16
+ maxTimelineEntries: number;
17
+ }>;
18
+ type DevtoolsExport = Readonly<{
19
+ activity?: RuntimeActivitySnapshot;
20
+ edges: readonly DiagnosticEdge[];
21
+ exportRevision: 'opetope.devtools-export/1';
22
+ nodes: readonly DiagnosticNode[];
23
+ plan: DevtoolsGraphStore['graph']['plan'];
24
+ status: DevtoolsGraphStore['status'];
25
+ timeline: readonly TimelineEntry[];
26
+ truncated: Readonly<{
27
+ edges: boolean;
28
+ nodes: boolean;
29
+ timeline: boolean;
30
+ }>;
31
+ }>;
32
+ declare function exportSnapshot(store: DevtoolsGraphStore, limits?: ExportLimits): DevtoolsExport;
33
+ export { exportSnapshot };
34
+ export type { DevtoolsExport, ExportLimits };
@@ -0,0 +1,45 @@
1
+ /**
2
+ * The two-step projection behind the `Extension points` preset (`docs/devtools.md` §4.2).
3
+ *
4
+ * A contribution is a node _between_ a feature and its target, so a one-hop neighbourhood shows a feature's
5
+ * contributions and stops exactly where the question starts: where do they go, and who else is there. This walks the
6
+ * second step — feature → contributions → targets — and then back down from each target to every other contribution
7
+ * aimed at it, because priority and competition are only legible side by side.
8
+ *
9
+ * Selecting a target inverts the walk: target → contributions → contributors. The rows are the same shape either
10
+ * way, so the layout draws one picture and the panel keeps one selection.
11
+ */
12
+ import type { DevtoolsGraphStore } from './graph-store.js';
13
+ import type { DiagnosticLabel, DiagnosticNodeId, DiagnosticNodeKind } from './protocol.js';
14
+ import type { NodeDisplayState } from './visual-axes.js';
15
+ /** One contribution and the node at its far end: its target, or its contributor when the walk is inverted. */
16
+ type ExtensionRow = Readonly<{
17
+ contribution: DiagnosticNodeId;
18
+ contributionDisplay: NodeDisplayState;
19
+ contributionLabel: DiagnosticLabel;
20
+ /** Whose contribution it is. On a sibling row this is the competitor's name, which is why the row is drawn. */
21
+ contributorLabel: DiagnosticLabel;
22
+ endpoint: DiagnosticNodeId;
23
+ endpointDisplay: NodeDisplayState;
24
+ endpointLabel: DiagnosticLabel;
25
+ /** Whether the selected node is at this contribution's near end. A sibling contribution is somebody else's. */
26
+ own: boolean;
27
+ /** The contribution's own state, as the protocol names it. */
28
+ state: string;
29
+ }>;
30
+ type ExtensionPointsProjection = Readonly<{
31
+ display: NodeDisplayState;
32
+ id: DiagnosticNodeId;
33
+ /** True when the selection is a target and the walk runs from it back to its contributors. */
34
+ inverted: boolean;
35
+ kind: DiagnosticNodeKind;
36
+ label: DiagnosticLabel;
37
+ rows: readonly ExtensionRow[];
38
+ }>;
39
+ /**
40
+ * The projection for the selected node, or `undefined` when it takes no part in any extension point — a feature
41
+ * that contributes nothing has nothing to draw here, and saying so is the layout's job, not this one's.
42
+ */
43
+ declare function projectExtensionPoints(store: DevtoolsGraphStore, id: DiagnosticNodeId): ExtensionPointsProjection | undefined;
44
+ export { projectExtensionPoints };
45
+ export type { ExtensionPointsProjection, ExtensionRow };
@@ -0,0 +1,62 @@
1
+ /**
2
+ * A synthetic workspace application's compiled topology. It exercises the same kinds of nodes and relationships
3
+ * that the runtime compiler emits, including hard/optional providers, on-demand hosts and a lazy feature body.
4
+ *
5
+ * Two features declare `when: [authorizedCondition]` and therefore form one condition group, the inferred owner of
6
+ * activation (D93). The other nine have an empty `when` and are owned by the application itself; one of those nine
7
+ * is a header whose `body` is still on the way, which the plan compiles like any other feature (D186).
8
+ *
9
+ * The story it encodes: the session ends, the group retires, `tasks.resource` fails its cleanup and is quarantined,
10
+ * so the group never finishes closing — and the feature that weakly imports it keeps running on a `missing` lookup
11
+ * instead of failing with it (D105). That is the case this tool exists for.
12
+ */
13
+ import type { ApplicationPlanSnapshot } from './graph.js';
14
+ import type { DiagnosticEdge, DiagnosticEdgeKind, DiagnosticNodeId, StructuralEdgeKind, WeakEdgeState } from './protocol.js';
15
+ declare const fixtureIds: {
16
+ readonly absentExperiments: DiagnosticNodeId;
17
+ readonly accountReminder: DiagnosticNodeId;
18
+ readonly application: DiagnosticNodeId;
19
+ readonly appInfo: DiagnosticNodeId;
20
+ readonly catalogResolve: DiagnosticNodeId;
21
+ readonly authorizedGroup: DiagnosticNodeId;
22
+ readonly condition: DiagnosticNodeId;
23
+ readonly checkoutItemLookup: DiagnosticNodeId;
24
+ readonly checkoutCreateForm: DiagnosticNodeId;
25
+ readonly inventoryFeed: DiagnosticNodeId;
26
+ readonly hostCatalogPlatform: DiagnosticNodeId;
27
+ readonly hostQuoteTelemetry: DiagnosticNodeId;
28
+ readonly hostConfirmActionCommands: DiagnosticNodeId;
29
+ readonly hostConfirmActionView: DiagnosticNodeId;
30
+ readonly hostHelpPlatform: DiagnosticNodeId;
31
+ readonly hostTasksPlatform: DiagnosticNodeId;
32
+ readonly dashboardPage: DiagnosticNodeId;
33
+ readonly portQuote: DiagnosticNodeId;
34
+ readonly portReceiptSink: DiagnosticNodeId;
35
+ readonly portResolveItem: DiagnosticNodeId;
36
+ readonly reportsPage: DiagnosticNodeId;
37
+ readonly confirmAction: DiagnosticNodeId;
38
+ readonly helpContributions: DiagnosticNodeId;
39
+ readonly targetHomeHeader: DiagnosticNodeId;
40
+ readonly targetDashboardPage: DiagnosticNodeId;
41
+ readonly targetOrderToolbar: DiagnosticNodeId;
42
+ readonly targetConfirmActionContent: DiagnosticNodeId;
43
+ readonly tasksResource: DiagnosticNodeId;
44
+ };
45
+ declare const fixtureContributionIds: {
46
+ readonly dashboardPage: DiagnosticNodeId;
47
+ readonly orderToolbar: DiagnosticNodeId;
48
+ readonly confirmActionContent: DiagnosticNodeId;
49
+ readonly supportHomeHeader: DiagnosticNodeId;
50
+ };
51
+ declare const authorizedConditionId: import("./protocol.js").DiagnosticConditionId;
52
+ /** A structural edge names a relation the compiler decided and nothing reports on later. */
53
+ declare function structuralEdge(kind: StructuralEdgeKind, from: DiagnosticNodeId, to: DiagnosticNodeId): DiagnosticEdge;
54
+ /** A hard edge is the only kind an activation waits on, so it is the only kind that can be pending. */
55
+ declare function hardEdge(kind: 'hard-import' | 'requires-port', from: DiagnosticNodeId, to: DiagnosticNodeId, pending?: boolean): DiagnosticEdge;
56
+ /** A weak edge reports the `Lookup` its consumer reads and never waits (D105). */
57
+ declare function weakEdge(kind: 'absent-import' | 'absent-port' | 'optional-import' | 'optional-port', from: DiagnosticNodeId, to: DiagnosticNodeId, lookup: WeakEdgeState): DiagnosticEdge;
58
+ declare const fixturePlan: ApplicationPlanSnapshot;
59
+ /** Exported so a test can assert the fixture really covers every edge semantic the protocol distinguishes. */
60
+ declare const fixtureEdgeKinds: readonly DiagnosticEdgeKind[];
61
+ export { hardEdge as fixtureHardEdge, structuralEdge as fixtureStructuralEdge, weakEdge as fixtureWeakEdge };
62
+ export { authorizedConditionId, fixtureContributionIds, fixtureEdgeKinds, fixtureIds, fixturePlan };
@@ -0,0 +1,37 @@
1
+ /**
2
+ * The live world of the fixture plan and the commits that take it apart.
3
+ *
4
+ * The initial snapshot is a healthy authorized session. The six frames are one logout: the condition flips, the
5
+ * group retires its members in reverse canonical order, `sessions.confirmAction` closes cleanly, and
6
+ * `tasks.resource` fails the cleanup of its resource attachment and is quarantined — so the group never reaches
7
+ * `closed`, and the feature that weakly imports tasks flips from `found` to `missing` and keeps running (D105).
8
+ *
9
+ * The snapshot is deliberately taken *before* the cascade, so connecting to the fixture exercises the reducer and
10
+ * populates the causal chain instead of handing the panel a finished picture it never had to reduce.
11
+ */
12
+ import type { RuntimeGraphFrame, RuntimeGraphSnapshot } from './graph.js';
13
+ import type { DiagnosticNodeId } from './protocol.js';
14
+ declare const fixtureSessionId: import("./protocol.js").DiagnosticSessionId;
15
+ declare const fixtureInstanceIds: {
16
+ readonly accountReminder: DiagnosticNodeId;
17
+ readonly appInfo: DiagnosticNodeId;
18
+ readonly catalogResolve: DiagnosticNodeId;
19
+ readonly checkoutItemLookup: DiagnosticNodeId;
20
+ readonly checkoutCreateForm: DiagnosticNodeId;
21
+ readonly inventoryFeed: DiagnosticNodeId;
22
+ readonly dashboardPage: DiagnosticNodeId;
23
+ readonly confirmAction: DiagnosticNodeId;
24
+ readonly helpContributions: DiagnosticNodeId;
25
+ readonly tasksResource: DiagnosticNodeId;
26
+ };
27
+ declare const fixtureAttachmentIds: {
28
+ readonly confirmActionCommands: DiagnosticNodeId;
29
+ readonly tasksResource: DiagnosticNodeId;
30
+ };
31
+ declare const fixtureSnapshot: RuntimeGraphSnapshot;
32
+ /**
33
+ * Seven commits, one visible scheduler transaction each: the session ends, one cleanup refuses to settle, and the
34
+ * header whose body was still loading finishes opening — an ordinary `opening` → `ready` transition (D186).
35
+ */
36
+ declare const fixtureFrames: readonly RuntimeGraphFrame[];
37
+ export { fixtureAttachmentIds, fixtureFrames, fixtureInstanceIds, fixtureSessionId, fixtureSnapshot };
@@ -0,0 +1,23 @@
1
+ /**
2
+ * A `RuntimeInspectionSession` backed by the sample state, so the panel and its tests run against the real pull /
3
+ * cursor / resync path rather than a hand-placed store.
4
+ *
5
+ * It can also misbehave on purpose: `dropNext` commits a frame and drops it from the servable ring, which is exactly
6
+ * the `queue overflow → bounded gap marker → snapshot resync` case of §7.
7
+ */
8
+ import type { RuntimeGraphFrame, RuntimeGraphSnapshot } from './graph.js';
9
+ import type { RuntimeInspectionSession } from './session.js';
10
+ type FixtureSession = Readonly<{
11
+ /** Commits the next frame but drops it from the servable ring: the client must resync from a snapshot. */
12
+ dropNext: () => boolean;
13
+ emitAll: () => void;
14
+ emitNext: () => boolean;
15
+ pendingCount: () => number;
16
+ }> & RuntimeInspectionSession;
17
+ type FixtureSessionOptions = Readonly<{
18
+ frames?: readonly RuntimeGraphFrame[];
19
+ snapshot?: RuntimeGraphSnapshot;
20
+ }>;
21
+ declare function createFixtureSession(options?: FixtureSessionOptions): FixtureSession;
22
+ export { createFixtureSession };
23
+ export type { FixtureSession, FixtureSessionOptions };
@@ -0,0 +1,2 @@
1
+ const y=2166136261,h=16777619;function d(t){if(Array.isArray(t))return t.map(n=>d(n));if(typeof t!="object"||t===null)return t;const o=Object.entries(t).filter(([,n])=>n!==void 0).sort(([n],[e])=>n<e?-1:n>e?1:0);return Object.fromEntries(o.map(([n,e])=>[n,d(e)]))}function f(t){return JSON.stringify(d(t))}function a(t){const o=f(t);let n=2166136261;for(let e=0;e<o.length;e+=1)n^=o.charCodeAt(e),n=Math.imul(n,16777619);return(n>>>0).toString(16).padStart(8,"0")}function p(t,o){return f(t)===f(o)}function s(t,o){const n=[];for(const e of t)e.kind===o&&n.push(e);return n}function m(t){const{runtime:o}=t,n=new Map,e=[o.conditions,o.groups,o.instances,o.attachments,o.contributions,o.ports,o.hostContracts];for(const r of e)for(const i of r)n.set(i.id,i);return n}function g(t,o,n){for(const e of t.operations)e.op==="upsert-node"?o.set(e.node.id,e.node):e.op==="upsert-edge"?n.set(e.edge.id,e.edge):e.op==="remove-node"?o.delete(e.id):n.delete(e.id)}function l(t,o){return{attachments:s(t,"attachment"),conditions:s(t,"condition"),contributions:s(t,"contribution"),edges:o,groups:s(t,"condition-group"),hostContracts:s(t,"host-contract"),instances:s(t,"feature-instance"),ports:s(t,"port")}}function b(t,o){const n=o.at(-1);if(n===void 0)return t;const e=m(t),r=new Map;for(const c of t.runtime.edges)r.set(c.id,c);let i=t.activity;for(const c of o)g(c,e,r),i=c.activity??i;const u=[...e.values()];return{...t,...i===void 0?{}:{activity:i},lastSequence:n.lastSequence,runtime:l(u,[...r.values()]),stateRevision:n.nextRevision}}export{f as c,a as d,b as f,p as s};
2
+ //# sourceMappingURL=fold-snapshot-DjiURn8v.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fold-snapshot-DjiURn8v.js","sources":["../src/digest.ts","../src/fold-snapshot.ts"],"sourcesContent":["/**\n * Canonical form of a protocol value, and a digest over it.\n *\n * The protocol is data-only by construction (§2.6), so a snapshot has exactly one canonical text: object keys sorted,\n * array order preserved because it is meaningful, absent keys dropped so `{ a: undefined }` and `{}` compare equal.\n * That gives the deterministic digest §6 asks of the 20k/60k corpus, and it gives the conformance kit a way to\n * compare two snapshots without importing a test framework's deep-equality.\n *\n * Digest is FNV-1a over the canonical text: it is not a cryptographic hash and is never used as one — it answers\n * \"did these two runs produce the same graph\", nothing else.\n */\n\nconst fnvOffsetBasis = 0x81_1c_9d_c5;\nconst fnvPrime = 0x01_00_01_93;\n\nfunction canonicalize(value: unknown): unknown {\n if (Array.isArray(value)) return value.map(entry => canonicalize(entry));\n\n if (typeof value !== 'object' || value === null) return value;\n\n const entries = Object.entries(value as Record<string, unknown>)\n .filter(([, entry]) => entry !== undefined)\n .sort(([left], [right]) => (left < right ? -1 : left > right ? 1 : 0));\n\n return Object.fromEntries(entries.map(([key, entry]) => [key, canonicalize(entry)]));\n}\n\n/** One text per graph, independent of the order in which the producer happened to build its records. */\nfunction canonicalJson(value: unknown): string {\n return JSON.stringify(canonicalize(value));\n}\n\nfunction digestOf(value: unknown): string {\n const text = canonicalJson(value);\n let hash = fnvOffsetBasis;\n\n for (let index = 0; index < text.length; index += 1) {\n hash ^= text.charCodeAt(index);\n hash = Math.imul(hash, fnvPrime);\n }\n\n return (hash >>> 0).toString(16).padStart(8, '0');\n}\n\n/** Whether two protocol values are the same data, ignoring key order and absent optional fields. */\nfunction sameProtocolValue(left: unknown, right: unknown): boolean {\n return canonicalJson(left) === canonicalJson(right);\n}\n\nexport { canonicalJson, digestOf, sameProtocolValue };\n","/**\n * A second, independent way to apply frames: fold them straight onto a `RuntimeGraphSnapshot` instead of into the\n * incremental indexes of `DevtoolsGraphStore`.\n *\n * `docs/devtools.md` §8 asks for exactly this: two implementations of the reducer must\n * reach the same workspace state from one transition corpus. It is also what lets a fixture or a replay answer\n * `getSnapshot()` truthfully at any cursor, rather than only at the beginning and the end.\n */\n\nimport type { DiagnosticRuntimeNode, RuntimeGraphFrame, RuntimeGraphSnapshot, RuntimeStateSnapshot } from './graph';\nimport type { DiagnosticEdge, DiagnosticEdgeId, DiagnosticNodeId } from './protocol';\n\ntype NodesOfKind<Kind extends DiagnosticRuntimeNode['kind']> = readonly Extract<\n DiagnosticRuntimeNode,\n { readonly kind: Kind }\n>[];\n\nfunction ofKind<Kind extends DiagnosticRuntimeNode['kind']>(\n nodes: Iterable<DiagnosticRuntimeNode>,\n kind: Kind,\n): NodesOfKind<Kind> {\n const picked: Extract<DiagnosticRuntimeNode, { readonly kind: Kind }>[] = [];\n\n for (const node of nodes) {\n if (node.kind === kind) picked.push(node as Extract<DiagnosticRuntimeNode, { readonly kind: Kind }>);\n }\n\n return picked;\n}\n\nfunction collectNodes(snapshot: RuntimeGraphSnapshot): Map<DiagnosticNodeId, DiagnosticRuntimeNode> {\n const { runtime } = snapshot;\n const nodes = new Map<DiagnosticNodeId, DiagnosticRuntimeNode>();\n const buckets: readonly (readonly DiagnosticRuntimeNode[])[] = [\n runtime.conditions,\n runtime.groups,\n runtime.instances,\n runtime.attachments,\n runtime.contributions,\n runtime.ports,\n runtime.hostContracts,\n ];\n\n for (const bucket of buckets) {\n for (const node of bucket) nodes.set(node.id, node);\n }\n\n return nodes;\n}\n\nfunction applyOperations(\n frame: RuntimeGraphFrame,\n nodes: Map<DiagnosticNodeId, DiagnosticRuntimeNode>,\n edges: Map<DiagnosticEdgeId, DiagnosticEdge>,\n): void {\n for (const operation of frame.operations) {\n if (operation.op === 'upsert-node') nodes.set(operation.node.id, operation.node);\n else if (operation.op === 'upsert-edge') edges.set(operation.edge.id, operation.edge);\n else if (operation.op === 'remove-node') nodes.delete(operation.id);\n else edges.delete(operation.id);\n }\n}\n\nfunction rebucket(all: readonly DiagnosticRuntimeNode[], edges: readonly DiagnosticEdge[]): RuntimeStateSnapshot {\n return {\n attachments: ofKind(all, 'attachment'),\n conditions: ofKind(all, 'condition'),\n contributions: ofKind(all, 'contribution'),\n edges,\n groups: ofKind(all, 'condition-group'),\n hostContracts: ofKind(all, 'host-contract'),\n instances: ofKind(all, 'feature-instance'),\n ports: ofKind(all, 'port'),\n };\n}\n\n/**\n * Applies `frames` in order. Frames are assumed contiguous and from this session — validation belongs to\n * `DevtoolsGraphStore`, which is the component that has to defend a live feed.\n */\nfunction foldFrames(snapshot: RuntimeGraphSnapshot, frames: readonly RuntimeGraphFrame[]): RuntimeGraphSnapshot {\n const last = frames.at(-1);\n\n if (last === undefined) return snapshot;\n\n const nodes = collectNodes(snapshot);\n const edges = new Map<DiagnosticEdgeId, DiagnosticEdge>();\n\n for (const edge of snapshot.runtime.edges) edges.set(edge.id, edge);\n\n let activity = snapshot.activity;\n\n for (const frame of frames) {\n applyOperations(frame, nodes, edges);\n activity = frame.activity ?? activity;\n }\n\n const all = [...nodes.values()];\n\n return {\n ...snapshot,\n ...(activity === undefined ? {} : { activity }),\n lastSequence: last.lastSequence,\n runtime: rebucket(all, [...edges.values()]),\n stateRevision: last.nextRevision,\n };\n}\n\nexport { foldFrames };\n"],"names":["fnvOffsetBasis","fnvPrime","canonicalize","value","entry","entries","left","right","key","canonicalJson","digestOf","text","hash","index","sameProtocolValue","ofKind","nodes","kind","picked","node","collectNodes","snapshot","runtime","buckets","bucket","applyOperations","frame","edges","operation","rebucket","all","foldFrames","frames","last","edge","activity"],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * A second, independent way to apply frames: fold them straight onto a `RuntimeGraphSnapshot` instead of into the
3
+ * incremental indexes of `DevtoolsGraphStore`.
4
+ *
5
+ * `docs/devtools.md` §8 asks for exactly this: two implementations of the reducer must
6
+ * reach the same workspace state from one transition corpus. It is also what lets a fixture or a replay answer
7
+ * `getSnapshot()` truthfully at any cursor, rather than only at the beginning and the end.
8
+ */
9
+ import type { RuntimeGraphFrame, RuntimeGraphSnapshot } from './graph.js';
10
+ /**
11
+ * Applies `frames` in order. Frames are assumed contiguous and from this session — validation belongs to
12
+ * `DevtoolsGraphStore`, which is the component that has to defend a live feed.
13
+ */
14
+ declare function foldFrames(snapshot: RuntimeGraphSnapshot, frames: readonly RuntimeGraphFrame[]): RuntimeGraphSnapshot;
15
+ export { foldFrames };
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Incremental indexes over the two projections, per `docs/devtools.md` §6: built `O(N + E)`
3
+ * from a snapshot, then updated per changed record. No recursive walk anywhere on the ingest path — owner chains are
4
+ * followed with a bounded loop so a 20k-node corpus cannot overflow the stack.
5
+ *
6
+ * Condition groups, ports, host contracts, conditions and contributions each hold a plan record and a runtime record
7
+ * under the same id: the plan declares them once, and a restart replaces the runtime record instead of minting a new
8
+ * node. Only a feature is split in two — a `feature` declaration and a `feature-instance` per life — because one
9
+ * declaration outlives many instances (§2.1).
10
+ */
11
+ import type { ApplicationPlanSnapshot, ConditionGroupPlanNode, ConditionGroupRuntimeNode, ConditionPlanNode, DiagnosticNode, DiagnosticPlanNode, DiagnosticRuntimeNode, FeatureInstanceNode, FeaturePlanNode, RuntimeGraphSnapshot } from './graph.js';
12
+ import type { DesiredAxis, DiagnosticConditionId, DiagnosticEdge, DiagnosticEdgeId, DiagnosticLabel, DiagnosticNodeId } from './protocol.js';
13
+ import type { DiagnosticAxes, NodeDisplayState } from './visual-axes.js';
14
+ /**
15
+ * A row of the tree carries structure only. Display is resolved per rendered row through `displayOf`, because
16
+ * structure is immutable inside a session while display changes on every commit: baking it in here would make one
17
+ * state-only frame cost `O(features)` instead of `O(rows actually drawn)` (§6).
18
+ */
19
+ type TreeFeatureRow = Readonly<{
20
+ declaration: FeaturePlanNode;
21
+ /** Stable declaration identity. Resolve its current instance only when reading state or inspecting the row. */
22
+ id: DiagnosticNodeId;
23
+ label: DiagnosticLabel;
24
+ }>;
25
+ /**
26
+ * One lifetime owner and its features. The application itself is an owner — a feature with an empty `when` lives as
27
+ * long as the application (D93) — so it takes the first row and carries no conditions.
28
+ */
29
+ type TreeGroupRow = Readonly<{
30
+ conditions: readonly ConditionPlanNode[];
31
+ features: readonly TreeFeatureRow[];
32
+ group?: ConditionGroupPlanNode;
33
+ id: DiagnosticNodeId;
34
+ label: DiagnosticLabel;
35
+ }>;
36
+ declare class DiagnosticGraphIndex {
37
+ readonly plan: ApplicationPlanSnapshot;
38
+ private readonly conditionsById;
39
+ private readonly edgesById;
40
+ private readonly edgesFrom;
41
+ private readonly edgesTo;
42
+ private readonly instanceByFeature;
43
+ private readonly planNodes;
44
+ private readonly runtimeNodes;
45
+ private treeCache;
46
+ constructor(snapshot: RuntimeGraphSnapshot);
47
+ axesOf(id: DiagnosticNodeId): DiagnosticAxes | undefined;
48
+ /** Edges whose `to` is this node: its blockers, owners and providers (§2.3 orientation). */
49
+ /**
50
+ * The condition a control command addresses, found by the atom the producer publishes. The readable declaration id
51
+ * is its label: a panel that printed the atom would show `a_sb` where the author wrote `a/b` (D200).
52
+ */
53
+ condition(id: DiagnosticConditionId): ConditionPlanNode | undefined;
54
+ /** Topology and selection keep declaration ids; the current runtime instance is a separate projection. */
55
+ currentNodeId(id: DiagnosticNodeId): DiagnosticNodeId;
56
+ /**
57
+ * What the activation machine wants for this node. It comes from the condition group that owns it, or from the
58
+ * application itself for a feature with an empty `when`, which lives as long as the application does.
59
+ */
60
+ desiredOf(id: DiagnosticNodeId): DesiredAxis;
61
+ displayOf(id: DiagnosticNodeId): NodeDisplayState;
62
+ edge(id: DiagnosticEdgeId): DiagnosticEdge | undefined;
63
+ edges(): IterableIterator<DiagnosticEdge>;
64
+ /** The condition group that owns this node's desired axis, found by a bounded owner walk. */
65
+ groupOf(id: DiagnosticNodeId): ConditionGroupRuntimeNode | undefined;
66
+ incoming(id: DiagnosticNodeId): readonly DiagnosticEdge[];
67
+ instanceOf(feature: DiagnosticNodeId): FeatureInstanceNode | undefined;
68
+ node(id: DiagnosticNodeId): DiagnosticNode | undefined;
69
+ /**
70
+ * Every node exactly once, runtime record first. A group, port, host contract, condition or contribution holds a
71
+ * plan and a runtime record under the same id, so the plan pass skips whatever the runtime pass already yielded.
72
+ */
73
+ nodes(): Generator<DiagnosticNode>;
74
+ /** Edges whose `from` is this node: what it owns, provides and unblocks. */
75
+ outgoing(id: DiagnosticNodeId): readonly DiagnosticEdge[];
76
+ planNode(id: DiagnosticNodeId): DiagnosticPlanNode | undefined;
77
+ removeEdge(id: DiagnosticEdgeId): void;
78
+ removeNode(id: DiagnosticNodeId): void;
79
+ runtimeNode(id: DiagnosticNodeId): DiagnosticRuntimeNode | undefined;
80
+ /**
81
+ * Features grouped by the lifetime owner the compiler inferred. Built once and kept: the plan is immutable inside a
82
+ * session (§2.1), so no commit can change which feature belongs to which owner. Only display changes, and display
83
+ * is not in these rows.
84
+ */
85
+ tree(): readonly TreeGroupRow[];
86
+ upsertEdge(edge: DiagnosticEdge): void;
87
+ upsertNode(node: DiagnosticRuntimeNode): void;
88
+ private addPlanNodes;
89
+ private addRuntimeNodes;
90
+ private buildTree;
91
+ private collect;
92
+ private conditionNodes;
93
+ /** A declaration has no runtime record, so its owner is read from the plan instead of walked. */
94
+ private declarationGroup;
95
+ private detachEdge;
96
+ private featureDeclaration;
97
+ /** Only immutable plan structure enters the cache. Instance arrival/replacement cannot stale this row. */
98
+ private featureRow;
99
+ private groupRow;
100
+ /** A feature with an empty `when` owns the desired axis of its descendants through the application (D93). */
101
+ private ownedByApplication;
102
+ }
103
+ export { DiagnosticGraphIndex };
104
+ export type { TreeFeatureRow, TreeGroupRow };
@@ -0,0 +1,77 @@
1
+ /**
2
+ * The reducer of `docs/devtools.md` §3.3 and §7: a snapshot is the source of truth, contiguous
3
+ * frames advance it, and anything that breaks continuity is rejected in favour of a fresh snapshot rather than
4
+ * quietly applied.
5
+ *
6
+ * A frame is only accepted when it continues exactly this session and exactly this revision. A frame from an older
7
+ * HMR session is ignored (its own store is gone); a duplicate, a revision jump or a sequence hole asks the caller to
8
+ * resync, because a partially applied delta is a debugger that lies.
9
+ */
10
+ import type { RuntimeActivitySnapshot } from './activity.js';
11
+ import type { GraphSchemaRevision, RuntimeGraphFrame, RuntimeGraphSnapshot } from './graph.js';
12
+ import { DiagnosticGraphIndex } from './graph-index.js';
13
+ import type { DiagnosticCausalStep } from './node-state.js';
14
+ import type { DiagnosticNodeId, DiagnosticPlanFingerprint, DiagnosticSequence, DiagnosticSessionId, DiagnosticStateRevision, DiagnosticTopologyRevision } from './protocol.js';
15
+ import type { TimelineGapReason, TimelineLimits } from './timeline.js';
16
+ import { DevtoolsTimeline } from './timeline.js';
17
+ type FrameRejection = 'duplicate' | 'malformed' | 'revision-gap' | 'schema-mismatch' | 'sequence-gap';
18
+ type FrameOutcome = Readonly<{
19
+ kind: 'applied';
20
+ }> | Readonly<{
21
+ kind: 'ignored';
22
+ reason: 'stale-session';
23
+ }> | Readonly<{
24
+ kind: 'resync-required';
25
+ reason: FrameRejection;
26
+ }>;
27
+ type DevtoolsStoreStatus = Readonly<{
28
+ droppedFrames: number;
29
+ frames: number;
30
+ lastSequence: DiagnosticSequence;
31
+ planFingerprint: DiagnosticPlanFingerprint;
32
+ /** Rolling average of the reducer cost, against the §6 budget of 4 ms p95 for ≤100 changed records. */
33
+ reducerMs: number;
34
+ schemaRevision: GraphSchemaRevision;
35
+ sessionId: DiagnosticSessionId;
36
+ stateRevision: DiagnosticStateRevision;
37
+ topologyRevision: DiagnosticTopologyRevision;
38
+ }>;
39
+ declare class DevtoolsGraphStore {
40
+ get activity(): RuntimeActivitySnapshot | undefined;
41
+ get graph(): DiagnosticGraphIndex;
42
+ get status(): DevtoolsStoreStatus;
43
+ get timeline(): DevtoolsTimeline;
44
+ private currentActivity;
45
+ private frames;
46
+ private index;
47
+ private lastSequence;
48
+ private readonly listeners;
49
+ private reducerMs;
50
+ private snapshot;
51
+ private stateRevision;
52
+ private readonly steps;
53
+ private readonly timelineLog;
54
+ private version;
55
+ constructor(snapshot: RuntimeGraphSnapshot, limits?: TimelineLimits);
56
+ applyFrame(frame: RuntimeGraphFrame): FrameOutcome;
57
+ /**
58
+ * The causal path behind one node: the newest commits that touched it, the condition group that owns its desired
59
+ * axis, or a node directly adjacent to it, one step per commit. That is what makes the chain read as a cascade — a
60
+ * condition flipping, its group turning active, a feature instance opening, its contribution publishing — rather
61
+ * than a burst of same-commit records that pushes the actual trigger out of the window.
62
+ */
63
+ chainFor(id: DiagnosticNodeId, limit?: number): readonly DiagnosticCausalStep[];
64
+ /** The value `useSyncExternalStore` compares: it changes exactly when the graph, timeline or status changed. */
65
+ getVersion(): number;
66
+ /** A new snapshot after a gap, a session reset or an HMR plan replacement (§3.3). */
67
+ reset(snapshot: RuntimeGraphSnapshot, reason: TimelineGapReason): void;
68
+ subscribe(listener: () => void): () => void;
69
+ private applyOperations;
70
+ private notify;
71
+ private recordSteps;
72
+ private relatedNodes;
73
+ private validateContinuity;
74
+ private validateIdentity;
75
+ }
76
+ export { DevtoolsGraphStore };
77
+ export type { DevtoolsStoreStatus, FrameOutcome, FrameRejection };