@contractspec/lib.surface-runtime 0.2.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 (232) hide show
  1. package/README.md +164 -0
  2. package/dist/adapters/ai-sdk-stub.d.ts +5 -0
  3. package/dist/adapters/ai-sdk-stub.js +13 -0
  4. package/dist/adapters/blocknote-stub.d.ts +6 -0
  5. package/dist/adapters/blocknote-stub.js +31 -0
  6. package/dist/adapters/dnd-kit-adapter.d.ts +13 -0
  7. package/dist/adapters/dnd-kit-adapter.js +44 -0
  8. package/dist/adapters/dnd-kit-stub.d.ts +6 -0
  9. package/dist/adapters/dnd-kit-stub.js +8 -0
  10. package/dist/adapters/floating-ui-stub.d.ts +6 -0
  11. package/dist/adapters/floating-ui-stub.js +19 -0
  12. package/dist/adapters/index.d.ts +11 -0
  13. package/dist/adapters/index.js +176 -0
  14. package/dist/adapters/interfaces.d.ts +75 -0
  15. package/dist/adapters/interfaces.js +1 -0
  16. package/dist/adapters/motion-stub.d.ts +7 -0
  17. package/dist/adapters/motion-stub.js +27 -0
  18. package/dist/adapters/motion-stub.test.d.ts +1 -0
  19. package/dist/adapters/resizable-panels-stub.d.ts +6 -0
  20. package/dist/adapters/resizable-panels-stub.js +46 -0
  21. package/dist/adapters/resizable-panels-stub.test.d.ts +1 -0
  22. package/dist/browser/adapters/ai-sdk-stub.js +12 -0
  23. package/dist/browser/adapters/blocknote-stub.js +30 -0
  24. package/dist/browser/adapters/dnd-kit-adapter.js +43 -0
  25. package/dist/browser/adapters/dnd-kit-stub.js +7 -0
  26. package/dist/browser/adapters/floating-ui-stub.js +18 -0
  27. package/dist/browser/adapters/index.js +175 -0
  28. package/dist/browser/adapters/interfaces.js +0 -0
  29. package/dist/browser/adapters/motion-stub.js +26 -0
  30. package/dist/browser/adapters/resizable-panels-stub.js +45 -0
  31. package/dist/browser/evals/golden-context.js +0 -0
  32. package/dist/browser/evals/golden-harness.js +848 -0
  33. package/dist/browser/examples/pm-workbench.bundle.js +476 -0
  34. package/dist/browser/i18n/catalogs/en.js +71 -0
  35. package/dist/browser/i18n/catalogs/es.js +32 -0
  36. package/dist/browser/i18n/catalogs/fr.js +32 -0
  37. package/dist/browser/i18n/catalogs/index.js +133 -0
  38. package/dist/browser/i18n/index.js +173 -0
  39. package/dist/browser/i18n/keys.js +19 -0
  40. package/dist/browser/i18n/messages.js +143 -0
  41. package/dist/browser/index.js +2466 -0
  42. package/dist/browser/react/BundleProvider.js +47 -0
  43. package/dist/browser/react/BundleRenderer.js +726 -0
  44. package/dist/browser/react/OverlayConflictResolver.js +255 -0
  45. package/dist/browser/react/PatchProposalCard.js +255 -0
  46. package/dist/browser/react/RegionRenderer.js +128 -0
  47. package/dist/browser/react/SlotRenderer.js +118 -0
  48. package/dist/browser/react/WidgetPalette.js +59 -0
  49. package/dist/browser/react/index.js +792 -0
  50. package/dist/browser/runtime/apply-surface-patch.js +322 -0
  51. package/dist/browser/runtime/audit-events.js +137 -0
  52. package/dist/browser/runtime/build-context.js +55 -0
  53. package/dist/browser/runtime/extension-registry.js +58 -0
  54. package/dist/browser/runtime/field-renderer-registry.js +145 -0
  55. package/dist/browser/runtime/index.js +1496 -0
  56. package/dist/browser/runtime/overlay-alignment.js +83 -0
  57. package/dist/browser/runtime/overlay-signer.js +15 -0
  58. package/dist/browser/runtime/override-store.js +52 -0
  59. package/dist/browser/runtime/planner-prompt.js +67 -0
  60. package/dist/browser/runtime/planner-tools.js +77 -0
  61. package/dist/browser/runtime/policy-eval.js +155 -0
  62. package/dist/browser/runtime/preference-adapter.js +67 -0
  63. package/dist/browser/runtime/resolve-bundle.js +767 -0
  64. package/dist/browser/runtime/resolve-preferences.js +59 -0
  65. package/dist/browser/runtime/rollback.js +347 -0
  66. package/dist/browser/runtime/widget-registry.js +36 -0
  67. package/dist/browser/spec/define-module-bundle.js +113 -0
  68. package/dist/browser/spec/index.js +319 -0
  69. package/dist/browser/spec/types.js +0 -0
  70. package/dist/browser/spec/validate-bundle.js +65 -0
  71. package/dist/browser/spec/validate-surface-patch.js +206 -0
  72. package/dist/browser/spec/verification-snapshot-types.js +0 -0
  73. package/dist/browser/telemetry/index.js +20 -0
  74. package/dist/browser/telemetry/surface-metrics.js +20 -0
  75. package/dist/evals/golden-context.d.ts +24 -0
  76. package/dist/evals/golden-context.js +1 -0
  77. package/dist/evals/golden-harness.d.ts +29 -0
  78. package/dist/evals/golden-harness.js +849 -0
  79. package/dist/evals/golden-harness.test.d.ts +1 -0
  80. package/dist/examples/pm-workbench.bundle.d.ts +177 -0
  81. package/dist/examples/pm-workbench.bundle.js +477 -0
  82. package/dist/i18n/catalogs/en.d.ts +1 -0
  83. package/dist/i18n/catalogs/en.js +72 -0
  84. package/dist/i18n/catalogs/es.d.ts +1 -0
  85. package/dist/i18n/catalogs/es.js +33 -0
  86. package/dist/i18n/catalogs/fr.d.ts +1 -0
  87. package/dist/i18n/catalogs/fr.js +33 -0
  88. package/dist/i18n/catalogs/index.d.ts +3 -0
  89. package/dist/i18n/catalogs/index.js +134 -0
  90. package/dist/i18n/index.d.ts +5 -0
  91. package/dist/i18n/index.js +174 -0
  92. package/dist/i18n/keys.d.ts +20 -0
  93. package/dist/i18n/keys.js +20 -0
  94. package/dist/i18n/messages.d.ts +5 -0
  95. package/dist/i18n/messages.js +144 -0
  96. package/dist/index.d.ts +4 -0
  97. package/dist/index.js +2467 -0
  98. package/dist/node/adapters/ai-sdk-stub.js +12 -0
  99. package/dist/node/adapters/blocknote-stub.js +30 -0
  100. package/dist/node/adapters/dnd-kit-adapter.js +43 -0
  101. package/dist/node/adapters/dnd-kit-stub.js +7 -0
  102. package/dist/node/adapters/floating-ui-stub.js +18 -0
  103. package/dist/node/adapters/index.js +175 -0
  104. package/dist/node/adapters/interfaces.js +0 -0
  105. package/dist/node/adapters/motion-stub.js +26 -0
  106. package/dist/node/adapters/resizable-panels-stub.js +45 -0
  107. package/dist/node/evals/golden-context.js +0 -0
  108. package/dist/node/evals/golden-harness.js +848 -0
  109. package/dist/node/examples/pm-workbench.bundle.js +476 -0
  110. package/dist/node/i18n/catalogs/en.js +71 -0
  111. package/dist/node/i18n/catalogs/es.js +32 -0
  112. package/dist/node/i18n/catalogs/fr.js +32 -0
  113. package/dist/node/i18n/catalogs/index.js +133 -0
  114. package/dist/node/i18n/index.js +173 -0
  115. package/dist/node/i18n/keys.js +19 -0
  116. package/dist/node/i18n/messages.js +143 -0
  117. package/dist/node/index.js +2466 -0
  118. package/dist/node/react/BundleProvider.js +47 -0
  119. package/dist/node/react/BundleRenderer.js +726 -0
  120. package/dist/node/react/OverlayConflictResolver.js +255 -0
  121. package/dist/node/react/PatchProposalCard.js +255 -0
  122. package/dist/node/react/RegionRenderer.js +128 -0
  123. package/dist/node/react/SlotRenderer.js +118 -0
  124. package/dist/node/react/WidgetPalette.js +59 -0
  125. package/dist/node/react/index.js +792 -0
  126. package/dist/node/runtime/apply-surface-patch.js +322 -0
  127. package/dist/node/runtime/audit-events.js +137 -0
  128. package/dist/node/runtime/build-context.js +55 -0
  129. package/dist/node/runtime/extension-registry.js +58 -0
  130. package/dist/node/runtime/field-renderer-registry.js +145 -0
  131. package/dist/node/runtime/index.js +1496 -0
  132. package/dist/node/runtime/overlay-alignment.js +83 -0
  133. package/dist/node/runtime/overlay-signer.js +15 -0
  134. package/dist/node/runtime/override-store.js +52 -0
  135. package/dist/node/runtime/planner-prompt.js +67 -0
  136. package/dist/node/runtime/planner-tools.js +77 -0
  137. package/dist/node/runtime/policy-eval.js +155 -0
  138. package/dist/node/runtime/preference-adapter.js +67 -0
  139. package/dist/node/runtime/resolve-bundle.js +767 -0
  140. package/dist/node/runtime/resolve-preferences.js +59 -0
  141. package/dist/node/runtime/rollback.js +347 -0
  142. package/dist/node/runtime/widget-registry.js +36 -0
  143. package/dist/node/spec/define-module-bundle.js +113 -0
  144. package/dist/node/spec/index.js +319 -0
  145. package/dist/node/spec/types.js +0 -0
  146. package/dist/node/spec/validate-bundle.js +65 -0
  147. package/dist/node/spec/validate-surface-patch.js +206 -0
  148. package/dist/node/spec/verification-snapshot-types.js +0 -0
  149. package/dist/node/telemetry/index.js +20 -0
  150. package/dist/node/telemetry/surface-metrics.js +20 -0
  151. package/dist/react/BundleProvider.d.ts +13 -0
  152. package/dist/react/BundleProvider.js +48 -0
  153. package/dist/react/BundleRenderer.d.ts +22 -0
  154. package/dist/react/BundleRenderer.js +727 -0
  155. package/dist/react/OverlayConflictResolver.d.ts +15 -0
  156. package/dist/react/OverlayConflictResolver.js +256 -0
  157. package/dist/react/PatchProposalCard.d.ts +13 -0
  158. package/dist/react/PatchProposalCard.js +256 -0
  159. package/dist/react/RegionRenderer.d.ts +13 -0
  160. package/dist/react/RegionRenderer.js +129 -0
  161. package/dist/react/SlotRenderer.d.ts +13 -0
  162. package/dist/react/SlotRenderer.js +119 -0
  163. package/dist/react/WidgetPalette.d.ts +12 -0
  164. package/dist/react/WidgetPalette.js +60 -0
  165. package/dist/react/index.d.ts +7 -0
  166. package/dist/react/index.js +793 -0
  167. package/dist/runtime/apply-surface-patch.d.ts +15 -0
  168. package/dist/runtime/apply-surface-patch.js +323 -0
  169. package/dist/runtime/apply-surface-patch.test.d.ts +1 -0
  170. package/dist/runtime/audit-events.d.ts +70 -0
  171. package/dist/runtime/audit-events.js +138 -0
  172. package/dist/runtime/audit-events.test.d.ts +1 -0
  173. package/dist/runtime/build-context.d.ts +9 -0
  174. package/dist/runtime/build-context.js +56 -0
  175. package/dist/runtime/extension-registry.d.ts +39 -0
  176. package/dist/runtime/extension-registry.js +59 -0
  177. package/dist/runtime/field-renderer-registry.d.ts +23 -0
  178. package/dist/runtime/field-renderer-registry.js +146 -0
  179. package/dist/runtime/field-renderer-registry.test.d.ts +1 -0
  180. package/dist/runtime/index.d.ts +16 -0
  181. package/dist/runtime/index.js +1497 -0
  182. package/dist/runtime/overlay-alignment.d.ts +49 -0
  183. package/dist/runtime/overlay-alignment.js +84 -0
  184. package/dist/runtime/overlay-alignment.test.d.ts +1 -0
  185. package/dist/runtime/overlay-signer.d.ts +15 -0
  186. package/dist/runtime/overlay-signer.js +16 -0
  187. package/dist/runtime/override-store.d.ts +44 -0
  188. package/dist/runtime/override-store.js +53 -0
  189. package/dist/runtime/override-store.test.d.ts +1 -0
  190. package/dist/runtime/planner-prompt.d.ts +39 -0
  191. package/dist/runtime/planner-prompt.js +68 -0
  192. package/dist/runtime/planner-prompt.test.d.ts +1 -0
  193. package/dist/runtime/planner-tools.d.ts +106 -0
  194. package/dist/runtime/planner-tools.js +78 -0
  195. package/dist/runtime/planner-tools.test.d.ts +1 -0
  196. package/dist/runtime/policy-eval.d.ts +23 -0
  197. package/dist/runtime/policy-eval.js +156 -0
  198. package/dist/runtime/preference-adapter.d.ts +6 -0
  199. package/dist/runtime/preference-adapter.js +68 -0
  200. package/dist/runtime/resolve-bundle.d.ts +68 -0
  201. package/dist/runtime/resolve-bundle.js +768 -0
  202. package/dist/runtime/resolve-bundle.test.d.ts +1 -0
  203. package/dist/runtime/resolve-preferences.d.ts +9 -0
  204. package/dist/runtime/resolve-preferences.js +60 -0
  205. package/dist/runtime/resolve-preferences.test.d.ts +1 -0
  206. package/dist/runtime/rollback.d.ts +21 -0
  207. package/dist/runtime/rollback.js +348 -0
  208. package/dist/runtime/rollback.test.d.ts +1 -0
  209. package/dist/runtime/widget-registry.d.ts +26 -0
  210. package/dist/runtime/widget-registry.js +37 -0
  211. package/dist/runtime/widget-registry.test.d.ts +1 -0
  212. package/dist/spec/define-module-bundle.d.ts +17 -0
  213. package/dist/spec/define-module-bundle.js +114 -0
  214. package/dist/spec/define-module-bundle.test.d.ts +1 -0
  215. package/dist/spec/index.d.ts +5 -0
  216. package/dist/spec/index.js +320 -0
  217. package/dist/spec/types.d.ts +494 -0
  218. package/dist/spec/types.js +1 -0
  219. package/dist/spec/validate-bundle.d.ts +23 -0
  220. package/dist/spec/validate-bundle.js +66 -0
  221. package/dist/spec/validate-bundle.test.d.ts +1 -0
  222. package/dist/spec/validate-surface-patch.d.ts +39 -0
  223. package/dist/spec/validate-surface-patch.js +207 -0
  224. package/dist/spec/validate-surface-patch.test.d.ts +1 -0
  225. package/dist/spec/verification-snapshot-types.d.ts +23 -0
  226. package/dist/spec/verification-snapshot-types.js +1 -0
  227. package/dist/spec/verification-snapshot.test.d.ts +5 -0
  228. package/dist/telemetry/index.d.ts +5 -0
  229. package/dist/telemetry/index.js +21 -0
  230. package/dist/telemetry/surface-metrics.d.ts +17 -0
  231. package/dist/telemetry/surface-metrics.js +21 -0
  232. package/package.json +920 -0
package/README.md ADDED
@@ -0,0 +1,164 @@
1
+ # @contractspec/lib.surface-runtime
2
+
3
+ AI-native surface specs and web runtime for adaptive ContractSpec surfaces.
4
+
5
+ ## Overview
6
+
7
+ This library provides:
8
+
9
+ - **ModuleBundleSpec** — TypeScript types for defining surface contracts (routes, slots, layouts, data recipes)
10
+ - **defineModuleBundle** — Runtime validator for bundle specs
11
+ - **resolveBundle** — Resolves a bundle spec + context into a ResolvedSurfacePlan
12
+ - **resolvePreferenceProfile** — Resolves preferences by scope order (user → … → session)
13
+ - **defaultPreferenceAdapter** — Stub `BundlePreferenceAdapter` (resolve + savePreferencePatch)
14
+ - **applySurfacePatch** — Applies patch operations to a plan
15
+ - **BundleProvider** / **BundleRenderer** — React components for rendering resolved plans
16
+ - **RegionRenderer** / **SlotRenderer** — Layout tree and slot content rendering
17
+ - **Adapters** — BlockNote, dnd-kit, Floating UI, Motion, resizable-panels, AI SDK (stubs)
18
+
19
+ ## Terminology (Glossary)
20
+
21
+ - **Bundle spec** / **Surface spec** (ModuleBundleSpec): A contract type defining a surface (routes, slots, layouts). Not an architectural layer. Use "surface spec" in prose to avoid confusion.
22
+ - **Bundle** (architectural): A domain package in `packages/bundles/` (e.g. bundle.workspace). Contains business logic.
23
+
24
+ ### Preference vs layout vs view vs overlay
25
+
26
+ | Concept | Meaning | Storage |
27
+ |---------|---------|---------|
28
+ | **Preference** | Stable user intent (e.g. `density=dense`, `guidance=hints`). Orthogonal 7-dimension model. | User/workspace/surface scope via `BundlePreferenceAdapter`. |
29
+ | **Layout snapshot** | Concrete arrangement (panel sizes, tab order, collapsed state). | Distinct from preferences; use `persistKey` + overlay-engine for durable layout. |
30
+ | **View** | Data-shaping projection (filters, sort, visible columns). | Often session-scoped or view-state. |
31
+ | **Overlay** | Durable mutation to the rendered contract (patches, field visibility). | `lib.overlay-engine` (OverlaySpec, merger). |
32
+
33
+ Do not merge these into one blob. Preferences drive adaptation; layout snapshots persist geometry; overlays persist structural patches.
34
+
35
+ ### Preference scope resolution order
36
+
37
+ Preferences are merged in this order (later overrides earlier):
38
+
39
+ 1. **user** — Global user defaults
40
+ 2. **workspace-user** — Workspace-specific user defaults
41
+ 3. **bundle** — Bundle-level defaults
42
+ 4. **surface** — Surface-specific overrides
43
+ 5. **entity** — Entity-context overrides
44
+ 6. **session** — Session overrides (highest priority)
45
+
46
+ ### Dimension → runtime behavior (summary)
47
+
48
+ | Dimension | Runtime effect |
49
+ |-----------|----------------|
50
+ | **guidance** | Hints, onboarding, progressive disclosure, assistant prompting style |
51
+ | **density** | Spacing, panel expansion, table vs card layout, side rails |
52
+ | **dataDepth** | Query shape, pagination size, inline expansion, raw payload access |
53
+ | **control** | Visible toggles, advanced inspectors, batch ops, policy editor |
54
+ | **media** | Text vs visual vs voice surfaces, charts vs prose, TTS |
55
+ | **pace** | Animation duration, confirmation depth, skeleton persistence; respects `prefers-reduced-motion` |
56
+ | **narrative** | Order of summary vs evidence, top-down vs bottom-up presentation |
57
+
58
+ Every surface must declare `verification.dimensions` describing how it responds to each dimension. See [docs/verification-matrix.md](./docs/verification-matrix.md) for the verification matrix, review questions, and snapshot coverage.
59
+
60
+ ## Package Strategy
61
+
62
+ ### Hard dependencies
63
+
64
+ - `@contractspec/lib.contracts-spec` — operations, capabilities, policies, presentations
65
+ - `@contractspec/lib.overlay-engine` — typed, auditable overrides
66
+ - `@contractspec/lib.observability` — tracing, metrics, lifecycle instrumentation
67
+ - `zod` — schema validation
68
+
69
+ ### Interoperability (consumes or composes with)
70
+
71
+ - `@contractspec/lib.contracts-runtime-client-react` — feature/form rendering
72
+ - `@contractspec/lib.presentation-runtime-react` — workflow, list state
73
+ - `@contractspec/lib.ai-agent` — agent orchestration (when assistant integration is added)
74
+ - `@contractspec/lib.ai-providers` — planner model selection
75
+ - `@contractspec/lib.metering` — when AI/chat features are used
76
+ - `@contractspec/lib.personalization` — preference dimensions (or surface-runtime-owned until adopted)
77
+
78
+ ### Peer dependencies
79
+
80
+ - `react`, `react-dom` — optional; required only for React renderer
81
+ - UI libs (BlockNote, dnd-kit, Floating UI, Motion, resizable-panels) — optional; added as peers when adapters are implemented
82
+
83
+ ## Usage
84
+
85
+ ```ts
86
+ import { defineModuleBundle } from "@contractspec/lib.surface-runtime/spec";
87
+ import { resolveBundle } from "@contractspec/lib.surface-runtime/runtime";
88
+
89
+ const PmWorkbenchBundle = defineModuleBundle({
90
+ meta: { key: "pm.workbench", version: "0.1.0", title: "PM Workbench" },
91
+ routes: [{ routeId: "pm-issue", path: "/operate/pm/issues/:issueId", defaultSurface: "issue-workbench" }],
92
+ surfaces: {
93
+ "issue-workbench": {
94
+ surfaceId: "issue-workbench",
95
+ kind: "workbench",
96
+ title: "Issue Workbench",
97
+ slots: [...],
98
+ layouts: [...],
99
+ data: [],
100
+ verification: { dimensions: { guidance: "...", density: "...", ... } },
101
+ },
102
+ },
103
+ });
104
+
105
+ const plan = await resolveBundle(PmWorkbenchBundle, ctx);
106
+ ```
107
+
108
+ ## Dependencies
109
+
110
+ See [Package Strategy](#package-strategy) above. Core: contracts-spec, overlay-engine, observability, zod.
111
+
112
+ ## AI Chat Integration (07_ai_native_chat_and_generative_ui)
113
+
114
+ The assistant slot can render chat UI from `@contractspec/module.ai-chat`. Wire as follows:
115
+
116
+ 1. **Assistant slot**: Declare a slot with `role: 'assistant'` in the surface spec. Set `SurfaceAiSpec.assistantSlotId` to that slot's `slotId`.
117
+
118
+ 2. **BundleRenderer + assistant slot**: When `SlotRenderer` receives `slotId === assistantSlotId`, the bundle should render `ChatContainer` from `@contractspec/module.ai-chat` instead of default slot content. Pass `useChat` from the same module for thread state.
119
+
120
+ 3. **Provider config**: Use `createProvider` from `@contractspec/lib.ai-providers` to build the model config. Pass it to the planner (ContractSpecAgent) and to the chat UI (useChat options).
121
+
122
+ 4. **Planner integration**: Use `compilePlannerPrompt` and `proposePatchToolConfig` from this package. Wire the propose-patch tool handler to `validatePatchProposal` before returning. Map planner output to `PlannerResponse` (summary, intent, patchProposals, blockDrafts).
123
+
124
+ 5. **Patch approval flow**: On accept, emit `patch.approved` audit event, call `applySurfacePatch`, then promote to session overlay. On reject, emit `patch.rejected`. Use `PatchApprovalHandler` for the callback signature.
125
+
126
+ ## Policy, Audit, and Rollback (10_policy_audit_and_safety)
127
+
128
+ ### Policy hooks
129
+
130
+ Pass `policy` and `audit` to `resolveBundle` options:
131
+
132
+ - **evaluateNode**: Return `UiPolicyDecision` (allow/deny/redact) per node. Stub: allow all.
133
+ - **redactBinding**: Redact binding values. Stub: pass-through.
134
+ - **evaluatePatchProposal**: Return allow/deny/require-approval for patch ops. Stub: allow.
135
+
136
+ Use `evaluateAndEmitPatchPolicy` before applying patches to gate on policy and emit `policy.denied` when blocked.
137
+
138
+ ### Audit events
139
+
140
+ Implement `BundleAuditEmitter` and pass to `resolveBundle` and patch approval flow. Events: `surface.resolved`, `patch.proposed`, `patch.approved`, `patch.rejected`, `overlay.saved`, `overlay.applied`, `overlay.failed`, `policy.denied`, `policy.redacted`. Use `emitPatchProposed`, `emitPatchApproved`, etc. from `./runtime`.
141
+
142
+ ### Rollback
143
+
144
+ `applySurfacePatch` returns `inverseOps` for reversible ops (insert/remove, replace, set-layout, reveal/hide-field). Store `OverlayApprovalMeta` (forwardOps, inverseOps, actorId, approvedAt) with each approved overlay. Use `rollbackSurfacePatches(plan, approvalStack, count)` to revert last N patches.
145
+
146
+ **Rollback limits**: move-node, resize-panel, set-focus, promote-action require previous state for full inverse; currently return null inverse. Document when storing approval metadata.
147
+
148
+ ## Rollout Status
149
+
150
+ See [ROLLOUT.md](./ROLLOUT.md) for the phased rollout plan, performance budgets, risks, and pilot route (`/operate/pm/issues/:issueId`).
151
+
152
+ **Current:** Phase 0–2 complete (scaffold, resolver, renderer). Phase 3–7 pending.
153
+
154
+ ## Adapter Rule (Lint)
155
+
156
+ **Rule**: No direct third-party UI imports outside `src/adapters/`. BlockNote, dnd-kit, Floating UI, Motion, resizable-panels, AI SDK UI—all must be behind adapter boundaries.
157
+
158
+ **Enforcement**: Run `bun run lint:adapters` to detect violations. Integrate into CI if desired.
159
+
160
+ ## Phase 2 Status
161
+
162
+ Types, defineModuleBundle, resolveBundle, applySurfacePatch, BundleProvider, BundleRenderer, RegionRenderer, SlotRenderer, and adapter stubs are implemented. ResolvedSurfacePlan includes layoutRoot for rendering. Motion tokens map pace to duration/entrance. Panel persistence (persistKey) via localStorage. Full BlockNote/dnd-kit/Floating UI integrations deferred to later phases. Planner prompt compiler, propose-patch tool schema, and patch proposal validator added for AI chat integration.
163
+
164
+ **Observability (09):** Tracing (`traceAsync`), metrics (`resolution_duration_ms`, patch acceptance/rejection, policy denial, surface fallback, missing renderer), structured logging. Golden-context harness for resolver evals with snapshot tests. Performance budget: resolver p95 <100ms (server). See [docs/evals-runbook.md](./docs/evals-runbook.md) and `./telemetry`.
@@ -0,0 +1,5 @@
1
+ /**
2
+ * AI SDK adapter stub. Full planner/chat integration deferred to Phase 5.
3
+ */
4
+ import type { AiSdkBundleAdapter } from './interfaces';
5
+ export declare const aiSdkAdapterStub: AiSdkBundleAdapter;
@@ -0,0 +1,13 @@
1
+ // @bun
2
+ // src/adapters/ai-sdk-stub.ts
3
+ var aiSdkAdapterStub = {
4
+ startThread(_args) {
5
+ return null;
6
+ },
7
+ async requestPatches(_args) {
8
+ return [];
9
+ }
10
+ };
11
+ export {
12
+ aiSdkAdapterStub
13
+ };
@@ -0,0 +1,6 @@
1
+ /**
2
+ * BlockNote adapter stub. Full integration deferred.
3
+ * No direct BlockNote imports in Phase 2.
4
+ */
5
+ import type { BlockNoteBundleAdapter } from './interfaces';
6
+ export declare const blocknoteAdapterStub: BlockNoteBundleAdapter;
@@ -0,0 +1,31 @@
1
+ // @bun
2
+ // src/adapters/blocknote-stub.tsx
3
+ import { jsxDEV } from "react/jsx-dev-runtime";
4
+ var blocknoteAdapterStub = {
5
+ supportsNode(_kind) {
6
+ return false;
7
+ },
8
+ createSchema(_registry) {
9
+ return {};
10
+ },
11
+ renderNode(node, _ctx) {
12
+ return /* @__PURE__ */ jsxDEV("div", {
13
+ "data-blocknote-stub": true,
14
+ "data-node-id": node.nodeId,
15
+ children: node.title ?? node.kind
16
+ }, undefined, false, undefined, this);
17
+ },
18
+ async serialize(node) {
19
+ return { nodeId: node.nodeId, kind: node.kind };
20
+ },
21
+ async deserialize(input) {
22
+ const o = input;
23
+ return {
24
+ nodeId: o?.nodeId ?? "stub",
25
+ kind: o?.kind ?? "custom-widget"
26
+ };
27
+ }
28
+ };
29
+ export {
30
+ blocknoteAdapterStub
31
+ };
@@ -0,0 +1,13 @@
1
+ /**
2
+ * dnd-kit adapter for customization mode. Wraps content in DndContext.
3
+ * Requires @dnd-kit/core (optional peer). Falls back to stub when not installed.
4
+ */
5
+ import React from 'react';
6
+ import type { SurfacePatchOp } from '../spec/types';
7
+ import type { DragDropBundleAdapter } from './interfaces';
8
+ export interface DndWrapperProps {
9
+ children: React.ReactNode;
10
+ mutableSlots: string[];
11
+ onPatch: (ops: SurfacePatchOp[]) => void;
12
+ }
13
+ export declare const dndKitAdapter: DragDropBundleAdapter;
@@ -0,0 +1,44 @@
1
+ // @bun
2
+ // src/adapters/dnd-kit-adapter.tsx
3
+ import {
4
+ DndContext,
5
+ PointerSensor,
6
+ useSensor,
7
+ useSensors
8
+ } from "@dnd-kit/core";
9
+ import { jsxDEV } from "react/jsx-dev-runtime";
10
+ "use client";
11
+ var noop = () => {
12
+ return;
13
+ };
14
+ var _onPatch = noop;
15
+ function handleDragEnd(event) {
16
+ const { active, over } = event;
17
+ if (!over)
18
+ return;
19
+ const fromId = active.id;
20
+ const toSlotId = over.id;
21
+ if (fromId && toSlotId) {
22
+ _onPatch([{ op: "move-node", nodeId: fromId, toSlotId }]);
23
+ }
24
+ }
25
+ function DndWrapperComponent({ children, onPatch }) {
26
+ _onPatch = onPatch;
27
+ const sensors = useSensors(useSensor(PointerSensor, {
28
+ activationConstraint: { distance: 8 }
29
+ }));
30
+ return /* @__PURE__ */ jsxDEV(DndContext, {
31
+ sensors,
32
+ onDragEnd: handleDragEnd,
33
+ children
34
+ }, undefined, false, undefined, this);
35
+ }
36
+ var dndKitAdapter = {
37
+ enableSurfaceEditing(args) {
38
+ _onPatch = args.onPatch;
39
+ },
40
+ DndWrapper: DndWrapperComponent
41
+ };
42
+ export {
43
+ dndKitAdapter
44
+ };
@@ -0,0 +1,6 @@
1
+ /**
2
+ * dnd-kit adapter stub. Full customization mode deferred to Phase 6.
3
+ * No direct dnd-kit imports in Phase 2.
4
+ */
5
+ import type { DragDropBundleAdapter } from './interfaces';
6
+ export declare const dndKitAdapterStub: DragDropBundleAdapter;
@@ -0,0 +1,8 @@
1
+ // @bun
2
+ // src/adapters/dnd-kit-stub.ts
3
+ var dndKitAdapterStub = {
4
+ enableSurfaceEditing(_args) {}
5
+ };
6
+ export {
7
+ dndKitAdapterStub
8
+ };
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Floating UI adapter stub. Full implementation deferred.
3
+ * No direct Floating UI imports in Phase 2.
4
+ */
5
+ import type { FloatingBundleAdapter } from './interfaces';
6
+ export declare const floatingUiAdapterStub: FloatingBundleAdapter;
@@ -0,0 +1,19 @@
1
+ // @bun
2
+ // src/adapters/floating-ui-stub.tsx
3
+ import { jsxDEV } from "react/jsx-dev-runtime";
4
+ var floatingUiAdapterStub = {
5
+ renderAnchoredMenu({ anchorId, items }) {
6
+ return /* @__PURE__ */ jsxDEV("div", {
7
+ "data-floating-stub": true,
8
+ "data-anchor-id": anchorId,
9
+ role: "menu",
10
+ children: items.map((a) => /* @__PURE__ */ jsxDEV("div", {
11
+ role: "menuitem",
12
+ children: a.title
13
+ }, a.actionId, false, undefined, this))
14
+ }, undefined, false, undefined, this);
15
+ }
16
+ };
17
+ export {
18
+ floatingUiAdapterStub
19
+ };
@@ -0,0 +1,11 @@
1
+ /**
2
+ * UI library adapters. No direct third-party imports outside this directory.
3
+ */
4
+ export type { AiSdkBundleAdapter, BlockNoteBundleAdapter, DragDropBundleAdapter, FieldRendererRegistry, FloatingBundleAdapter, MotionBundleAdapter, MotionTokens, PanelLayoutAdapter, RenderContext, RenderRegionFn, } from './interfaces';
5
+ export { aiSdkAdapterStub } from './ai-sdk-stub';
6
+ export { blocknoteAdapterStub } from './blocknote-stub';
7
+ export { dndKitAdapterStub } from './dnd-kit-stub';
8
+ export { dndKitAdapter } from './dnd-kit-adapter';
9
+ export { floatingUiAdapterStub } from './floating-ui-stub';
10
+ export { motionAdapterStub } from './motion-stub';
11
+ export { resizablePanelsAdapterStub } from './resizable-panels-stub';
@@ -0,0 +1,176 @@
1
+ // @bun
2
+ // src/adapters/ai-sdk-stub.ts
3
+ var aiSdkAdapterStub = {
4
+ startThread(_args) {
5
+ return null;
6
+ },
7
+ async requestPatches(_args) {
8
+ return [];
9
+ }
10
+ };
11
+
12
+ // src/adapters/blocknote-stub.tsx
13
+ import { jsxDEV } from "react/jsx-dev-runtime";
14
+ var blocknoteAdapterStub = {
15
+ supportsNode(_kind) {
16
+ return false;
17
+ },
18
+ createSchema(_registry) {
19
+ return {};
20
+ },
21
+ renderNode(node, _ctx) {
22
+ return /* @__PURE__ */ jsxDEV("div", {
23
+ "data-blocknote-stub": true,
24
+ "data-node-id": node.nodeId,
25
+ children: node.title ?? node.kind
26
+ }, undefined, false, undefined, this);
27
+ },
28
+ async serialize(node) {
29
+ return { nodeId: node.nodeId, kind: node.kind };
30
+ },
31
+ async deserialize(input) {
32
+ const o = input;
33
+ return {
34
+ nodeId: o?.nodeId ?? "stub",
35
+ kind: o?.kind ?? "custom-widget"
36
+ };
37
+ }
38
+ };
39
+
40
+ // src/adapters/dnd-kit-adapter.tsx
41
+ import {
42
+ DndContext,
43
+ PointerSensor,
44
+ useSensor,
45
+ useSensors
46
+ } from "@dnd-kit/core";
47
+ import { jsxDEV as jsxDEV2 } from "react/jsx-dev-runtime";
48
+ "use client";
49
+ var noop = () => {
50
+ return;
51
+ };
52
+ var _onPatch = noop;
53
+ function handleDragEnd(event) {
54
+ const { active, over } = event;
55
+ if (!over)
56
+ return;
57
+ const fromId = active.id;
58
+ const toSlotId = over.id;
59
+ if (fromId && toSlotId) {
60
+ _onPatch([{ op: "move-node", nodeId: fromId, toSlotId }]);
61
+ }
62
+ }
63
+ function DndWrapperComponent({ children, onPatch }) {
64
+ _onPatch = onPatch;
65
+ const sensors = useSensors(useSensor(PointerSensor, {
66
+ activationConstraint: { distance: 8 }
67
+ }));
68
+ return /* @__PURE__ */ jsxDEV2(DndContext, {
69
+ sensors,
70
+ onDragEnd: handleDragEnd,
71
+ children
72
+ }, undefined, false, undefined, this);
73
+ }
74
+ var dndKitAdapter = {
75
+ enableSurfaceEditing(args) {
76
+ _onPatch = args.onPatch;
77
+ },
78
+ DndWrapper: DndWrapperComponent
79
+ };
80
+
81
+ // src/adapters/dnd-kit-stub.ts
82
+ var dndKitAdapterStub = {
83
+ enableSurfaceEditing(_args) {}
84
+ };
85
+
86
+ // src/adapters/floating-ui-stub.tsx
87
+ import { jsxDEV as jsxDEV3 } from "react/jsx-dev-runtime";
88
+ var floatingUiAdapterStub = {
89
+ renderAnchoredMenu({ anchorId, items }) {
90
+ return /* @__PURE__ */ jsxDEV3("div", {
91
+ "data-floating-stub": true,
92
+ "data-anchor-id": anchorId,
93
+ role: "menu",
94
+ children: items.map((a) => /* @__PURE__ */ jsxDEV3("div", {
95
+ role: "menuitem",
96
+ children: a.title
97
+ }, a.actionId, false, undefined, this))
98
+ }, undefined, false, undefined, this);
99
+ }
100
+ };
101
+
102
+ // src/adapters/motion-stub.ts
103
+ var PACE_TOKENS = {
104
+ deliberate: {
105
+ durationMs: 300,
106
+ enableEntrance: true,
107
+ layout: true
108
+ },
109
+ balanced: {
110
+ durationMs: 150,
111
+ enableEntrance: true,
112
+ layout: true
113
+ },
114
+ rapid: {
115
+ durationMs: 50,
116
+ enableEntrance: false,
117
+ layout: false
118
+ }
119
+ };
120
+ var motionAdapterStub = {
121
+ getTokens(pace) {
122
+ return PACE_TOKENS[pace] ?? PACE_TOKENS.balanced;
123
+ }
124
+ };
125
+
126
+ // src/adapters/resizable-panels-stub.tsx
127
+ import { jsxDEV as jsxDEV4 } from "react/jsx-dev-runtime";
128
+ var LAYOUT_STORAGE_KEY = "surface-runtime:panel-layout:";
129
+ var resizablePanelsAdapterStub = {
130
+ renderPanelGroup(region, ctx, renderChild) {
131
+ const direction = region.direction === "horizontal" ? "row" : "column";
132
+ return /* @__PURE__ */ jsxDEV4("div", {
133
+ "data-panel-group": true,
134
+ "data-persist-key": region.persistKey,
135
+ style: {
136
+ display: "flex",
137
+ flexDirection: direction,
138
+ flex: 1,
139
+ minHeight: 0
140
+ },
141
+ children: region.children.map((child, i) => /* @__PURE__ */ jsxDEV4("div", {
142
+ style: { flex: 1, minWidth: 0, minHeight: 0 },
143
+ children: renderChild(child, ctx)
144
+ }, i, false, undefined, this))
145
+ }, undefined, false, undefined, this);
146
+ },
147
+ async restoreLayout(persistKey) {
148
+ if (typeof localStorage === "undefined")
149
+ return null;
150
+ try {
151
+ const raw = localStorage.getItem(LAYOUT_STORAGE_KEY + persistKey);
152
+ if (!raw)
153
+ return null;
154
+ const parsed = JSON.parse(raw);
155
+ return Array.isArray(parsed) ? parsed : null;
156
+ } catch {
157
+ return null;
158
+ }
159
+ },
160
+ async saveLayout(persistKey, sizes) {
161
+ if (typeof localStorage === "undefined")
162
+ return;
163
+ try {
164
+ localStorage.setItem(LAYOUT_STORAGE_KEY + persistKey, JSON.stringify(sizes));
165
+ } catch {}
166
+ }
167
+ };
168
+ export {
169
+ resizablePanelsAdapterStub,
170
+ motionAdapterStub,
171
+ floatingUiAdapterStub,
172
+ dndKitAdapterStub,
173
+ dndKitAdapter,
174
+ blocknoteAdapterStub,
175
+ aiSdkAdapterStub
176
+ };
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Adapter interfaces for third-party UI libraries.
3
+ * No direct imports of BlockNote, dnd-kit, Floating UI, Motion, etc. outside adapters/.
4
+ * Aligns with 06_ui_composition_and_adapters.md.
5
+ */
6
+ import type { ComponentType, ReactElement } from 'react';
7
+ import type { FieldRendererRegistry } from '../runtime/field-renderer-registry';
8
+ import type { ResolvedSurfacePlan } from '../runtime/resolve-bundle';
9
+ import type { ActionSpec, BundleNodeKind, PanelGroupRegion, PreferenceDimensions, RegionNode, SurfaceNode, SurfacePatchOp, SurfacePatchProposal } from '../spec/types';
10
+ /** Context passed to renderers. */
11
+ export interface RenderContext {
12
+ plan: ResolvedSurfacePlan;
13
+ bindings: Record<string, unknown>;
14
+ preferences: PreferenceDimensions;
15
+ }
16
+ /** Field renderer registry for entity surfaces. See runtime/field-renderer-registry. */
17
+ export type { FieldRendererRegistry };
18
+ /** BlockNote adapter for rich document surfaces. Stub in Phase 2. */
19
+ export interface BlockNoteBundleAdapter {
20
+ supportsNode(kind: BundleNodeKind): boolean;
21
+ createSchema(registry: FieldRendererRegistry): unknown;
22
+ renderNode(node: SurfaceNode, ctx: RenderContext): ReactElement;
23
+ serialize(node: SurfaceNode): Promise<unknown>;
24
+ deserialize(input: unknown): Promise<SurfaceNode>;
25
+ }
26
+ /** dnd-kit adapter for drag-and-drop. Stub in Phase 2; full impl in Phase 6. */
27
+ export interface DragDropBundleAdapter {
28
+ enableSurfaceEditing(args: {
29
+ mutableSlots: string[];
30
+ onPatch: (ops: SurfacePatchOp[]) => void;
31
+ }): void;
32
+ /** When provided, wrap children for DnD when customization mode is on. */
33
+ DndWrapper?: ComponentType<{
34
+ children: React.ReactNode;
35
+ mutableSlots: string[];
36
+ onPatch: (ops: SurfacePatchOp[]) => void;
37
+ }>;
38
+ }
39
+ /** Floating UI adapter for anchored menus and popovers. Stub in Phase 2. */
40
+ export interface FloatingBundleAdapter {
41
+ renderAnchoredMenu(args: {
42
+ anchorId: string;
43
+ items: ActionSpec[];
44
+ }): ReactElement;
45
+ }
46
+ /** Motion tokens derived from pace preference. */
47
+ export interface MotionTokens {
48
+ durationMs: number;
49
+ enableEntrance: boolean;
50
+ layout: boolean;
51
+ }
52
+ /** Motion adapter for layout transitions. Maps pace to tokens. */
53
+ export interface MotionBundleAdapter {
54
+ getTokens(pace: PreferenceDimensions['pace']): MotionTokens;
55
+ }
56
+ /** Renders a child region. Passed to panel adapter to avoid adapter→renderer cycle. */
57
+ export type RenderRegionFn = (region: RegionNode, ctx: RenderContext) => ReactElement;
58
+ /** Panels adapter for split-pane workbench layouts. */
59
+ export interface PanelLayoutAdapter {
60
+ renderPanelGroup(region: PanelGroupRegion, ctx: RenderContext, renderChild: RenderRegionFn): ReactElement;
61
+ restoreLayout(persistKey: string): Promise<number[] | null>;
62
+ saveLayout(persistKey: string, sizes: number[]): Promise<void>;
63
+ }
64
+ /** AI SDK adapter for planner/chat integration. Stub in Phase 2. */
65
+ export interface AiSdkBundleAdapter {
66
+ startThread(args: {
67
+ plannerId: string;
68
+ systemPrompt: string;
69
+ tools: Record<string, unknown>;
70
+ }): unknown;
71
+ requestPatches(args: {
72
+ currentPlan: ResolvedSurfacePlan;
73
+ userMessage: string;
74
+ }): Promise<SurfacePatchProposal[]>;
75
+ }
@@ -0,0 +1 @@
1
+ // @bun
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Motion adapter stub. Maps pace preference to motion tokens.
3
+ * Aligns with 01_preference_dimensions.md animation mapping.
4
+ * No direct Motion imports in Phase 2 — tokens drive CSS or future Motion usage.
5
+ */
6
+ import type { MotionBundleAdapter } from './interfaces';
7
+ export declare const motionAdapterStub: MotionBundleAdapter;
@@ -0,0 +1,27 @@
1
+ // @bun
2
+ // src/adapters/motion-stub.ts
3
+ var PACE_TOKENS = {
4
+ deliberate: {
5
+ durationMs: 300,
6
+ enableEntrance: true,
7
+ layout: true
8
+ },
9
+ balanced: {
10
+ durationMs: 150,
11
+ enableEntrance: true,
12
+ layout: true
13
+ },
14
+ rapid: {
15
+ durationMs: 50,
16
+ enableEntrance: false,
17
+ layout: false
18
+ }
19
+ };
20
+ var motionAdapterStub = {
21
+ getTokens(pace) {
22
+ return PACE_TOKENS[pace] ?? PACE_TOKENS.balanced;
23
+ }
24
+ };
25
+ export {
26
+ motionAdapterStub
27
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Resizable panels adapter stub.
3
+ * Renders panel groups with flex layout. Full react-resizable-panels integration deferred.
4
+ */
5
+ import type { PanelLayoutAdapter } from './interfaces';
6
+ export declare const resizablePanelsAdapterStub: PanelLayoutAdapter;