2sagaco48 1.0.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 (365) hide show
  1. package/diagnostics.mjs +141 -0
  2. package/dist/compute.worker-j5ahIpF-.js +1 -0
  3. package/dist/graph.worker-DG6iGCB9.js +1 -0
  4. package/dist/index.html +2314 -0
  5. package/flatten-guide.md +248 -0
  6. package/index.html +12 -0
  7. package/package.json +38 -0
  8. package/src/App.orig.tsx +204 -0
  9. package/src/App.orig1.tsx +27 -0
  10. package/src/App.orig2.tsx +25 -0
  11. package/src/App.tsx +3 -0
  12. package/src/BaseApp.tsx +18 -0
  13. package/src/INTEGRATION_LEDGER.md +22 -0
  14. package/src/PERSIST_CANARY.orig.txt +1 -0
  15. package/src/PERSIST_CANARY.txt +1 -0
  16. package/src/REGRESSION_LEDGER.md +651 -0
  17. package/src/TEMPLATE_PIPELINE_AUDIT.md +208 -0
  18. package/src/WILLIAMS_PERSONA_GUIDE.md +62 -0
  19. package/src/chat/VeritasChat.tsx +69 -0
  20. package/src/chat/synthesis.ts +31 -0
  21. package/src/chat/tier.ts +45 -0
  22. package/src/components/AdaptersPage.orig.tsx +99 -0
  23. package/src/components/AdaptersPage.tsx +3 -0
  24. package/src/components/AdversarialPanel.tsx +136 -0
  25. package/src/components/AnchorBaselinePanel.tsx +75 -0
  26. package/src/components/AnswerPanel.tsx +60 -0
  27. package/src/components/ArtifactPanel.tsx +43 -0
  28. package/src/components/ChatApp.orig.tsx +1582 -0
  29. package/src/components/ChatApp.orig1.tsx +722 -0
  30. package/src/components/ChatApp.tsx +95 -0
  31. package/src/components/ChatAugmentPanels.tsx +73 -0
  32. package/src/components/CitationLedgerPanel.tsx +313 -0
  33. package/src/components/ComputeSandboxPanel.tsx +130 -0
  34. package/src/components/ConfigPanel.tsx +110 -0
  35. package/src/components/ControlPlanePage.tsx +195 -0
  36. package/src/components/CreativeTreeLifePage.tsx +234 -0
  37. package/src/components/CreativeTreeOfLifePanel.tsx +149 -0
  38. package/src/components/DebugTracePanel.tsx +137 -0
  39. package/src/components/DeepReasoningTrace.tsx +48 -0
  40. package/src/components/EntitySheetPanel.tsx +51 -0
  41. package/src/components/FailureModesPanel.tsx +104 -0
  42. package/src/components/GBSDashboard.tsx +239 -0
  43. package/src/components/GraphView.tsx +116 -0
  44. package/src/components/HUD.tsx +125 -0
  45. package/src/components/HypothesisPanel.tsx +67 -0
  46. package/src/components/InnovationGenomeEngine.tsx +792 -0
  47. package/src/components/InnovationPersonaGuide.tsx +348 -0
  48. package/src/components/InnovationPersonaPanel.tsx +289 -0
  49. package/src/components/LiveResourceHUD.tsx +109 -0
  50. package/src/components/LongWriterPanel.tsx +112 -0
  51. package/src/components/MainPipelineV10Bridge.tsx +72 -0
  52. package/src/components/MarkdownLite.tsx +63 -0
  53. package/src/components/MemoryInspector.tsx +38 -0
  54. package/src/components/MemoryMonitor.tsx +52 -0
  55. package/src/components/MemoryStressPanel.tsx +176 -0
  56. package/src/components/ModulesPage.tsx +171 -0
  57. package/src/components/PipelineDebugConsole.orig.tsx +1856 -0
  58. package/src/components/PipelineDebugConsole.tsx +3 -0
  59. package/src/components/PreFlightHUD.tsx +67 -0
  60. package/src/components/PrismaFetchTracePanel.tsx +87 -0
  61. package/src/components/ReportOSPanel.tsx +165 -0
  62. package/src/components/ResourceEstimatorPage.tsx +327 -0
  63. package/src/components/RichText.tsx +181 -0
  64. package/src/components/SagaOsPanel.tsx +605 -0
  65. package/src/components/SagaOsReference.tsx +421 -0
  66. package/src/components/SagaOsV2Panel.tsx +594 -0
  67. package/src/components/SagaStyleTab.tsx +382 -0
  68. package/src/components/SageBlueprintPanel.tsx +416 -0
  69. package/src/components/SharedChatInput.tsx +77 -0
  70. package/src/components/StatFinancePanel.tsx +190 -0
  71. package/src/components/StylePersonaPanel.tsx +128 -0
  72. package/src/components/TemplatesPage.tsx +300 -0
  73. package/src/components/TestPanel.tsx +63 -0
  74. package/src/components/TraceLog.tsx +89 -0
  75. package/src/components/V15CalibrationAugment.tsx +1134 -0
  76. package/src/components/V15CalibrationDialog.tsx +1871 -0
  77. package/src/components/V15Overlay.orig.tsx +120 -0
  78. package/src/components/V15Overlay.tsx +4 -0
  79. package/src/components/V15OverlayWrapper.tsx +362 -0
  80. package/src/components/V15Toggle.tsx +34 -0
  81. package/src/console/DiagnosticRecoveryPanel.tsx +358 -0
  82. package/src/console/EngineeringConsole.tsx +103 -0
  83. package/src/console/FictionSystemPanels.tsx +882 -0
  84. package/src/console/InspectorPanels.tsx +254 -0
  85. package/src/console/ManuscriptGraderPanel.tsx +182 -0
  86. package/src/console/RecursiveReviserPanel.tsx +245 -0
  87. package/src/console/SeedControlPanel.tsx +135 -0
  88. package/src/console/StoryControllerPanel.tsx +443 -0
  89. package/src/console/engineBridge.ts +258 -0
  90. package/src/console/gemini.ts +106 -0
  91. package/src/console/grading.ts +250 -0
  92. package/src/console/util.ts +45 -0
  93. package/src/fiction/continuity.ts +372 -0
  94. package/src/fiction/crash-protection.ts +111 -0
  95. package/src/fiction/diagnostics.ts +208 -0
  96. package/src/fiction/diff.ts +74 -0
  97. package/src/fiction/entity-state-machine.ts +214 -0
  98. package/src/fiction/nive-engine.ts +404 -0
  99. package/src/fiction/pipeline-selftest.ts +132 -0
  100. package/src/fiction/seed-control.ts +193 -0
  101. package/src/fiction/story-engine.ts +326 -0
  102. package/src/fiction/types.ts +68 -0
  103. package/src/flatten-guide.md +379 -0
  104. package/src/index.css +14 -0
  105. package/src/index.orig.css +13 -0
  106. package/src/lib/academic-sources.base.ts +188 -0
  107. package/src/lib/academic-sources.orig.ts +28 -0
  108. package/src/lib/academic-sources.ts +10 -0
  109. package/src/lib/advanced-math.ts +119 -0
  110. package/src/lib/adversarial-engine.base.ts +409 -0
  111. package/src/lib/adversarial-engine.ts +82 -0
  112. package/src/lib/ai-revision.ts +623 -0
  113. package/src/lib/app-state.tsx +426 -0
  114. package/src/lib/artifact-registry.ts +97 -0
  115. package/src/lib/artifacts.ts +180 -0
  116. package/src/lib/atlas-dr.ts +167 -0
  117. package/src/lib/browser-mtls.ts +127 -0
  118. package/src/lib/browser-queue.ts +221 -0
  119. package/src/lib/browser-search-scraper.base.ts +382 -0
  120. package/src/lib/browser-search-scraper.orig.ts +90 -0
  121. package/src/lib/browser-search-scraper.ts +10 -0
  122. package/src/lib/calc-interceptor.ts +82 -0
  123. package/src/lib/citation-lane-tap.ts +78 -0
  124. package/src/lib/citation-ledger-store.ts +369 -0
  125. package/src/lib/citation-ledger.ts +203 -0
  126. package/src/lib/compute-sandbox.ts +215 -0
  127. package/src/lib/compute.worker.ts +153 -0
  128. package/src/lib/connectors/gemini.ts +120 -0
  129. package/src/lib/connectors/jina.ts +233 -0
  130. package/src/lib/connectors/marketdata.ts +51 -0
  131. package/src/lib/connectors/prismafetch.base.ts +175 -0
  132. package/src/lib/connectors/prismafetch.ts +1 -0
  133. package/src/lib/connectors/serpapi.ts +65 -0
  134. package/src/lib/connectors/wikidata.ts +44 -0
  135. package/src/lib/constraints.ts +331 -0
  136. package/src/lib/continuation-detector.base.ts +153 -0
  137. package/src/lib/continuation-detector.ts +48 -0
  138. package/src/lib/contradraft.ts +168 -0
  139. package/src/lib/cors-proxy.ts +120 -0
  140. package/src/lib/coverage.ts +81 -0
  141. package/src/lib/debug/architecture-prescription.ts +556 -0
  142. package/src/lib/debug/covea-repair.ts +543 -0
  143. package/src/lib/debug/helios-ground.ts +319 -0
  144. package/src/lib/debug/intent-decomposer.ts +281 -0
  145. package/src/lib/debug/intent-lattice.ts +349 -0
  146. package/src/lib/debug/pipeline-diagnosis.ts +1149 -0
  147. package/src/lib/debug/pipeline-trace-bus.ts +424 -0
  148. package/src/lib/debug/prompt-forge.ts +678 -0
  149. package/src/lib/debug/repair-sites.ts +513 -0
  150. package/src/lib/debug/research-phase.ts +266 -0
  151. package/src/lib/debug/retrieval-context.ts +76 -0
  152. package/src/lib/debug/retrieval-hardener.ts +142 -0
  153. package/src/lib/debug/scraper-debug-runner.ts +576 -0
  154. package/src/lib/debug/scraper-forensics.ts +418 -0
  155. package/src/lib/debug/scraper-lane-roadmap.ts +136 -0
  156. package/src/lib/debug/self-test.ts +415 -0
  157. package/src/lib/debug/step-attribution.ts +211 -0
  158. package/src/lib/debug/template-rubric.ts +301 -0
  159. package/src/lib/debug/unified-innovation.ts +126 -0
  160. package/src/lib/debug/veritas-hybrid-scraper.ts +335 -0
  161. package/src/lib/defense-registry.ts +104 -0
  162. package/src/lib/defenses.ts +162 -0
  163. package/src/lib/deterministic-citation-ledger.ts +174 -0
  164. package/src/lib/elo-registry.base.ts +117 -0
  165. package/src/lib/elo-registry.ts +1 -0
  166. package/src/lib/entity-resolver.ts +291 -0
  167. package/src/lib/failure-modes.ts +267 -0
  168. package/src/lib/feature-registry.ts +69 -0
  169. package/src/lib/flaw-registry.ts +143 -0
  170. package/src/lib/flaws/_template.ts +52 -0
  171. package/src/lib/flaws/builtins.ts +63 -0
  172. package/src/lib/flaws/finance.ts +169 -0
  173. package/src/lib/flaws/fixers.ts +10 -0
  174. package/src/lib/flaws/index.ts +32 -0
  175. package/src/lib/flaws/legal.ts +157 -0
  176. package/src/lib/flaws/medical.ts +188 -0
  177. package/src/lib/flaws/original-defenses-pack.ts +81 -0
  178. package/src/lib/flaws/sample-declarative-pack.json +38 -0
  179. package/src/lib/flaws/selftest.ts +201 -0
  180. package/src/lib/flaws/software-extended.ts +125 -0
  181. package/src/lib/flaws/software-rn-webgl.ts +182 -0
  182. package/src/lib/flaws/statistics-advanced.ts +46 -0
  183. package/src/lib/flaws/statistics.ts +61 -0
  184. package/src/lib/gbse/config.ts +20 -0
  185. package/src/lib/gbse/engine.ts +226 -0
  186. package/src/lib/gbse/graph.ts +118 -0
  187. package/src/lib/gbse/graph.worker.ts +21 -0
  188. package/src/lib/gbse/tests.ts +117 -0
  189. package/src/lib/gbse/tiers.ts +65 -0
  190. package/src/lib/gbse/types.ts +94 -0
  191. package/src/lib/innovation-genome-engine-v2.orig.ts +1001 -0
  192. package/src/lib/innovation-genome-engine-v2.ts +3 -0
  193. package/src/lib/innovation-genome-engine.orig.ts +330 -0
  194. package/src/lib/innovation-genome-engine.ts +3 -0
  195. package/src/lib/innovation-genome-v10.orig.ts +314 -0
  196. package/src/lib/innovation-genome-v10.ts +2 -0
  197. package/src/lib/innovation-genome-v3.orig.ts +2015 -0
  198. package/src/lib/innovation-genome-v3.ts +2 -0
  199. package/src/lib/innovation-genome-v4.orig.ts +1959 -0
  200. package/src/lib/innovation-genome-v4.ts +2 -0
  201. package/src/lib/innovation-genome-v5.orig.ts +1210 -0
  202. package/src/lib/innovation-genome-v5.ts +2 -0
  203. package/src/lib/innovation-genome-v7.orig.ts +2549 -0
  204. package/src/lib/innovation-genome-v7.ts +2 -0
  205. package/src/lib/innovation-genome-v8.orig.ts +1485 -0
  206. package/src/lib/innovation-genome-v8.ts +2 -0
  207. package/src/lib/innovation-genome-v9.orig.ts +643 -0
  208. package/src/lib/innovation-genome-v9.ts +2 -0
  209. package/src/lib/jina.base.ts +293 -0
  210. package/src/lib/jina.ts +1 -0
  211. package/src/lib/live-telemetry.ts +61 -0
  212. package/src/lib/longwriter.ts +84 -0
  213. package/src/lib/manuscript-grade-v2.ts +298 -0
  214. package/src/lib/memory-governor.ts +173 -0
  215. package/src/lib/memory-stress-tests.ts +248 -0
  216. package/src/lib/model-intelligence.ts +48 -0
  217. package/src/lib/model-rotator.ts +75 -0
  218. package/src/lib/models.orig.ts +513 -0
  219. package/src/lib/models.ts +151 -0
  220. package/src/lib/n-deep.base.ts +592 -0
  221. package/src/lib/n-deep.ts +51 -0
  222. package/src/lib/nih-simulator.ts +56 -0
  223. package/src/lib/nih-vulnerability-fixes.ts +460 -0
  224. package/src/lib/omega-templates.base.ts +294 -0
  225. package/src/lib/omega-templates.ts +225 -0
  226. package/src/lib/omni-nexus.ts +64 -0
  227. package/src/lib/oracle-adapters.ts +67 -0
  228. package/src/lib/oracle-registry.ts +163 -0
  229. package/src/lib/orchestrator.ts +357 -0
  230. package/src/lib/output-boundary.ts +235 -0
  231. package/src/lib/overrides/vite-native-chaos-harness.ts +154 -0
  232. package/src/lib/overrides/vite-native-contract-plane.ts +245 -0
  233. package/src/lib/overrides/vite-native-doctor-plane.ts +208 -0
  234. package/src/lib/overrides/vite-native-knowledge-store.ts +267 -0
  235. package/src/lib/overrides/vite-native-policy-plane.ts +190 -0
  236. package/src/lib/overrides/vite-native-replay-plane.ts +197 -0
  237. package/src/lib/overrides/vite-native-runtime-plane.ts +106 -0
  238. package/src/lib/overrides/vite-native-scraper.ts +848 -0
  239. package/src/lib/overrides/vite-native-selftest-functional.ts +217 -0
  240. package/src/lib/overrides/vite-native-selftest.ts +183 -0
  241. package/src/lib/overrides/vite-native-snapshot-plane.ts +123 -0
  242. package/src/lib/philosophy-toolkit.ts +75 -0
  243. package/src/lib/pipeline.orig.ts +659 -0
  244. package/src/lib/pipeline.ts +42 -0
  245. package/src/lib/precache.ts +128 -0
  246. package/src/lib/py-sandbox.ts +205 -0
  247. package/src/lib/quality-score.ts +96 -0
  248. package/src/lib/quant-engine.ts +231 -0
  249. package/src/lib/quant-lib.ts +80 -0
  250. package/src/lib/reportos.ts +175 -0
  251. package/src/lib/research-os.ts +306 -0
  252. package/src/lib/resource-estimator.ts +114 -0
  253. package/src/lib/rpm-governor.ts +93 -0
  254. package/src/lib/runtime-estimator.ts +135 -0
  255. package/src/lib/sage/empty-space-v6.ts +560 -0
  256. package/src/lib/sage/empty-space-v7.ts +222 -0
  257. package/src/lib/sage/empty-space.ts +733 -0
  258. package/src/lib/sage/engine.ts +786 -0
  259. package/src/lib/sage/index.ts +17 -0
  260. package/src/lib/sage/judge.ts +246 -0
  261. package/src/lib/sage/prng.ts +53 -0
  262. package/src/lib/sage/reality-grounding.ts +380 -0
  263. package/src/lib/sage/repetition.ts +151 -0
  264. package/src/lib/sage/saga-os-v2-engines.ts +1870 -0
  265. package/src/lib/sage/saga-os-v2.ts +274 -0
  266. package/src/lib/sage/saga-os-v3-core.ts +1491 -0
  267. package/src/lib/sage/saga-os-v3-release.ts +739 -0
  268. package/src/lib/sage/saga-os-v4-aureate.ts +1161 -0
  269. package/src/lib/sage/saga-os-v4.ts +212 -0
  270. package/src/lib/sage/saga-os-v5.ts +205 -0
  271. package/src/lib/sage/saga-os-v6.ts +346 -0
  272. package/src/lib/sage/saga-os-v7.ts +314 -0
  273. package/src/lib/sage/saga-os-v8.ts +505 -0
  274. package/src/lib/sage/saga-os.ts +329 -0
  275. package/src/lib/sage/taxonomy.ts +403 -0
  276. package/src/lib/sage/unified-roll.ts +524 -0
  277. package/src/lib/sage/worked-sketches.ts +44 -0
  278. package/src/lib/sage.ts +166 -0
  279. package/src/lib/scraper-debug-bus.ts +53 -0
  280. package/src/lib/scraper-enhanced.orig.ts +153 -0
  281. package/src/lib/scraper-enhanced.ts +10 -0
  282. package/src/lib/scraper-hardener.base.orig.ts +155 -0
  283. package/src/lib/scraper-hardener.base.ts +317 -0
  284. package/src/lib/scraper-hardener.ts +1 -0
  285. package/src/lib/scraper-palisade/palisade-adjudicator.orig.ts +419 -0
  286. package/src/lib/scraper-palisade/palisade-adjudicator.ts +13 -0
  287. package/src/lib/scraper-vnext/arbiter-omega.orig.ts +688 -0
  288. package/src/lib/scraper-vnext/arbiter-omega.ts +13 -0
  289. package/src/lib/scraper-vnext/canonical-portfolio-augments.ts +479 -0
  290. package/src/lib/scraper-vnext/canonical-portfolio-orchestrator.orig.ts +1529 -0
  291. package/src/lib/scraper-vnext/canonical-portfolio-orchestrator.ts +13 -0
  292. package/src/lib/scraper-vnext/conclave-omega.ts +1830 -0
  293. package/src/lib/scraper-vnext/content-extractor-v2.ts +226 -0
  294. package/src/lib/scraper-vnext/diagnostics-suite.ts +36 -0
  295. package/src/lib/scraper-vnext/epistemic-packer.ts +97 -0
  296. package/src/lib/scraper-vnext/fusion-v2.ts +65 -0
  297. package/src/lib/scraper-vnext/hydra-reader.orig.ts +1216 -0
  298. package/src/lib/scraper-vnext/hydra-reader.ts +13 -0
  299. package/src/lib/scraper-vnext/native-scraper-browser-vnext.orig.ts +91 -0
  300. package/src/lib/scraper-vnext/native-scraper-browser-vnext.ts +13 -0
  301. package/src/lib/scraper-vnext/nexus-consensus.orig.ts +905 -0
  302. package/src/lib/scraper-vnext/nexus-consensus.ts +13 -0
  303. package/src/lib/scraper-vnext/portfolio-consensus-adjudicator.ts +804 -0
  304. package/src/lib/scraper-vnext/portfolio-consensus-memory.ts +377 -0
  305. package/src/lib/scraper-vnext/portfolio-terminal-governor.ts +548 -0
  306. package/src/lib/scraper-vnext/query-strategist.orig.ts +224 -0
  307. package/src/lib/scraper-vnext/query-strategist.ts +423 -0
  308. package/src/lib/scraper-vnext/retrieval-accelerator.ts +708 -0
  309. package/src/lib/scraper-vnext/retrieval-audit-augments.ts +477 -0
  310. package/src/lib/scraper-vnext/retrieval-control-plane.ts +904 -0
  311. package/src/lib/scraper-vnext/retrieval-policy-augments.ts +358 -0
  312. package/src/lib/scraper-vnext/safe-fetch-v2.ts +157 -0
  313. package/src/lib/scraper-vnext/sentinel-omega.ts +56 -0
  314. package/src/lib/scraper-vnext/sentinel-orchestrator.ts +69 -0
  315. package/src/lib/scraper-vnext/sibyl-oracle.orig.ts +413 -0
  316. package/src/lib/scraper-vnext/sibyl-oracle.ts +13 -0
  317. package/src/lib/scraper-vnext/smart-read-v2.ts +55 -0
  318. package/src/lib/scraper-vnext/spa-rescue-bridge.ts +608 -0
  319. package/src/lib/scraper-vnext/strata-engine.orig.ts +2666 -0
  320. package/src/lib/scraper-vnext/strata-engine.ts +13 -0
  321. package/src/lib/scraper-vnext/structured-source-adapter.orig.ts +369 -0
  322. package/src/lib/scraper-vnext/structured-source-adapter.ts +34 -0
  323. package/src/lib/scraper-vnext/terminal-complete.ts +224 -0
  324. package/src/lib/scraper-vnext/terminal-final.ts +277 -0
  325. package/src/lib/scraper-vnext/terminal-saturation.ts +345 -0
  326. package/src/lib/scraper-vnext/terminal-wire.ts +425 -0
  327. package/src/lib/scraper-vnext/vanguard-titanium.orig.ts +467 -0
  328. package/src/lib/scraper-vnext/vanguard-titanium.ts +17 -0
  329. package/src/lib/search-cache.ts +138 -0
  330. package/src/lib/sloop-runner.base.ts +223 -0
  331. package/src/lib/sloop-runner.ts +104 -0
  332. package/src/lib/sloop.ts +114 -0
  333. package/src/lib/small-model-adapters.ts +82 -0
  334. package/src/lib/sscp.ts +86 -0
  335. package/src/lib/system-map.ts +54 -0
  336. package/src/lib/uni-node.ts +86 -0
  337. package/src/lib/universal-rigor-guard.ts +433 -0
  338. package/src/lib/v15-gate-testbed.ts +274 -0
  339. package/src/lib/v15-gemini.base.ts +89 -0
  340. package/src/lib/v15-gemini.ts +1 -0
  341. package/src/lib/v15-grounding.orig.ts +241 -0
  342. package/src/lib/v15-grounding.ts +79 -0
  343. package/src/lib/v15-pipeline.base.ts +769 -0
  344. package/src/lib/v15-pipeline.orig.ts +1154 -0
  345. package/src/lib/v15-pipeline.ts +674 -0
  346. package/src/lib/v15-questions.ts +64 -0
  347. package/src/lib/v15-rate-limiter.orig.ts +91 -0
  348. package/src/lib/v15-rate-limiter.ts +48 -0
  349. package/src/lib/v15-state.base.ts +87 -0
  350. package/src/lib/v15-state.ts +28 -0
  351. package/src/lib/visual-table-generator.tsx +207 -0
  352. package/src/lib/wasm-runtime.ts +133 -0
  353. package/src/lib/williams-persona-guide.ts +265 -0
  354. package/src/lib/williams-style.base.ts +464 -0
  355. package/src/lib/williams-style.ts +59 -0
  356. package/src/lib/worker-pool.ts +96 -0
  357. package/src/lib/writing-tiers.ts +125 -0
  358. package/src/main.orig.tsx +10 -0
  359. package/src/main.tsx +10 -0
  360. package/src/utils/cn.orig.ts +6 -0
  361. package/src/utils/cn.ts +6 -0
  362. package/src/vite-env.d.ts +1 -0
  363. package/tsconfig.json +31 -0
  364. package/unify.mjs +537 -0
  365. package/vite.config.ts +18 -0
@@ -0,0 +1,605 @@
1
+ /**
2
+ * SAGA-OS STUDIO โ€” usability-first rework of the v1โ€“v8 panel (P15).
3
+ * ---------------------------------------------------------------------------
4
+ * Organised by the v3 ยง2.2 pipeline (what a writer DOES), not by version tag:
5
+ * Compose โ†’ Catalog โ†’ Names โ†’ Evolve โ†’ Realize โ†’ Score โ†’ Screen โ†’ Release
6
+ * Every tab drives a real deterministic computation over the SAME unified roll
7
+ * the fiction prompt is compiled from, so nothing shown here is decorative.
8
+ * The v1โ€“v8 canonical tables and the v2 lab are preserved under "Reference".
9
+ */
10
+ import { useCallback, useEffect, useMemo, useState } from "react";
11
+ import { rollSage } from '@/lib/sage/engine.ts';
12
+ import { GENRE_HELM } from '@/lib/sage/saga-os-v2.ts';
13
+ import { unifiedRoll, type UnifiedRoll } from "@/lib/sage/unified-roll";
14
+ import { loadRealityLevel } from "@/lib/sage/reality-grounding";
15
+ import {
16
+ compileCatalog, GENRE_PROFILES, resolveGenreKey, loadBuildRequest, saveBuildRequest, defaultBuildRequest,
17
+ NAME_PROFILES, generateNameSet, hybridProfile, generateName, createNameRegistry, GenomeRng, sha256Hex,
18
+ runMorphogenesis, auditCastConfusionV3, SLOT_CLASS_LIST,
19
+ type BuildRequest, type CompiledElement, type NameClass, type LengthMode, type GeneratedName,
20
+ } from "@/lib/sage/saga-os-v3-core";
21
+ import {
22
+ auditManuscript, evaluateKallosV3, adaptToScreen, toFountain, runScreenAudits, screenplayPromptPack,
23
+ runProductionTests, runReleaseGates, loadLiveStore, saveLiveStore, updateAudienceProfile, profileKeyFor,
24
+ SAGA_V3_COMPETITOR_MATRIX, KALLOS_V3_LABELS,
25
+ type AdaptationMode, type AudienceObservation, type AudienceProfile, type ManuscriptAudit,
26
+ } from "@/lib/sage/saga-os-v3-release";
27
+ import { SLOT_CLASS_META, type SlotClassId } from "@/lib/sage/saga-os-v2-engines";
28
+ import { KALLOS_DIMENSIONS } from '@/lib/sage/saga-os-v2.ts';
29
+ import { SagaStyleTab } from "./SagaStyleTab";
30
+ import { SagaOsReference } from "./SagaOsReference";
31
+ import { SagaOsV2Panel } from "./SagaOsV2Panel";
32
+
33
+ // โ”€โ”€ fiction-mode detection (same key the packaged runner uses) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
34
+ function useFictionActive(): boolean {
35
+ const [active, setActive] = useState<boolean>(() => {
36
+ try { return localStorage.getItem("veritas.sage.pipeline.mode.v2") === "fiction"; } catch { return false; }
37
+ });
38
+ useEffect(() => {
39
+ const check = () => { try { setActive(localStorage.getItem("veritas.sage.pipeline.mode.v2") === "fiction"); } catch { /* ignore */ } };
40
+ check();
41
+ const id = window.setInterval(check, 800);
42
+ window.addEventListener("storage", check);
43
+ const obs = new MutationObserver(check);
44
+ try { obs.observe(document.body, { childList: true, subtree: true }); } catch { /* ignore */ }
45
+ return () => { window.clearInterval(id); window.removeEventListener("storage", check); obs.disconnect(); };
46
+ }, []);
47
+ return active;
48
+ }
49
+
50
+ // โ”€โ”€ atoms (same typography as Style Persona / SAGE panels) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
51
+ const T: Record<string, string> = {
52
+ zinc: "bg-zinc-100 text-zinc-700 border-zinc-200", indigo: "bg-indigo-50 text-indigo-700 border-indigo-200",
53
+ violet: "bg-violet-50 text-violet-700 border-violet-200", emerald: "bg-emerald-50 text-emerald-700 border-emerald-200",
54
+ amber: "bg-amber-50 text-amber-800 border-amber-200", rose: "bg-rose-50 text-rose-700 border-rose-200",
55
+ sky: "bg-sky-50 text-sky-700 border-sky-200", teal: "bg-teal-50 text-teal-700 border-teal-200",
56
+ };
57
+ const Pill = ({ children, tone = "zinc", title }: { children: React.ReactNode; tone?: string; title?: string }) =>
58
+ <span title={title} className={`inline-flex items-center rounded-md border px-1.5 py-0.5 text-[10px] font-semibold ${T[tone] ?? T.zinc}`}>{children}</span>;
59
+ const Head = ({ icon, title, right }: { icon: string; title: string; right?: React.ReactNode }) => (
60
+ <div className="flex items-center gap-2 border-b border-zinc-100 pb-1">
61
+ <span className="text-sm">{icon}</span><span className="text-[11px] font-bold uppercase tracking-wider text-zinc-700">{title}</span>
62
+ {right != null && <span className="ml-auto text-[10px] text-zinc-400">{right}</span>}
63
+ </div>
64
+ );
65
+ const Card = ({ children, tone = "white" }: { children: React.ReactNode; tone?: "white" | "violet" | "emerald" | "amber" | "rose" }) => (
66
+ <div className={`rounded-lg border p-2.5 ${tone === "white" ? "border-zinc-200 bg-white" : tone === "violet" ? "border-violet-200 bg-violet-50/40" : tone === "emerald" ? "border-emerald-200 bg-emerald-50/50" : tone === "amber" ? "border-amber-200 bg-amber-50/60" : "border-rose-200 bg-rose-50/60"}`}>{children}</div>
67
+ );
68
+ const KV = ({ k, v }: { k: string; v: React.ReactNode }) => (
69
+ <div className="flex items-baseline gap-1.5 border-b border-zinc-50 py-0.5 last:border-0">
70
+ <span className="w-32 shrink-0 text-[10px] font-bold uppercase tracking-wider text-zinc-400">{k}</span><span className="text-[11px] text-zinc-800">{v}</span>
71
+ </div>
72
+ );
73
+ const Bar = ({ v, label, note }: { v: number; label: string; note?: string }) => (
74
+ <div>
75
+ <div className="flex items-center gap-1.5">
76
+ <span className="w-[150px] shrink-0 truncate text-right text-[10px] text-zinc-500" title={label}>{label}</span>
77
+ <div className="h-1.5 flex-1 rounded-full bg-zinc-100"><div className="h-full rounded-full" style={{ width: `${Math.round(v * 100)}%`, backgroundColor: `hsl(${Math.round(v * 120)},55%,50%)` }} /></div>
78
+ <span className="w-9 shrink-0 text-right font-mono text-[10px] text-zinc-600">{(v * 100).toFixed(0)}%</span>
79
+ </div>
80
+ {note && <div className="ml-[158px] text-[9px] leading-tight text-zinc-400">{note}</div>}
81
+ </div>
82
+ );
83
+ const Btn = ({ onClick, children, tone = "indigo", disabled }: { onClick: () => void; children: React.ReactNode; tone?: "indigo" | "ghost" | "emerald" | "rose"; disabled?: boolean }) => (
84
+ <button onClick={onClick} disabled={disabled} className={`rounded px-2.5 py-1 text-[11px] font-bold disabled:opacity-40 ${tone === "indigo" ? "bg-indigo-600 text-white hover:bg-indigo-700" : tone === "emerald" ? "bg-emerald-600 text-white hover:bg-emerald-700" : tone === "rose" ? "bg-rose-600 text-white hover:bg-rose-700" : "border border-zinc-300 bg-white text-zinc-700 hover:bg-zinc-50"}`}>{children}</button>
85
+ );
86
+ const Sel = ({ value, onChange, options }: { value: string; onChange: (v: string) => void; options: readonly string[] | { v: string; l: string }[] }) => (
87
+ <select value={value} onChange={(e) => onChange(e.target.value)} className="rounded border border-zinc-200 bg-white px-1.5 py-1 text-[11px]">
88
+ {(options as (string | { v: string; l: string })[]).map((o) => typeof o === "string" ? <option key={o} value={o}>{o}</option> : <option key={o.v} value={o.v}>{o.l}</option>)}
89
+ </select>
90
+ );
91
+ function liveSeed(): number | null { try { const n = Number(localStorage.getItem("veritas.sage.previewSeed.v1")); return Number.isFinite(n) && n > 0 ? n : null; } catch { return null; } }
92
+ function ls(k: string, d: string) { try { return localStorage.getItem(k) ?? d; } catch { return d; } }
93
+
94
+ type Tab = "compose" | "catalog" | "names" | "evolve" | "realize" | "style" | "score" | "screen" | "release" | "reference";
95
+ const TABS: { id: Tab; icon: string; label: string; stage: string }[] = [
96
+ { id: "compose", icon: "๐ŸŽญ", label: "Compose", stage: "Genre + audience" },
97
+ { id: "catalog", icon: "๐Ÿ—‚", label: "Catalog", stage: "Compile 947 rows" },
98
+ { id: "names", icon: "๐Ÿ“›", label: "Names", stage: "ONOMASTRATA" },
99
+ { id: "evolve", icon: "๐Ÿงฌ", label: "Evolve", stage: "Morphogenesis" },
100
+ { id: "realize", icon: "๐Ÿ“", label: "Realize", stage: "Obligations + audit" },
101
+ { id: "style", icon: "โœ’", label: "Style", stage: "AUREATE-SCRIBE v4" },
102
+ { id: "score", icon: "โœจ", label: "Score", stage: "KALLOS v3 + LIVE" },
103
+ { id: "screen", icon: "๐ŸŽฌ", label: "Screen", stage: "Adapt + Fountain" },
104
+ { id: "release", icon: "๐Ÿšฆ", label: "Release", stage: "Gates + TEST-025" },
105
+ { id: "reference", icon: "๐Ÿ“š", label: "Reference", stage: "v1โ€“v8 tables ยท v2 lab" },
106
+ ];
107
+
108
+ // โ•โ•โ• COMPOSE โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
109
+ export function ComposeTab({ roll, onApplied }: { roll: UnifiedRoll; onApplied: () => void }) {
110
+ const [req, setReq] = useState<BuildRequest>(() => loadBuildRequest() ?? roll.request);
111
+ const genreKeys = Object.keys(GENRE_PROFILES);
112
+ const set = (fn: (r: BuildRequest) => BuildRequest) => setReq((r) => fn(structuredClone(r)));
113
+ const weights = req.creative.mixWeights ?? req.creative.genres.map((_, i) => Math.max(0.15, 1 / (i + 1)));
114
+ const wsum = weights.reduce((a, b) => a + b, 0) || 1;
115
+ const subOptions = [...new Set(req.creative.genres.flatMap((g) => GENRE_HELM[GENRE_PROFILES[resolveGenreKey(g) ?? "literary"]?.family ?? "LITERARY"] ?? []))];
116
+ return (
117
+ <div className="space-y-2">
118
+ <Card tone="violet">
119
+ <Head icon="๐ŸŽญ" title="Genre Helm v3 โ€” build request" right={roll.requestSource === "studio" ? "ACTIVE: studio contract drives the prompt" : "prompt currently uses the derived contract"} />
120
+ <div className="mt-1 text-[10px] text-zinc-600">Compose the human-readable request (v3 ยง7.1). <b>Apply</b> writes it to the fiction pipeline: the archetype set, the KALLOS weights, the lattice's target ending, and the prompt's GENRE CONTRACT block all change. Structure (POV, cast, setting) stays seed-driven.</div>
121
+ </Card>
122
+ <Card>
123
+ <Head icon="๐ŸŽš" title="Genres & mix" right="ฮฃ weights normalised to 1" />
124
+ {req.creative.genres.map((g, i) => (
125
+ <div key={i} className="mt-1 flex flex-wrap items-center gap-1.5">
126
+ <Sel value={resolveGenreKey(g) ?? "literary"} onChange={(v) => set((r) => { r.creative.genres[i] = v; return r; })} options={genreKeys} />
127
+ <input type="range" min={5} max={100} value={Math.round((weights[i] / wsum) * 100)} onChange={(e) => set((r) => { const w = [...weights]; w[i] = Number(e.target.value) / 100; r.creative.mixWeights = w; return r; })} className="w-40 accent-indigo-600" />
128
+ <span className="w-10 font-mono text-[10px] text-zinc-600">{Math.round((weights[i] / wsum) * 100)}%</span>
129
+ {i === 0 ? <Pill tone="indigo">dominant contract</Pill> : <Pill tone="zinc">secondary pleasures</Pill>}
130
+ {req.creative.genres.length > 1 && <button onClick={() => set((r) => { r.creative.genres.splice(i, 1); r.creative.mixWeights = weights.filter((_, j) => j !== i); return r; })} className="text-[10px] text-rose-600">remove</button>}
131
+ </div>
132
+ ))}
133
+ {req.creative.genres.length < 3 && <button onClick={() => set((r) => { r.creative.genres.push("mystery"); r.creative.mixWeights = [...weights, 0.3]; return r; })} className="mt-1 text-[10px] font-semibold text-indigo-600">+ add genre</button>}
134
+ <div className="mt-2 text-[10px] font-bold uppercase tracking-wider text-zinc-400">Subgenres</div>
135
+ <div className="mt-0.5 flex flex-wrap gap-1">
136
+ {subOptions.map((sg) => { const on = req.creative.subgenres.includes(sg); return <button key={sg} onClick={() => set((r) => { r.creative.subgenres = on ? r.creative.subgenres.filter((x) => x !== sg) : [...r.creative.subgenres, sg]; return r; })} className={`rounded px-1.5 py-0.5 text-[10px] ${on ? "bg-indigo-600 text-white" : "bg-zinc-100 text-zinc-600"}`}>{sg}</button>; })}
137
+ </div>
138
+ </Card>
139
+ <div className="grid gap-2 sm:grid-cols-2">
140
+ <Card>
141
+ <Head icon="๐Ÿ‘ฅ" title="Audience" />
142
+ <div className="mt-1 grid grid-cols-2 gap-1">
143
+ <label className="text-[10px] text-zinc-500">age band<br /><Sel value={req.creative.audience.ageBand} onChange={(v) => set((r) => { r.creative.audience.ageBand = v as BuildRequest["creative"]["audience"]["ageBand"]; return r; })} options={["children", "middle_grade", "ya", "adult"]} /></label>
144
+ <label className="text-[10px] text-zinc-500">reading<br /><Sel value={req.creative.audience.readingComplexity} onChange={(v) => set((r) => { r.creative.audience.readingComplexity = v as BuildRequest["creative"]["audience"]["readingComplexity"]; return r; })} options={["accessible", "accessible_literary", "literary", "dense"]} /></label>
145
+ <label className="text-[10px] text-zinc-500">heat<br /><Sel value={req.creative.audience.heatLevel} onChange={(v) => set((r) => { r.creative.audience.heatLevel = v as BuildRequest["creative"]["audience"]["heatLevel"]; return r; })} options={["none", "low", "moderate", "high"]} /></label>
146
+ <label className="text-[10px] text-zinc-500">violence<br /><Sel value={req.creative.audience.violenceLevel} onChange={(v) => set((r) => { r.creative.audience.violenceLevel = v as BuildRequest["creative"]["audience"]["violenceLevel"]; return r; })} options={["none", "low", "moderate", "high"]} /></label>
147
+ </div>
148
+ </Card>
149
+ <Card>
150
+ <Head icon="๐ŸŒก" title="Tone trajectory" />
151
+ <div className="mt-1 grid grid-cols-3 gap-1">
152
+ <label className="text-[10px] text-zinc-500">opening<br /><Sel value={req.creative.tone.opening} onChange={(v) => set((r) => { r.creative.tone.opening = v as BuildRequest["creative"]["tone"]["opening"]; return r; })} options={["intimate", "cool", "warm", "dread", "wry", "plain", "charged", "wonder"]} /></label>
153
+ <label className="text-[10px] text-zinc-500">trajectory<br /><Sel value={req.creative.tone.trajectory} onChange={(v) => set((r) => { r.creative.tone.trajectory = v as BuildRequest["creative"]["tone"]["trajectory"]; return r; })} options={["darkening", "lightening", "steady", "oscillating", "collapsing", "converging"]} /></label>
154
+ <label className="text-[10px] text-zinc-500">ending<br /><Sel value={req.creative.tone.ending} onChange={(v) => set((r) => { r.creative.tone.ending = v as BuildRequest["creative"]["tone"]["ending"]; return r; })} options={["bittersweet", "earned", "tragic", "open", "custodial", "triumphant", "quiet"]} /></label>
155
+ </div>
156
+ <div className="mt-1 grid grid-cols-2 gap-1">
157
+ <label className="text-[10px] text-zinc-500">volumes<br /><input type="number" min={1} max={13} value={req.project.length.volumes} onChange={(e) => set((r) => { r.project.length.volumes = Math.max(1, Math.min(13, Number(e.target.value) || 1)); return r; })} className="w-16 rounded border border-zinc-200 px-1.5 py-1 font-mono text-[11px]" /></label>
158
+ <label className="text-[10px] text-zinc-500">words / volume<br /><input type="number" min={1000} step={5000} value={req.project.length.wordsPerVolume} onChange={(e) => set((r) => { r.project.length.wordsPerVolume = Math.max(1000, Number(e.target.value) || 1000); return r; })} className="w-24 rounded border border-zinc-200 px-1.5 py-1 font-mono text-[11px]" /></label>
159
+ </div>
160
+ </Card>
161
+ </div>
162
+ <Card>
163
+ <Head icon="โœ" title="Requests (free text, carried into the contract)" />
164
+ <div className="mt-1 grid gap-1 sm:grid-cols-3">
165
+ {(["protagonist", "setting", "relationship"] as const).map((k) => (
166
+ <input key={k} placeholder={k} value={req.creative.requests[k] ?? ""} onChange={(e) => set((r) => { r.creative.requests[k] = e.target.value; return r; })} className="rounded border border-zinc-200 px-2 py-1 text-[11px]" />
167
+ ))}
168
+ </div>
169
+ </Card>
170
+ <div className="flex flex-wrap items-center gap-2">
171
+ <Btn onClick={() => { saveBuildRequest(req); onApplied(); }}>Apply to fiction pipeline</Btn>
172
+ <Btn tone="ghost" onClick={() => { saveBuildRequest(null); setReq(defaultBuildRequest(roll.seed, roll.spec.family)); onApplied(); }}>Reset to derived</Btn>
173
+ <span className="text-[10px] text-zinc-400">contract preview: <b>{roll.contract.mix.map((m) => `${m.key} ${Math.round(m.weight * 100)}%`).join(" + ")}</b> ยท audience {roll.contract.audienceSegment}</span>
174
+ </div>
175
+ <Card>
176
+ <Head icon="๐Ÿ“œ" title="Resulting genre contract (what the prompt receives)" />
177
+ <KV k="Reader question" v={roll.contract.dominantReaderQuestion} />
178
+ <KV k="Mandatory payoffs" v={<ul className="ml-3 list-disc">{roll.contract.mandatoryPayoffs.map((x) => <li key={x}>{x}</li>)}</ul>} />
179
+ <KV k="Forbidden failures" v={<ul className="ml-3 list-disc">{roll.contract.forbiddenFailures.map((x) => <li key={x}>{x}</li>)}</ul>} />
180
+ <KV k="Ending families" v={roll.contract.defaultEndingFamilies.join(" ยท ")} />
181
+ <KV k="Tone contract" v={roll.contract.toneContract.join(" ยท ")} />
182
+ <KV k="Audience constraints" v={roll.contract.audienceConstraints.join(" ยท ")} />
183
+ <KV k="KALLOS base weights" v={<span className="font-mono text-[10px]">{KALLOS_DIMENSIONS.map((d, i) => `${d} ${roll.contract.kallosBaseWeights[i]}`).join(" ยท ")}</span>} />
184
+ {roll.contract.unresolved.length > 0 && <div className="mt-1 text-[10px] text-amber-700">Unresolved genre keys (declared, not dropped): {roll.contract.unresolved.join(", ")}</div>}
185
+ </Card>
186
+ </div>
187
+ );
188
+ }
189
+
190
+ // โ•โ•โ• CATALOG โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
191
+ export function CatalogTab() {
192
+ const m = useMemo(() => compileCatalog(), []);
193
+ const [q, setQ] = useState(""); const [slot, setSlot] = useState<SlotClassId | "">(""); const [sel, setSel] = useState<CompiledElement | null>(null);
194
+ const rows = useMemo(() => m.elements.filter((e) => (!slot || e.primarySlot === slot) && (!q || `${e.id} ${e.name} ${e.desc} ${e.semanticDomain.join(" ")}`.toLowerCase().includes(q.toLowerCase()))).slice(0, 120), [m, q, slot]);
195
+ const perSlot = useMemo(() => { const c: Record<string, number> = {}; for (const e of m.elements) c[e.primarySlot] = (c[e.primarySlot] ?? 0) + 1; return c; }, [m]);
196
+ return (
197
+ <div className="space-y-2">
198
+ <Card tone={m.releaseOk ? "emerald" : "rose"}>
199
+ <Head icon="๐Ÿ—‚" title="CATALOG-COMPILER โ€” coverage gate" right={`manifest ${m.manifestHash.slice(0, 16)}โ€ฆ`} />
200
+ <div className="mt-1 flex flex-wrap gap-1">
201
+ <Pill tone={m.coverage === 1 ? "emerald" : "rose"}>WIRE-COVERAGE {(m.coverage * 100).toFixed(4)}% ({m.compiledRows}/{m.canonicalRows})</Pill>
202
+ <Pill tone={m.duplicateIdCount === 0 ? "emerald" : "rose"}>duplicate ids {m.duplicateIdCount}</Pill>
203
+ <Pill tone={m.unresolvedSlotCount === 0 ? "emerald" : "rose"}>unresolved slots {m.unresolvedSlotCount}</Pill>
204
+ <Pill tone={m.missingRealizationFrameCount === 0 ? "emerald" : "rose"}>missing frames {m.missingRealizationFrameCount}</Pill>
205
+ <Pill tone={m.releaseOk ? "emerald" : "rose"}>{m.releaseOk ? "RELEASE OK" : "RELEASE BLOCKED"}</Pill>
206
+ </div>
207
+ <div className="mt-1 text-[10px] text-zinc-600">Every row is materialised as a CompiledElement with a row hash (ยง6.3). Rows that cannot resolve a mandatory field FAIL CLOSED and are listed โ€” there is no "covered by default". The catalog shipped in this package is <b>{m.canonicalRows} rows</b>; that number is stated, not rounded up.</div>
208
+ {m.failedClosed.length > 0 && <ul className="ml-3 mt-1 list-disc text-[10px] text-rose-700">{m.failedClosed.map((f) => <li key={f.id}>{f.id}: {f.reason}</li>)}</ul>}
209
+ </Card>
210
+ <div className="flex flex-wrap gap-1">
211
+ <button onClick={() => setSlot("")} className={`rounded px-1.5 py-0.5 text-[10px] font-semibold ${slot === "" ? "bg-indigo-600 text-white" : "bg-zinc-100 text-zinc-700"}`}>all</button>
212
+ {SLOT_CLASS_LIST.map((s) => <button key={s} onClick={() => setSlot(s)} className={`rounded px-1.5 py-0.5 text-[10px] font-semibold ${slot === s ? "bg-indigo-600 text-white" : "bg-zinc-100 text-zinc-700"}`} title={SLOT_CLASS_META[s].note}>{SLOT_CLASS_META[s].name} ยท {perSlot[s] ?? 0}</button>)}
213
+ </div>
214
+ <input value={q} onChange={(e) => setQ(e.target.value)} placeholder="search id / name / mechanism / domainโ€ฆ" className="w-full rounded border border-zinc-200 px-2 py-1 text-[11px]" />
215
+ <div className="grid gap-2 lg:grid-cols-[1fr_1fr]">
216
+ <Card>
217
+ <div className="max-h-[420px] overflow-y-auto">
218
+ <table className="w-full text-[10px]"><tbody>
219
+ {rows.map((e) => <tr key={e.id} onClick={() => setSel(e)} className={`cursor-pointer border-b border-zinc-50 hover:bg-indigo-50 ${sel?.id === e.id ? "bg-indigo-50" : ""}`}>
220
+ <td className="w-20 py-0.5 font-mono text-zinc-500">{e.id}</td><td className="py-0.5 font-semibold text-zinc-800">{e.name}</td>
221
+ <td className="w-14 py-0.5 font-mono text-[9px] text-amber-700">{e.stakesAxes[0]}</td><td className="w-10 py-0.5 text-right font-mono text-[9px] text-zinc-400">{e.noveltyPrior.toFixed(2)}</td>
222
+ </tr>)}
223
+ </tbody></table>
224
+ </div>
225
+ <div className="mt-1 text-[9px] text-zinc-400">showing {rows.length}{rows.length === 120 ? " (first 120)" : ""} ยท click a row for its compiled record</div>
226
+ </Card>
227
+ <Card>
228
+ {sel ? (<>
229
+ <Head icon="๐Ÿงพ" title={sel.name} right={<span className="font-mono">{sel.id} ยท row {sel.rowHash}</span>} />
230
+ <div className="mt-1 text-[10px] text-zinc-600">{sel.desc}</div>
231
+ <div className="mt-1.5 space-y-0.5">
232
+ <KV k="slot" v={<>{SLOT_CLASS_META[sel.primarySlot].name} <span className="text-zinc-400">+ {sel.secondarySlots.join(", ")}</span></>} />
233
+ <KV k="domains" v={sel.semanticDomain.join(", ") || "โ€”"} />
234
+ <KV k="world layers" v={sel.worldLayers.join(" ")} />
235
+ <KV k="preconditions" v={sel.preconditions.join(" ยท ")} />
236
+ <KV k="effects" v={sel.effects.join(" ยท ")} />
237
+ <KV k="conflicts (โ‰ )" v={sel.conflictsWith.join(", ") || "โ€”"} />
238
+ <KV k="enables" v={sel.enables.join(", ")} />
239
+ <KV k="cost / stakes" v={`${sel.costAxes.join("/")} ยท ${sel.stakesAxes.join("/")}`} />
240
+ <KV k="scene grammars" v={sel.sceneGrammars.join(", ")} />
241
+ <KV k="realization frames" v={sel.realizationFrames.join(", ")} />
242
+ <KV k="screen channels" v={sel.screenplayChannels.join(", ")} />
243
+ <KV k="names required" v={sel.nameRequirements.join(", ") || "โ€”"} />
244
+ <KV k="genre affinity" v={<span className="font-mono text-[9px]">{Object.entries(sel.genreAffinity).sort((a, b) => b[1] - a[1]).slice(0, 5).map(([k, v]) => `${k} ${v}`).join(" ยท ")}</span>} />
245
+ <KV k="audience" v={<span className="font-mono text-[9px]">{Object.entries(sel.audienceAffinity).map(([k, v]) => `${k} ${v.toFixed(2)}`).join(" ยท ")}</span>} />
246
+ <KV k="provenance" v={<><Pill tone={sel.provenanceStatus === "attested-clean" ? "emerald" : sel.provenanceStatus === "uncertain" ? "amber" : "zinc"}>{sel.provenanceStatus}</Pill> {sel.harnessRequired && <Pill tone="rose">HARNESS REQUIRED</Pill>}</>} />
247
+ </div>
248
+ </>) : <div className="text-[11px] text-zinc-400">Select a row to inspect its compiled record.</div>}
249
+ </Card>
250
+ </div>
251
+ </div>
252
+ );
253
+ }
254
+
255
+ // โ•โ•โ• NAMES โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
256
+ export function NamesTab({ roll }: { roll: UnifiedRoll }) {
257
+ const [profile, setProfile] = useState(roll.nameProfile);
258
+ const [hybridB, setHybridB] = useState("");
259
+ const [contact, setContact] = useState("");
260
+ const [counts, setCounts] = useState<Partial<Record<NameClass, number>>>({ PERSON: 6, PLACE: 4, INSTITUTION: 2, OFFICE: 1, LAW: 1, DOCUMENT: 1, ARTIFACT: 1 });
261
+ const [out, setOut] = useState<ReturnType<typeof generateNameSet> | null>(null);
262
+ const [err, setErr] = useState("");
263
+ const gen = useCallback(() => {
264
+ try {
265
+ setErr("");
266
+ if (hybridB) {
267
+ const hp = hybridProfile(profile, hybridB, contact);
268
+ const rng = new GenomeRng(sha256Hex(`onoma|${roll.morph.genome}|${hp.profileId}`)); const reg = createNameRegistry(); const names: GeneratedName[] = [];
269
+ for (const [cls, n] of Object.entries(counts) as [NameClass, number][]) for (let i = 0; i < (n ?? 0); i++) { const g = generateName(rng, hp, cls, reg); if (g) names.push(g); }
270
+ setOut({ names, registry: reg, audit: auditCastConfusionV3(names.filter((n) => n.nameClass === "PERSON"), hp) });
271
+ } else setOut(generateNameSet(roll.morph.genome, profile, counts));
272
+ } catch (e) { setErr(e instanceof Error ? e.message : String(e)); setOut(null); }
273
+ }, [profile, hybridB, contact, counts, roll.morph.genome]);
274
+ const P = NAME_PROFILES[profile];
275
+ return (
276
+ <div className="space-y-2">
277
+ <Card tone="violet">
278
+ <Head icon="๐Ÿ“›" title="ONOMASTRATA v3 โ€” live set for this build" right={`profile ${roll.nameProfile} ยท genome ${roll.morph.genome.slice(0, 10)}โ€ฆ`} />
279
+ <div className="mt-1 flex flex-wrap gap-1">{roll.nameSet.map((n) => <Pill key={n.full} tone={n.nameClass === "PERSON" ? "indigo" : n.nameClass === "PLACE" ? "teal" : "amber"} title={`${n.nameClass} ยท formal ${n.formal} ยท nickname ${n.nickname} ยท payload ${n.semanticPayload}`}>{n.full}</Pill>)}</div>
280
+ <div className="mt-1 text-[10px] text-zinc-600">These are the names in the prompt's archetype set (overlaid on the cast graph). Hover for forms. Generated by the 12-step pipeline (ยง10.3) and audited by all six ยง10.4 rules.</div>
281
+ </Card>
282
+ <Card>
283
+ <Head icon="๐Ÿงช" title="Name lab" right="deterministic from this build's genome" />
284
+ <div className="mt-1 flex flex-wrap items-center gap-1.5">
285
+ <label className="text-[10px] text-zinc-500">profile <Sel value={profile} onChange={setProfile} options={Object.keys(NAME_PROFILES)} /></label>
286
+ <label className="text-[10px] text-zinc-500">hybrid with <Sel value={hybridB} onChange={setHybridB} options={[{ v: "", l: "โ€” none โ€”" }, ...Object.keys(NAME_PROFILES).filter((k) => k !== profile).map((k) => ({ v: k, l: k }))]} /></label>
287
+ {hybridB && <input value={contact} onChange={(e) => setContact(e.target.value)} placeholder="declared historical contact event (mandatory, ยง10.5)" className="w-72 rounded border border-amber-300 px-2 py-1 text-[11px]" />}
288
+ <Btn onClick={gen}>Generate</Btn>
289
+ </div>
290
+ <div className="mt-1 flex flex-wrap gap-1">{(Object.keys(counts) as NameClass[]).map((c) => <label key={c} className="text-[10px] text-zinc-500">{c} <input type="number" min={0} max={12} value={counts[c]} onChange={(e) => setCounts({ ...counts, [c]: Number(e.target.value) || 0 })} className="w-11 rounded border border-zinc-200 px-1 py-0.5 font-mono text-[10px]" /></label>)}</div>
291
+ {err && <div className="mt-1 rounded border border-rose-200 bg-rose-50 p-1.5 text-[10px] text-rose-700">{err}</div>}
292
+ {P && <div className="mt-1 text-[9px] text-zinc-400">{P.profileId}: order {P.nameOrder} ยท stress {P.stress} ยท lineage {P.lineage} "{P.lineageAffix}" ยท harmony {P.vowelHarmony ? "yes" : "no"} ยท taboo {P.tabooSequences.join(",")} ยท templates {P.syllableTemplates.map(([t]) => t).join("/")}</div>}
293
+ </Card>
294
+ {out && (<>
295
+ <Card>
296
+ <Head icon="๐Ÿงพ" title="Generated" right={`${out.names.length} reserved ยท ${out.registry.rejections.length} rejected`} />
297
+ <table className="mt-1 w-full text-[10px]"><thead><tr className="text-left text-[9px] uppercase text-zinc-400"><th>class</th><th>full (CLDR order)</th><th>formal</th><th>nickname</th><th>syl:stress</th><th>skeleton</th><th>phonetic</th><th>payload</th></tr></thead><tbody>
298
+ {out.names.map((n) => <tr key={n.full} className="border-b border-zinc-50"><td className="py-0.5 font-mono text-zinc-400">{n.nameClass}</td><td className="py-0.5 font-semibold text-zinc-800">{n.full}</td><td className="py-0.5">{n.formal}</td><td className="py-0.5">{n.nickname}</td><td className="py-0.5 font-mono">{n.syllables}:{n.stressOn}</td><td className="py-0.5 font-mono text-zinc-400">{n.skeleton}</td><td className="py-0.5 font-mono text-zinc-400">{n.phonetic}</td><td className="py-0.5 text-zinc-500">{n.semanticPayload}</td></tr>)}
299
+ </tbody></table>
300
+ </Card>
301
+ <div className="grid gap-2 sm:grid-cols-2">
302
+ <Card tone={out.audit.length ? "amber" : "emerald"}>
303
+ <Head icon="๐Ÿ”" title="ยง10.4 cast-confusion audit" right={out.audit.length ? `${out.audit.length} finding(s)` : "clean"} />
304
+ {out.audit.length ? <ul className="ml-3 mt-1 list-disc text-[10px] text-amber-800">{out.audit.map((f, i) => <li key={i}><b>{f.rule}</b>: {f.a} โ†” {f.b} โ€” {f.detail}</li>)}</ul> : <div className="mt-1 text-[10px] text-emerald-800">first-letter โ‰ค2 ยท edit distance โ‰ฅ0.35 ยท phonetic ยท syllable+stress โ‰ค3 ยท skeleton ยท nickname โ€” all pass.</div>}
305
+ </Card>
306
+ <Card>
307
+ <Head icon="๐Ÿ—‘" title="Registry rejections (why candidates were refused)" right={`${out.registry.rejections.length}`} />
308
+ <div className="mt-1 max-h-32 overflow-y-auto text-[10px] text-zinc-500">{out.registry.rejections.slice(0, 40).map((r, i) => <div key={i}><span className="font-mono text-zinc-400">{r.candidate}</span> โ€” {r.reason}</div>)}</div>
309
+ </Card>
310
+ </div>
311
+ </>)}
312
+ </div>
313
+ );
314
+ }
315
+
316
+ // โ•โ•โ• EVOLVE โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
317
+ export function EvolveTab({ roll }: { roll: UnifiedRoll }) {
318
+ const [mode, setMode] = useState<LengthMode>(roll.morph.lengthMode);
319
+ const [budget, setBudget] = useState(roll.morph.budgetTicks);
320
+ const [lab, setLab] = useState<UnifiedRoll["morph"] | null>(null);
321
+ const run = lab ?? roll.morph;
322
+ const isLive = !lab;
323
+ const byLattice = useMemo(() => { const c: Record<string, number> = {}; for (const d of run.deltas) c[`${d.from}โ†’${d.to}`] = (c[`${d.from}โ†’${d.to}`] ?? 0) + 1; return c; }, [run]);
324
+ return (
325
+ <div className="space-y-2">
326
+ <Card tone={isLive ? "emerald" : "amber"}>
327
+ <Head icon="๐Ÿงฌ" title="MORPHOGENESIS-LATTICE v2" right={isLive ? "โ— LIVE โ€” this run is in the prompt" : "โ—‹ LAB โ€” not sent to the model"} />
328
+ <div className="mt-1 flex flex-wrap items-center gap-1.5">
329
+ <label className="text-[10px] text-zinc-500">length mode <Sel value={mode} onChange={(v) => setMode(v as LengthMode)} options={["flash", "short", "novel", "saga", "serial"]} /></label>
330
+ <label className="text-[10px] text-zinc-500">tick budget <input type="number" min={6} max={120} value={budget} onChange={(e) => setBudget(Math.max(6, Math.min(120, Number(e.target.value) || 6)))} className="w-16 rounded border border-zinc-200 px-1.5 py-1 font-mono text-[11px]" /></label>
331
+ <Btn onClick={() => setLab(runMorphogenesis({ userSeed: roll.seed, contract: roll.contract, spec: roll.spec, lengthMode: mode, budget }))}>Evolve (lab)</Btn>
332
+ {lab && <Btn tone="ghost" onClick={() => setLab(null)}>back to live run</Btn>}
333
+ </div>
334
+ <div className="mt-1.5 flex flex-wrap gap-1">
335
+ <Pill tone="violet">{run.attractor}</Pill><Pill tone={run.attractorInTarget ? "emerald" : "amber"}>{run.attractorInTarget ? "in contract target" : `outside target [${run.targetAttractors.join(",")}]`}</Pill>
336
+ <Pill tone="indigo">โ†’ {run.endingFamily}</Pill><Pill tone="zinc">{run.ticks.length} ticks</Pill><Pill tone="zinc">{run.volumes.length} vol</Pill><Pill tone="zinc">{run.promises.length} promises</Pill>
337
+ </div>
338
+ <div className="mt-0.5 font-mono text-[9px] text-zinc-400">genome {run.genome}</div>
339
+ {run.compressionAdvice.length > 0 && <ul className="ml-3 mt-1 list-disc text-[10px] text-amber-800">{run.compressionAdvice.map((a) => <li key={a}>{a}</li>)}</ul>}
340
+ </Card>
341
+ <Card>
342
+ <Head icon="๐Ÿ“ˆ" title="Population ยท intensity per tick" right={`births ${run.stats.births} ยท deaths ${run.stats.deaths} ยท distinct elements ${run.stats.distinctElements} ยท slots ${run.stats.slotsExercised}/14`} />
343
+ <div className="mt-1 flex items-end gap-[2px]" style={{ height: 48 }}>
344
+ {run.ticks.map((t) => { const mx = Math.max(...run.ticks.map((x) => x.population), 1); return <div key={t.t} className="flex-1 rounded-t" title={`t=${t.t} live ${t.activeCount} ยท +${t.births.length}/โˆ’${t.deaths.length} ยท intensity ${t.intensity} ยท checkpoint ${run.checkpoints[t.t]?.hash}`} style={{ height: Math.max(2, Math.round((t.activeCount / mx) * 44)), backgroundColor: `hsl(${Math.round((t.intensity / 100) * 120)},60%,55%)` }} />; })}
345
+ </div>
346
+ <div className="mt-1 flex flex-wrap gap-1">{run.chapters.map((c) => <Pill key={c.index} tone="zinc">{c.volume > 1 ? `v${c.volume}ยท` : ""}{c.index}:{c.dominantStakes ?? "โ€”"}</Pill>)}</div>
347
+ </Card>
348
+ <div className="grid gap-2 sm:grid-cols-2">
349
+ <Card>
350
+ <Head icon="๐Ÿ”€" title="Typed deltas between lattices" right={`${run.deltas.length}`} />
351
+ <div className="mt-1 space-y-0.5">{Object.entries(byLattice).sort((a, b) => b[1] - a[1]).map(([k, n]) => <Bar key={k} v={n / Math.max(1, run.deltas.length)} label={k} note={`${n} deltas`} />)}</div>
352
+ <div className="mt-1 max-h-28 overflow-y-auto text-[9.5px] text-zinc-500">{run.deltas.slice(0, 40).map((d, i) => <div key={i}><span className="font-mono text-zinc-400">t{d.tick}</span> {d.from}โ†’{d.to} <b>{d.kind}</b> {d.detail}</div>)}</div>
353
+ </Card>
354
+ <Card>
355
+ <Head icon="๐ŸŽฏ" title="Promises ยท volumes ยท masks" />
356
+ <div className="mt-1 flex flex-wrap gap-1">{run.promises.map((p) => <Pill key={p.id} tone={p.state === "PAID" ? "emerald" : p.state === "RESIDUALIZED" ? "violet" : p.state === "EXPIRED" ? "rose" : p.state === "TRANSFERRED" ? "sky" : "amber"} title={`${p.scope} ยท opened t${p.openedTick}${p.closedTick != null ? ` ยท closed t${p.closedTick}` : ""}`}>{p.id} {p.state}</Pill>)}</div>
357
+ <div className="mt-1.5 space-y-0.5">{run.volumes.map((v) => <div key={v.index} className="text-[10px] text-zinc-600">vol {v.index}: t{v.startTick}โ€“t{v.endTick} ยท local closure {v.localClosure ? "โœ“" : "โœ—"} ยท {v.seriesDelta}</div>)}</div>
358
+ <div className="mt-1.5 text-[10px] font-bold uppercase tracking-wider text-zinc-400">Audit masks (births zeroed before they happened)</div>
359
+ <div className="text-[10px] text-zinc-500">{run.masked.length ? Object.entries(run.masked.reduce<Record<string, number>>((a, x) => { a[x.rule] = (a[x.rule] ?? 0) + 1; return a; }, {})).map(([r, n]) => `${r} ร—${n}`).join(" ยท ") : "none needed on this run"}</div>
360
+ </Card>
361
+ </div>
362
+ <Card>
363
+ <Head icon="๐Ÿงพ" title="Checkpoints (content-addressed, TEST-021)" right={`${run.checkpoints.length}`} />
364
+ <div className="mt-1 flex flex-wrap gap-1 font-mono text-[9px] text-zinc-400">{run.checkpoints.map((c) => <span key={c.tick} title={`t${c.tick} pop ${c.population} open ${c.promisesOpen}`}>{c.hash}</span>)}</div>
365
+ </Card>
366
+ </div>
367
+ );
368
+ }
369
+
370
+ // โ•โ•โ• REALIZE โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
371
+ export function RealizeTab({ roll }: { roll: UnifiedRoll }) {
372
+ const [text, setText] = useState(""); const [audit, setAudit] = useState<ManuscriptAudit | null>(null);
373
+ const budget = roll.blueprint.chapters.reduce((a, c) => a + c.targetWords, 0);
374
+ const req = roll.obligations.filter((o) => o.required);
375
+ return (
376
+ <div className="space-y-2">
377
+ <Card tone="violet">
378
+ <Head icon="๐Ÿ“" title="PROSE-COMPILER โ€” honest scope" />
379
+ <div className="mt-1 text-[10px] text-zinc-600"><b>CONTROLLED_MODE</b> is implemented: a feature-unification grammar that guarantees agreement, articles and tense <i>within its restricted grammar</i> (v3 ยง12.5). <b>LITERARY_MODE</b> is delegated to the host LLM via the unified prompt and is <i>not</i> claimed as deterministic. The <b>realization obligation ledger</b> and the ยง12.3 release counters are real and run against any manuscript you paste.</div>
380
+ </Card>
381
+ <Card>
382
+ <Head icon="๐Ÿงฑ" title="Controlled-mode skeleton (certified by construction)" />
383
+ <div className="mt-1 rounded bg-zinc-50 p-2 font-mono text-[10px] text-zinc-700">{roll.controlledSample}</div>
384
+ </Card>
385
+ <Card>
386
+ <Head icon="๐Ÿ“‹" title="Realization obligations" right={`${roll.obligations.length} total ยท ${req.length} required`} />
387
+ <div className="mt-1 max-h-48 overflow-y-auto"><table className="w-full text-[10px]"><tbody>
388
+ {roll.obligations.slice(0, 80).map((o) => <tr key={o.propositionId} className="border-b border-zinc-50"><td className="w-20 py-0.5 font-mono text-zinc-400">{o.propositionId}</td><td className="w-16 py-0.5"><Pill tone={o.required ? "rose" : "zinc"}>{o.required ? "required" : "optional"}</Pill></td><td className="py-0.5 text-zinc-700">{o.text}</td><td className="w-40 py-0.5 font-mono text-[9px] text-zinc-400">{o.detectors.slice(0, 3).join(" ")}</td></tr>)}
389
+ </tbody></table></div>
390
+ </Card>
391
+ <Card>
392
+ <Head icon="๐Ÿ”ฌ" title="Audit a manuscript against this plan" right={`word budget ${budget.toLocaleString()}`} />
393
+ <textarea value={text} onChange={(e) => setText(e.target.value)} rows={5} placeholder="Paste the generated manuscript (## chapter headings expected)โ€ฆ" className="mt-1 w-full rounded border border-zinc-200 p-2 font-mono text-[10px]" />
394
+ <div className="mt-1 flex gap-2"><Btn onClick={() => setAudit(auditManuscript(text, roll.morph, roll.obligations, budget))} disabled={!text.trim()}>Run ยง12.3 audit</Btn></div>
395
+ {audit && (<div className="mt-2">
396
+ <div className="flex flex-wrap gap-1">
397
+ {([["required_unrealized", audit.requiredUnrealized], ["template_tokens", audit.unresolvedTemplateToken], ["placeholders", audit.placeholderToken], ["truncated", audit.truncatedSentence], ["unclosed_quote", audit.unclosedQuote], ["dup_paragraph", audit.duplicateParagraphHash], ["missing_chapter", audit.missingChapter], ["budget_shortfall", audit.wordBudgetShortfall]] as const).map(([k, v]) => <Pill key={k} tone={v === 0 ? "emerald" : "rose"}>{k} = {v}</Pill>)}
398
+ <Pill tone={audit.releaseOk ? "emerald" : "rose"}>{audit.releaseOk ? "RELEASE OK" : "RELEASE BLOCKED"}</Pill>
399
+ </div>
400
+ <div className="mt-1 text-[10px] text-zinc-500">{audit.wordCount.toLocaleString()} words ยท {audit.chapterHeadingsFound}/{audit.chapterHeadingsExpected} chapter headings ยท optional unrealized {audit.optionalUnrealized}</div>
401
+ {audit.remedies.length > 0 && <><div className="mt-1 text-[10px] font-bold uppercase tracking-wider text-zinc-400">ยง12.4 remedies (no padding)</div><ul className="ml-3 list-disc text-[10px] text-zinc-600">{audit.remedies.map((r) => <li key={r}>{r}</li>)}</ul></>}
402
+ <div className="mt-1 max-h-40 overflow-y-auto text-[9.5px]">{audit.obligations.filter((o) => o.status === "UNREALIZED" && o.required).slice(0, 30).map((o) => <div key={o.propositionId} className="text-rose-700">โœ— {o.propositionId} {o.text}</div>)}{audit.obligations.filter((o) => o.status === "EMITTED").slice(0, 10).map((o) => <div key={o.propositionId} className="text-emerald-700">โœ“ {o.propositionId} โ€” "โ€ฆ{o.emittedSpan}โ€ฆ"</div>)}</div>
403
+ </div>)}
404
+ </Card>
405
+ </div>
406
+ );
407
+ }
408
+
409
+ // โ•โ•โ• SCORE โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
410
+ export function ScoreTab({ roll }: { roll: UnifiedRoll }) {
411
+ const k = roll.kallosV3;
412
+ const [text, setText] = useState(""); const [kt, setKt] = useState<typeof k | null>(null);
413
+ const K = kt ?? k;
414
+ const [store, setStore] = useState(() => loadLiveStore());
415
+ const key = profileKeyFor({ genre: roll.contract.dominant.key, ageBand: roll.request.creative.audience.ageBand });
416
+ const prof: AudienceProfile = store.profiles[key] ?? { profileKey: key, weights: [], observations: 0, holdoutError: null, lastUpdate: null, history: [] };
417
+ const [obs, setObs] = useState<Omit<AudienceObservation, "featureScores" | "timestamp" | "audienceSegment" | "genre" | "workId">>({ explicitRating: 0.7, completionFraction: 0.8, chapterReturnRate: 0.6, rereadRate: 0.2, recommendationRate: 0.5, endingSatisfaction: 0.7, emotionalResonance: 0.7 });
418
+ const [report, setReport] = useState<string[]>([]);
419
+ const mine = store.observations.filter((o) => o.genre === roll.contract.dominant.key);
420
+ const addObs = () => { const o: AudienceObservation = { ...obs, featureScores: K.dims.map((d) => d.score), timestamp: Date.now(), audienceSegment: roll.contract.audienceSegment, genre: roll.contract.dominant.key, workId: roll.buildId.slice(0, 12) }; const s = { ...store, observations: [...store.observations, o] }; saveLiveStore(s); setStore(s); };
421
+ const runUpdate = () => { const r = updateAudienceProfile(prof, roll.contract.kallosBaseWeights, mine); const s = { ...store, profiles: { ...store.profiles, [key]: r.profile } }; saveLiveStore(s); setStore(s); setReport(r.report); };
422
+ return (
423
+ <div className="space-y-2">
424
+ <Card tone={K.release === "OK" ? "emerald" : "rose"}>
425
+ <Head icon="โœจ" title={`KALLOS v3 โ€” ${K.scale} ${kt ? "(manuscript)" : "(plan-level)"}`} right={`profile ${roll.contract.dominant.kallosKey}`} />
426
+ <div className="mt-1 flex items-baseline gap-2"><span className="text-2xl font-bold text-zinc-900">{K.composite.toFixed(1)}</span><span className="text-[10px] text-zinc-500">/100 ยท raw {K.compositeRaw} ยท regularizers {K.regularizerDelta >= 0 ? "+" : ""}{K.regularizerDelta} (cap ยฑ3)</span><Pill tone={K.release === "OK" ? "emerald" : "rose"}>RELEASE {K.release}</Pill></div>
427
+ <div className="mt-1 flex flex-wrap gap-1">{K.gates.map((g) => <Pill key={g.id} tone={g.pass ? "emerald" : "rose"} title={g.detail}>{g.id} {g.pass ? "pass" : "FAIL"}</Pill>)}</div>
428
+ <div className="mt-1 text-[10px] text-zinc-500">Hard gates: any failure โ‡’ KALLOS = 0 and release BLOCKED (ยง9.3). Weakest measurable dimension: <b>{K.weakest?.label}</b> ({K.weakest?.score}).</div>
429
+ </Card>
430
+ <Card>
431
+ <Head icon="๐Ÿ“Š" title="12 dimensions" right="basis: measured ยท proxy ยท unavailable" />
432
+ <div className="mt-1 space-y-1">{K.dims.map((d) => <Bar key={d.key} v={d.score} label={`${d.key} ยท ${d.label}`} note={`w ${d.weight} ยท ${d.basis.toUpperCase()} ยท ${d.detail}`} />)}</div>
433
+ <div className="mt-1.5 text-[10px] font-bold uppercase tracking-wider text-zinc-400">ยง9.5 regularizers (capped)</div>
434
+ <div className="flex flex-wrap gap-1">{K.regularizers.map((r) => <Pill key={r.name} tone="zinc" title={r.cap}>{r.name} {r.applied >= 0 ? "+" : ""}{r.applied}</Pill>)}</div>
435
+ <div className="mt-1 text-[9px] italic text-zinc-400">{K.caveat}</div>
436
+ </Card>
437
+ <Card>
438
+ <Head icon="๐Ÿ“„" title="Score a manuscript (K3)" />
439
+ <textarea value={text} onChange={(e) => setText(e.target.value)} rows={3} placeholder="Paste manuscript text (โ‰ฅ200 words) to switch TRN/EMO/PRO from proxy to measuredโ€ฆ" className="mt-1 w-full rounded border border-zinc-200 p-2 font-mono text-[10px]" />
440
+ <div className="mt-1 flex gap-2"><Btn onClick={() => setKt(evaluateKallosV3({ run: roll.morph, contract: roll.contract, text, controlledSample: roll.controlledSample, scale: "K3" }))} disabled={text.trim().split(/\s+/).length < 200}>Evaluate K3</Btn>{kt && <Btn tone="ghost" onClick={() => setKt(null)}>back to plan-level</Btn>}</div>
441
+ </Card>
442
+ <Card tone="violet">
443
+ <Head icon="๐Ÿ“ก" title="KALLOS-LIVE โ€” local audience calibration" right={`profile ${key}`} />
444
+ <div className="mt-1 text-[10px] text-zinc-600">Observations are stored locally only. Adaptation needs โ‰ฅ50 observations, moves any weight โ‰ค0.02 per update, keeps hard human dimensions โ‰ฅ0.03, caps any weight at 0.35, and is promoted only if holdout error improves (ยง9.7). No demographic inference โ€” segment is the declared age band.</div>
445
+ <div className="mt-1.5 grid gap-1 sm:grid-cols-4">
446
+ {(Object.keys(obs) as (keyof typeof obs)[]).map((kk) => <label key={kk} className="text-[9.5px] text-zinc-500">{kk}<input type="range" min={0} max={1} step={0.05} value={obs[kk] ?? 0} onChange={(e) => setObs({ ...obs, [kk]: Number(e.target.value) })} className="w-full accent-violet-600" /><span className="font-mono">{(obs[kk] ?? 0).toFixed(2)}</span></label>)}
447
+ </div>
448
+ <div className="mt-1 flex flex-wrap items-center gap-2"><Btn onClick={addObs}>Record observation for this build</Btn><Btn tone="emerald" onClick={runUpdate} disabled={mine.length === 0}>Run profile update</Btn><span className="text-[10px] text-zinc-500">{mine.length} observation(s) for {roll.contract.dominant.key} ยท profile has {prof.observations} ยท last {prof.lastUpdate ? new Date(prof.lastUpdate).toISOString().slice(0, 10) : "never"}</span></div>
449
+ {report.length > 0 && <div className="mt-1 rounded bg-white p-1.5 font-mono text-[10px] text-zinc-700">{report.join("\n")}</div>}
450
+ {prof.weights.length > 0 && <div className="mt-1 flex flex-wrap gap-1">{KALLOS_DIMENSIONS.map((d, i) => <Pill key={d} tone="violet" title={KALLOS_V3_LABELS[d]}>{d} {(prof.weights[i] * 100).toFixed(1)}%</Pill>)}</div>}
451
+ </Card>
452
+ </div>
453
+ );
454
+ }
455
+
456
+ // โ•โ•โ• SCREEN โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
457
+ export function ScreenTab({ roll }: { roll: UnifiedRoll }) {
458
+ const [mode, setMode] = useState<AdaptationMode>("FAITHFUL");
459
+ const [sel, setSel] = useState(0);
460
+ const scenes = useMemo(() => adaptToScreen(roll.morph, roll.nameSet, mode), [roll, mode]);
461
+ const fountain = useMemo(() => toFountain(scenes, roll.blueprint.titleHint), [scenes, roll.blueprint.titleHint]);
462
+ const audits = useMemo(() => runScreenAudits(scenes, fountain, roll.morph, mode), [scenes, fountain, roll.morph, mode]);
463
+ const s = scenes[Math.min(sel, scenes.length - 1)];
464
+ return (
465
+ <div className="space-y-2">
466
+ <Card tone="amber">
467
+ <Head icon="โ—‹" title="Downstream artifact โ€” does NOT affect the chapter prompt" />
468
+ <div className="mt-1 text-[10px] text-amber-900">
469
+ SAGA-SCREEN adapts a <b>finished</b> plan into a screenplay. It is deliberately <b>not</b> part of the
470
+ fiction prompt: the model is writing prose, not a script. Everything below is real and executable
471
+ (Fountain export + SCR-001..010 audits all run), but changing it will not change the story the
472
+ model writes. It is listed as <b>inactive</b> in the pipeline contributions for that reason.
473
+ </div>
474
+ </Card>
475
+ <Card tone="violet">
476
+ <Head icon="๐ŸŽฌ" title="SAGA-SCREEN โ€” adaptation" right={`${scenes.length} scenes ยท ${new Set(scenes.map((x) => x.episode)).size} episode(s)`} />
477
+ <div className="mt-1 flex flex-wrap items-center gap-1">{(["FAITHFUL", "CINEMATIC", "LIMITED_SERIES", "FEATURE", "ANIMATION"] as AdaptationMode[]).map((m) => <button key={m} onClick={() => { setMode(m); setSel(0); }} className={`rounded px-2 py-1 text-[11px] font-semibold ${mode === m ? "bg-indigo-600 text-white" : "bg-zinc-100 text-zinc-600"}`}>{m}</button>)}</div>
478
+ <div className="mt-1 flex flex-wrap gap-1">{audits.map((a) => <Pill key={a.id} tone={a.pass ? "emerald" : "rose"} title={a.detail}>{a.id} {a.name}</Pill>)}</div>
479
+ </Card>
480
+ <div className="grid gap-2 lg:grid-cols-[1fr_1.2fr]">
481
+ <Card>
482
+ <Head icon="๐ŸŽž" title="Scenes" />
483
+ <div className="mt-1 max-h-[380px] overflow-y-auto">{scenes.map((x, i) => <button key={x.sceneId} onClick={() => setSel(i)} className={`block w-full border-b border-zinc-50 px-1 py-0.5 text-left text-[10px] hover:bg-indigo-50 ${i === sel ? "bg-indigo-50" : ""}`}><span className="font-mono text-zinc-400">{x.sceneId}</span> <b>{x.slugline}</b><div className="text-zinc-500">{x.purpose}</div></button>)}</div>
484
+ </Card>
485
+ <div className="space-y-2">
486
+ {s && <Card>
487
+ <Head icon="๐Ÿงพ" title={s.sceneId} right={s.slugline} />
488
+ <KV k="purpose" v={s.purpose} /><KV k="visible goal" v={s.visibleGoal} /><KV k="opposition" v={s.visibleOpposition} /><KV k="blocking" v={s.blocking.join(" ยท ")} />
489
+ <KV k="interiority โ†’" v={s.interiorityMap.length ? s.interiorityMap.map((m) => `${m.proposition} โ‡’ ${m.channel}`).join(" ยท ") : "none in this scene"} />
490
+ <KV k="props / wardrobe" v={`${s.props.join(", ")} ยท ${s.wardrobeContinuity.join(", ")}`} /><KV k="source deltas" v={s.sourceStateDeltas.join(" ยท ") || "โ€”"} />
491
+ {s.adaptationLoss.length > 0 && <KV k="approved loss" v={s.adaptationLoss.map((l) => `${l.proposition} (${l.approvedReason})`).join(" ยท ")} />}
492
+ <details className="mt-1"><summary className="cursor-pointer text-[10px] font-semibold text-indigo-600">ยง13.5 production prompt pack (never in the spec script)</summary><pre className="mt-1 max-h-48 overflow-auto rounded bg-zinc-50 p-1.5 font-mono text-[9px] text-zinc-700">{JSON.stringify(screenplayPromptPack(s), null, 2)}</pre></details>
493
+ </Card>}
494
+ <Card>
495
+ <Head icon="๐Ÿ“œ" title="Fountain spec script" right={`${fountain.length.toLocaleString()} chars`} />
496
+ <pre className="mt-1 max-h-64 overflow-auto whitespace-pre-wrap rounded bg-zinc-50 p-2 font-mono text-[9.5px] text-zinc-800">{fountain.slice(0, 6000)}{fountain.length > 6000 ? "\nโ€ฆ" : ""}</pre>
497
+ <Btn tone="ghost" onClick={() => { try { navigator.clipboard.writeText(fountain); } catch { /* clipboard optional */ } }}>Copy .fountain</Btn>
498
+ </Card>
499
+ </div>
500
+ </div>
501
+ </div>
502
+ );
503
+ }
504
+
505
+ // โ•โ•โ• RELEASE โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
506
+ export function ReleaseTab({ roll }: { roll: UnifiedRoll }) {
507
+ const [withScreen, setWithScreen] = useState(true);
508
+ const result = useMemo(() => {
509
+ const screen = withScreen ? (() => { const scenes = adaptToScreen(roll.morph, roll.nameSet, "FAITHFUL"); const fountain = toFountain(scenes, roll.blueprint.titleHint); return { mode: "FAITHFUL" as AdaptationMode, scenes, fountain, audits: runScreenAudits(scenes, fountain, roll.morph, "FAITHFUL") }; })() : undefined;
510
+ const partial = { buildId: roll.buildId, seedGenome: roll.morph.genome, catalogManifestHash: compileCatalog().manifestHash, rulesVersion: "", genreProfileHash: "", audienceProfileHash: "", grammarPackHash: "", request: roll.request, contract: roll.contract, morph: roll.morph, names: roll.nameSet, kallos: roll.kallosV3, screen, obligations: roll.obligations, controlledSample: roll.controlledSample };
511
+ const tests = runProductionTests(partial, () => runMorphogenesis({ userSeed: roll.seed, contract: roll.contract, spec: roll.spec, lengthMode: roll.morph.lengthMode, budget: roll.morph.budgetTicks, targetWords: roll.blueprint.chapters.reduce((a, c) => a + c.targetWords, 0) }), roll.contract.audienceSegment);
512
+ const gates = runReleaseGates({ ...partial, tests });
513
+ return { tests, ...gates };
514
+ }, [roll, withScreen]);
515
+ const counts = result.tests.reduce<Record<string, number>>((a, t) => { a[t.status] = (a[t.status] ?? 0) + 1; return a; }, {});
516
+ return (
517
+ <div className="space-y-2">
518
+ <Card tone={result.releaseOk ? "emerald" : "amber"}>
519
+ <Head icon="๐Ÿšฆ" title="BUILD-KERNEL โ€” release" right={<label className="flex items-center gap-1"><input type="checkbox" checked={withScreen} onChange={(e) => setWithScreen(e.target.checked)} />include screen audits</label>} />
520
+ <div className="mt-1 font-mono text-[10px] text-zinc-600">BuildID <b>{roll.buildId}</b></div>
521
+ <div className="mt-0.5 font-mono text-[9px] text-zinc-400">SeedGenome {roll.morph.genome} ยท catalog manifest {compileCatalog().manifestHash.slice(0, 16)}โ€ฆ</div>
522
+ <div className="mt-1 flex flex-wrap gap-1">{result.gates.map((g) => <Pill key={g.id} tone={g.pass ? "emerald" : "rose"} title={g.detail}>{g.pass ? "โœ“" : "โœ—"} {g.id}</Pill>)}</div>
523
+ <div className="mt-1 text-[11px] font-bold">{result.releaseOk ? "RELEASE OK โ€” all admissibility gates pass (ยง2.1)" : "RELEASE BLOCKED โ€” see failing gate(s)"}</div>
524
+ </Card>
525
+ <Card>
526
+ <Head icon="๐Ÿงช" title="TEST-001 โ€ฆ TEST-025" right={Object.entries(counts).map(([k, v]) => `${k} ${v}`).join(" ยท ")} />
527
+ <table className="mt-1 w-full text-[10px]"><tbody>{result.tests.map((t) => <tr key={t.id} className="border-b border-zinc-50 align-top"><td className="w-16 py-0.5 font-mono text-zinc-400">{t.id}</td><td className="w-16 py-0.5"><Pill tone={t.status === "PASS" ? "emerald" : t.status === "FAIL" ? "rose" : "zinc"}>{t.status === "NOT_EXECUTABLE" ? "N/E" : t.status}</Pill></td><td className="w-40 py-0.5 font-semibold text-zinc-700">{t.name}</td><td className="py-0.5 text-zinc-500">{t.detail}</td></tr>)}</tbody></table>
528
+ <div className="mt-1 text-[9px] italic text-zinc-400">N/E = not executable in this environment; the blocker is stated, never hidden.</div>
529
+ </Card>
530
+ <Card>
531
+ <Head icon="โš–" title="ยง14 competitor matrix (honest)" />
532
+ <table className="mt-1 w-full text-[10px]"><tbody>{SAGA_V3_COMPETITOR_MATRIX.map((c) => <tr key={c.system} className="border-b border-zinc-50 align-top"><td className="w-32 py-0.5 font-semibold text-zinc-800">{c.system}</td><td className="py-0.5 text-emerald-800">{c.advantages}</td><td className="py-0.5 text-zinc-500">{c.limitations}</td></tr>)}</tbody></table>
533
+ </Card>
534
+ </div>
535
+ );
536
+ }
537
+
538
+ // โ•โ•โ• STUDIO โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
539
+ export function SagaOsPanel() {
540
+ const fictionActive = useFictionActive();
541
+ const [open, setOpen] = useState(false);
542
+ const [tab, setTab] = useState<Tab>("compose");
543
+ const [tick, setTick] = useState(0);
544
+ const [labSeed, setLabSeed] = useState<number | null>(null);
545
+ useEffect(() => { const t = window.setInterval(() => setTick((n) => n + 1), 1500); return () => window.clearInterval(t); }, []);
546
+ const live = liveSeed();
547
+ const seed = labSeed ?? live ?? 12345;
548
+ const roll = useMemo(() => {
549
+ try {
550
+ const bp = rollSage({ seed, cadenceSeed: Number(ls("veritas.sage.cadenceSeed.v1", "")) || seed, lengthId: ls("veritas.sage.scale.v2", "novel") as never, chapters: Number(ls("veritas.sage.chapters.v2", "")) || undefined, wordsPerChapter: Number(ls("veritas.sage.wordsPerChapter.v2", "")) || undefined, forceMacroId: ls("veritas.sage.macro.v2", "") || undefined });
551
+ return unifiedRoll({ seed, blueprint: bp, realityLevel: loadRealityLevel() });
552
+ } catch { return null; }
553
+ // eslint-disable-next-line react-hooks/exhaustive-deps
554
+ }, [seed, tick]);
555
+ const stages = roll ? [
556
+ { id: "compose" as Tab, ok: true, label: `${roll.contract.dominant.key}${roll.contract.secondary.length ? "+" : ""}` },
557
+ { id: "catalog" as Tab, ok: compileCatalog().releaseOk, label: `${compileCatalog().compiledRows} rows` },
558
+ { id: "names" as Tab, ok: roll.nameSet.length > 0, label: `${roll.nameSet.length} names` },
559
+ { id: "evolve" as Tab, ok: roll.morph.attractor !== "BUDGET-TRUNCATED", label: roll.morph.attractor },
560
+ { id: "realize" as Tab, ok: roll.obligations.length > 0, label: `${roll.obligations.length} obligations` },
561
+ { id: "style" as Tab, ok: true, label: roll.styleGenome.proseMode },
562
+ { id: "score" as Tab, ok: roll.kallosV3.release === "OK", label: `KALLOS ${roll.kallosV3.composite}` },
563
+ { id: "screen" as Tab, ok: false, label: "not in prompt" },
564
+ { id: "release" as Tab, ok: roll.kallosV3.release === "OK" && compileCatalog().releaseOk, label: roll.buildId.slice(0, 8) },
565
+ ] : [];
566
+ return (
567
+ <section className={`rounded-xl border ${fictionActive ? "border-zinc-200 bg-zinc-50/80" : "border-zinc-200 bg-zinc-100 opacity-60"} transition-colors`}>
568
+ <button onClick={() => setOpen(!open)} className="flex w-full items-center justify-between px-4 py-2.5 text-left">
569
+ <div className="flex flex-wrap items-center gap-2">
570
+ <span className="text-base">๐Ÿ›</span><span className="text-xs font-bold uppercase tracking-wider text-zinc-700">SAGA-OS Studio</span>
571
+ <span className="rounded bg-amber-100 px-1.5 py-0.5 font-mono text-[10px] text-amber-800">v3 ยท SYSTEM v14</span>
572
+ {!fictionActive && <span className="rounded bg-zinc-300 px-1.5 py-0.5 font-mono text-[10px] text-zinc-600">โ—‹ INACTIVE โ€” switch to Fiction Engine to activate</span>}
573
+ {roll && fictionActive && <><Pill tone={labSeed == null && live != null ? "emerald" : "amber"}>{labSeed == null && live != null ? "โ— LIVE" : "โ—‹ LAB"} seed {seed}</Pill><Pill tone={roll.kallosV3.release === "OK" ? "emerald" : "rose"}>KALLOS {roll.kallosV3.composite} ยท {roll.kallosV3.release}</Pill><Pill tone="zinc">{roll.morph.attractor} โ†’ {roll.morph.endingFamily}</Pill></>}
574
+ </div>
575
+ <span className="text-zinc-400">{open ? "โ–พ" : "โ–ธ"}</span>
576
+ </button>
577
+ {open && (
578
+ <div className={`border-t border-zinc-200 px-4 py-3 ${fictionActive ? "" : "pointer-events-none opacity-60"}`}>
579
+ {!fictionActive && (
580
+ <div className="mb-2 rounded border border-zinc-300 bg-white p-2 text-[11px] text-zinc-600">
581
+ This studio configures the fiction pipeline. It is <b>inactive</b> while Live Research is selected โ€” its genre contract, lattice verdict, names and KALLOS gates do not affect the prompt. Toggle to <b>๐Ÿ“– Fiction Engine</b> in the header to activate it. All controls below are disabled in this state.
582
+ </div>
583
+ )}
584
+ {roll ? (<>
585
+ <div className="mb-2 flex flex-wrap items-center gap-1 rounded-lg border border-zinc-200 bg-white p-1.5">
586
+ {stages.map((s, i) => <button key={s.id} onClick={() => setTab(s.id)} className={`flex items-center gap-1 rounded px-1.5 py-0.5 text-[10px] ${tab === s.id ? "bg-indigo-600 text-white" : "hover:bg-zinc-100"}`}><span className={tab === s.id ? "" : s.ok ? "text-emerald-600" : "text-amber-600"}>{s.ok ? "โ—" : "!"}</span><span className="font-semibold">{TABS[i].label}</span><span className={tab === s.id ? "opacity-80" : "text-zinc-400"}>{s.label}</span>{i < stages.length - 1 && <span className="ml-1 text-zinc-300">โ†’</span>}</button>)}
587
+ <span className="ml-auto flex items-center gap-1 text-[10px] text-zinc-400">lab seed <input type="number" value={labSeed ?? ""} placeholder={String(live ?? "")} onChange={(e) => setLabSeed(e.target.value ? Number(e.target.value) : null)} className="w-24 rounded border border-zinc-200 px-1 py-0.5 font-mono text-[10px]" />{labSeed != null && <button onClick={() => setLabSeed(null)} className="text-indigo-600">sync live</button>}</span>
588
+ </div>
589
+ <div className="mb-3 flex flex-wrap gap-1">{TABS.map((t) => <button key={t.id} onClick={() => setTab(t.id)} className={`rounded-md px-2 py-1 text-[11px] font-semibold ${tab === t.id ? "bg-indigo-600 text-white" : "bg-zinc-100 text-zinc-600 hover:bg-zinc-200"}`} title={t.stage}>{t.icon} {t.label}</button>)}</div>
590
+ {tab === "compose" && <ComposeTab roll={roll} onApplied={() => setTick((n) => n + 1)} />}
591
+ {tab === "catalog" && <CatalogTab />}
592
+ {tab === "names" && <NamesTab roll={roll} />}
593
+ {tab === "evolve" && <EvolveTab roll={roll} />}
594
+ {tab === "realize" && <RealizeTab roll={roll} />}
595
+ {tab === "style" && <SagaStyleTab roll={roll} />}
596
+ {tab === "score" && <ScoreTab roll={roll} />}
597
+ {tab === "screen" && <ScreenTab roll={roll} />}
598
+ {tab === "release" && <ReleaseTab roll={roll} />}
599
+ {tab === "reference" && <div className="space-y-2"><SagaOsReference /><SagaOsV2Panel /></div>}
600
+ </>) : <div className="text-[11px] text-amber-700">Unified roll unavailable for seed {seed}.</div>}
601
+ </div>
602
+ )}
603
+ </section>
604
+ );
605
+ }