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,882 @@
1
+ /**
2
+ * FICTION SYSTEM PANELS (NEW, additive, console-mounted).
3
+ * ---------------------------------------------------------------------------
4
+ * Complete 1:1 functional implementation of:
5
+ * 1. 📥 Fiction Input — Exact per-chapter prompts tapped at generateSynthesizedResponse.
6
+ * 2. 🚀 Story Engine & Recovery — Real start/stop, model switching mid-story,
7
+ * pickup/resumption from interrupted chapters, and live engine sync.
8
+ * 3. 🔬 Diagnostics & Recovery — FM-02 through FM-16 deterministic scanning,
9
+ * auto-fixers, LLM deep diagnosis, and emergency chat unblocker.
10
+ * 4. 🗺 System Map & Runtime — Subsystem tiers & real session call counters.
11
+ * 5. 📚 Manuscript Grade v2 — Standalone weighted 100-point rubric, quote-verified
12
+ * rewrites, and copyable text reports.
13
+ * 6. 🔁 Recursive Revision — Pareto-superior targeted revision loop with
14
+ * weighted-deficit targeting, L0/L1/L2 escalation, 15 negative-space checks,
15
+ * noise margin calibration, and highlighted diff visualization.
16
+ */
17
+ import { useCallback, useEffect, useMemo, useState, type RefObject } from "react";
18
+ import {
19
+ MODELS,
20
+ getModelCallLog,
21
+ pickRandomAvailableModel,
22
+ generateSynthesizedResponse,
23
+ type ModelCallRecord,
24
+ } from "@/lib/models";
25
+ import { gradeManuscriptV2, type ManuscriptGradeReportV2 } from "@/lib/manuscript-grade-v2";
26
+ import {
27
+ runRecursiveRevisionLoop,
28
+ type RevisionLoopResult,
29
+ type RevisionProgressEvent,
30
+ } from "@/lib/ai-revision";
31
+ import { SAGA_SYSTEM_MAP, SYSTEM_TIER_LABEL, SYSTEM_TIER_TONE } from "@/lib/system-map";
32
+ import {
33
+ assembleManuscript,
34
+ attemptAdoptToEngine,
35
+ getActiveModel,
36
+ getCustomTemplateCount,
37
+ getKeys,
38
+ getPersonaSeed,
39
+ getSettings,
40
+ } from "./engineBridge";
41
+ import { copyText } from "./util";
42
+ import { computeWordDiff, formatDiffAsHtml } from "../fiction/diff";
43
+ import { type ContinuityToggles, DEFAULT_CONTINUITY_TOGGLES } from "../fiction/types";
44
+ import { StoryControllerPanel } from "./StoryControllerPanel";
45
+ import { DiagnosticRecoveryPanel } from "./DiagnosticRecoveryPanel";
46
+ import { SeedControlPanel } from "./SeedControlPanel";
47
+ import {
48
+ loadSeedControl,
49
+ saveSeedControl,
50
+ type SeedControl,
51
+ } from "../fiction/seed-control";
52
+ import { type RevisionBudgetOptions, DEFAULT_REVISION_BUDGET } from "@/lib/ai-revision";
53
+
54
+ type FrameRef = RefObject<HTMLIFrameElement | null>;
55
+ type Keys = Partial<Record<string, string>>;
56
+
57
+ const TONE: Record<string, string> = {
58
+ zinc: "bg-zinc-100 text-zinc-700 border-zinc-200",
59
+ teal: "bg-teal-50 text-teal-700 border-teal-200",
60
+ indigo: "bg-indigo-50 text-indigo-700 border-indigo-200",
61
+ violet: "bg-violet-50 text-violet-700 border-violet-200",
62
+ emerald: "bg-emerald-50 text-emerald-700 border-emerald-200",
63
+ amber: "bg-amber-50 text-amber-800 border-amber-200",
64
+ rose: "bg-rose-50 text-rose-700 border-rose-200",
65
+ sky: "bg-sky-50 text-sky-700 border-sky-200",
66
+ };
67
+
68
+ function Pill({ children, tone = "zinc" }: { children: React.ReactNode; tone?: string }) {
69
+ return (
70
+ <span className={`inline-flex items-center rounded-md border px-1.5 py-0.5 text-[10px] font-semibold ${TONE[tone] ?? TONE.zinc}`}>
71
+ {children}
72
+ </span>
73
+ );
74
+ }
75
+ function Head({ icon, title, right }: { icon: string; title: string; right?: React.ReactNode }) {
76
+ return (
77
+ <div className="flex items-center gap-2 border-b border-zinc-200 pb-1">
78
+ <span className="text-sm">{icon}</span>
79
+ <span className="text-[11px] font-bold uppercase tracking-wider text-zinc-700">{title}</span>
80
+ {right != null && <span className="ml-auto text-[10px] text-zinc-400">{right}</span>}
81
+ </div>
82
+ );
83
+ }
84
+ function Card({ children }: { children: React.ReactNode }) {
85
+ return <div className="rounded-lg border border-zinc-200 bg-white p-3">{children}</div>;
86
+ }
87
+
88
+ function wordCount(text: string): number {
89
+ return (text.trim().match(/\S+/g) || []).length;
90
+ }
91
+ function estTokens(text: string): number {
92
+ return Math.ceil((text || "").length / 4);
93
+ }
94
+
95
+ function pickModelWithAllowlist(keys: Keys, exclude: string[], allowlist: string[]) {
96
+ if (!allowlist.length) return pickRandomAvailableModel(keys as Record<string, string>, exclude);
97
+ const withKeys = MODELS.filter((m) => !!keys[m.provider] && allowlist.includes(m.id));
98
+ const pool = withKeys.filter((m) => !exclude.includes(m.id));
99
+ const finalPool = pool.length > 0 ? pool : withKeys;
100
+ if (!finalPool.length) return null;
101
+ return finalPool[Math.floor(Math.random() * finalPool.length)];
102
+ }
103
+
104
+ async function generateWith(
105
+ keys: Keys,
106
+ choice: { provider: string; modelId: string },
107
+ userMessage: string,
108
+ ): Promise<string> {
109
+ const apiKey = keys[choice.provider] ?? "";
110
+ if (!apiKey) throw new Error(`No API key configured for provider "${choice.provider}".`);
111
+ const params = {
112
+ provider: choice.provider as any,
113
+ model: choice.modelId as any,
114
+ apiKey,
115
+ userMessage,
116
+ retrievedWebData: [] as { title: string; url: string; content: string }[],
117
+ conversationHistory: [] as { role: "user" | "assistant"; text: string }[],
118
+ };
119
+ // One honest retry after a 2s backoff: a single empty/rate-limited response
120
+ // (a common Gemini transient) must not abort an entire grade/revision run.
121
+ try {
122
+ return await generateSynthesizedResponse(params);
123
+ } catch {
124
+ await new Promise((r) => setTimeout(r, 2000));
125
+ return generateSynthesizedResponse(params);
126
+ }
127
+ }
128
+
129
+ type Tab = "input" | "story" | "seed" | "diagnostics" | "map" | "grade" | "revise";
130
+
131
+ // ═══ TAB 1 — FICTION INPUT ═══════════════════════════════════════════
132
+ function FictionInputTab({ iframeRef }: { iframeRef: FrameRef }) {
133
+ const [tick, setTick] = useState(0);
134
+ const [showFullSession, setShowFullSession] = useState(false);
135
+ const [openId, setOpenId] = useState<string | null>(null);
136
+ const [showWorkspaceCalls, setShowWorkspaceCalls] = useState(false);
137
+
138
+ useEffect(() => {
139
+ const t = window.setInterval(() => setTick((n) => n + 1), 1000);
140
+ return () => window.clearInterval(t);
141
+ }, []);
142
+
143
+ const chapterCalls = useMemo(() => getModelCallLog().filter((r) => r.tag === "fiction-chapter"), [tick]);
144
+ const workspaceCalls = useMemo(
145
+ () => getModelCallLog().filter((r) => r.tag === "manuscript-grade-v2" || r.tag === "ai-revision-v2"),
146
+ [tick],
147
+ );
148
+
149
+ const displayed = useMemo(() => {
150
+ if (showFullSession || chapterCalls.length === 0) return chapterCalls;
151
+ let startIdx = 0;
152
+ for (let i = chapterCalls.length - 1; i > 0; i -= 1) {
153
+ if (chapterCalls[i].ts - chapterCalls[i - 1].ts > 120_000) {
154
+ startIdx = i;
155
+ break;
156
+ }
157
+ }
158
+ return chapterCalls.slice(startIdx);
159
+ }, [chapterCalls, showFullSession]);
160
+
161
+ const frame = iframeRef.current;
162
+ const settings = getSettings(frame);
163
+ const seed = getPersonaSeed(frame);
164
+ const model = getActiveModel(frame);
165
+
166
+ return (
167
+ <div className="space-y-3 text-[13px]">
168
+ <Card>
169
+ <Head icon="📥" title="Fiction Input" right={chapterCalls.length ? `${displayed.length} of ${chapterCalls.length} call(s)` : "no chapter calls recorded yet"} />
170
+ <div className="mt-2 space-y-2 text-[12px] text-zinc-600">
171
+ <p>
172
+ Single source of truth: this is the literal <code>userMessage</code> argument passed to the model for each call — tapped
173
+ after style persona + template override + the SAGE contract are already concatenated.
174
+ </p>
175
+ <div className="grid grid-cols-2 gap-x-3 gap-y-0.5 rounded-md border border-zinc-100 bg-zinc-50/60 p-2 font-mono text-[11px]">
176
+ <span className="text-zinc-500">persona seed</span><span className="font-semibold text-zinc-800">{seed != null ? seed : "(none)"}</span>
177
+ <span className="text-zinc-500">active model</span><span className="font-semibold text-zinc-800">{model || "(default)"}</span>
178
+ <span className="text-zinc-500">template</span><span className="font-semibold text-zinc-800">{String(settings.activeTemplateId ?? "—")}{settings.templateEnabled ? " (on)" : ""}</span>
179
+ <span className="text-zinc-500">style mode</span><span className="font-semibold text-zinc-800">{String(settings.styleMode ?? "—")}</span>
180
+ <span className="text-zinc-500">custom templates</span><span className="font-semibold text-zinc-800">{getCustomTemplateCount(frame)}</span>
181
+ </div>
182
+ <div className="flex gap-2">
183
+ {chapterCalls.length > 1 && (
184
+ <button onClick={() => setShowFullSession((v) => !v)} className="rounded bg-zinc-100 px-2 py-0.5 text-[11px] font-semibold text-zinc-600 hover:bg-zinc-200">
185
+ {showFullSession ? "show latest run only" : "show full session"}
186
+ </button>
187
+ )}
188
+ {workspaceCalls.length > 0 && (
189
+ <button onClick={() => setShowWorkspaceCalls((v) => !v)} className="rounded bg-violet-100 px-2 py-0.5 text-[11px] font-semibold text-violet-700 hover:bg-violet-200">
190
+ {showWorkspaceCalls ? "hide workspace calls" : `show workspace calls (${workspaceCalls.length})`}
191
+ </button>
192
+ )}
193
+ </div>
194
+ </div>
195
+ </Card>
196
+
197
+ {showWorkspaceCalls && (
198
+ <div className="space-y-1">
199
+ {workspaceCalls.map((rec) => {
200
+ const isOpen = openId === rec.id;
201
+ return (
202
+ <div key={rec.id} className="rounded-lg border border-zinc-200 bg-zinc-50/60">
203
+ <button
204
+ type="button"
205
+ onClick={() => setOpenId(isOpen ? null : rec.id)}
206
+ className="flex w-full items-center gap-2 px-2 py-1.5 text-left text-[11px] font-bold text-zinc-700"
207
+ >
208
+ <Pill tone={rec.tag === "manuscript-grade-v2" ? "violet" : "rose"}>{rec.tag}</Pill>
209
+ <Pill tone="sky">{rec.provider}/{rec.model}</Pill>
210
+ <span className="font-mono text-[9px] text-zinc-400">{rec.userMessage.length.toLocaleString()} chars</span>
211
+ <span className="ml-auto text-zinc-400">{new Date(rec.ts).toLocaleTimeString()}</span>
212
+ <span>{isOpen ? "▲" : "▼"}</span>
213
+ </button>
214
+ {isOpen && (
215
+ <pre className="mx-2 mb-2 max-h-[320px] overflow-auto whitespace-pre-wrap rounded border border-zinc-200 bg-white p-2 font-mono text-[9.5px] leading-snug text-zinc-800">
216
+ {rec.userMessage}
217
+ </pre>
218
+ )}
219
+ </div>
220
+ );
221
+ })}
222
+ </div>
223
+ )}
224
+
225
+ {displayed.map((rec: ModelCallRecord, i: number) => {
226
+ const isOpen = openId === rec.id;
227
+ const unifiedDirectivePresent = /REMINDER — REALITY LEVEL \d\/6/.test(rec.userMessage);
228
+ return (
229
+ <div key={rec.id} className="rounded-lg border border-violet-200 bg-violet-50/40">
230
+ <button
231
+ type="button"
232
+ onClick={() => setOpenId(isOpen ? null : rec.id)}
233
+ className="flex w-full items-center gap-2 px-3 py-2 text-left text-[11px] font-bold text-violet-800"
234
+ >
235
+ <span>Chapter call #{i + 1}</span>
236
+ <Pill tone={rec.ok ? "emerald" : "rose"}>{rec.ok ? "ok" : "error"}</Pill>
237
+ <Pill tone="sky">{rec.provider}/{rec.model}</Pill>
238
+ <Pill tone={unifiedDirectivePresent ? "teal" : "amber"}>{unifiedDirectivePresent ? "unified directive verified in prompt" : "unified directive marker not found"}</Pill>
239
+ <span className="font-mono text-[9px] text-violet-500">{rec.userMessage.length.toLocaleString()} chars · ~{estTokens(rec.userMessage).toLocaleString()} tok</span>
240
+ <span className="ml-auto text-zinc-400">{new Date(rec.ts).toLocaleTimeString()}</span>
241
+ <span>{isOpen ? "▲" : "▼"}</span>
242
+ </button>
243
+ {isOpen && (
244
+ <div className="space-y-1 border-t border-violet-200 px-3 py-2">
245
+ <pre className="max-h-[420px] overflow-auto whitespace-pre-wrap rounded border border-violet-200 bg-white p-2 font-mono text-[9.5px] leading-snug text-violet-950">
246
+ {rec.userMessage}
247
+ </pre>
248
+ <div className="text-[10px] italic text-violet-600">
249
+ Response: {rec.responseChars.toLocaleString()} chars in {rec.durationMs.toLocaleString()}ms.
250
+ {rec.error ? ` Error: ${rec.error}` : ""}
251
+ </div>
252
+ </div>
253
+ )}
254
+ </div>
255
+ );
256
+ })}
257
+ </div>
258
+ );
259
+ }
260
+
261
+ // ═══ TAB 2 — SYSTEM MAP & LIVE RUNTIME ═══════════════════════════════
262
+ function SystemMapTab({ iframeRef }: { iframeRef: FrameRef }) {
263
+ const [tick, setTick] = useState(0);
264
+ useEffect(() => {
265
+ const t = window.setInterval(() => setTick((n) => n + 1), 1500);
266
+ return () => window.clearInterval(t);
267
+ }, []);
268
+
269
+ const callLog = useMemo(() => getModelCallLog(), [tick]);
270
+ const counts = useMemo(() => {
271
+ const c: Record<string, number> = {};
272
+ for (const r of callLog) c[r.tag] = (c[r.tag] ?? 0) + 1;
273
+ return c;
274
+ }, [callLog]);
275
+ const seed = getPersonaSeed(iframeRef.current);
276
+ const chapters = useMemo(() => assembleManuscript(iframeRef.current).chapters, [tick, iframeRef]);
277
+
278
+ function liveEvidenceFor(entryName: string): string | null {
279
+ const key = entryName.toLowerCase();
280
+ if (key.includes("fiction input")) return `${counts["fiction-chapter"] ?? 0} chapter call(s) logged this session`;
281
+ if (key.includes("grade v2")) return `${counts["manuscript-grade-v2"] ?? 0} grading call(s) this session`;
282
+ if (key.includes("revision")) return `${counts["ai-revision-v2"] ?? 0} revision call(s) this session`;
283
+ if (key.includes("legacy grade")) return `${counts["manuscript-grade-legacy"] ?? 0} legacy grading call(s) this session`;
284
+ if (key.includes("blueprint") || key.includes("unified roll") || key.includes("persona") || key.includes("saga-os v1") || key.includes("saga-os v2") || key.includes("saga-os v3") || key.includes("saga-os v4"))
285
+ return seed != null ? `ACTIVE this roll — seed ${seed}` : "engine seed active (persona seed set)";
286
+ if (key.includes("reality") || key.includes("style judge")) return "audited post-generation (engine-internal; counts not exposed by prebuilt bundle)";
287
+ return null;
288
+ }
289
+
290
+ return (
291
+ <div className="space-y-3 text-[13px]">
292
+ <Card>
293
+ <Head icon="🗺" title="SAGA OS Studio — system map" right={seed != null ? `live roll: seed ${seed}` : "no live roll available"} />
294
+ <div className="mt-2 text-[12px] text-zinc-600">
295
+ Every subsystem below is honestly tiered by how it actually participates. Automatic systems recompute from the SAME
296
+ localStorage settings the engine uses, so &quot;ACTIVE this roll&quot; here is the same fact shown there — not a second
297
+ opinion.
298
+ </div>
299
+ </Card>
300
+ <Card>
301
+ <Head icon="📊" title="Live call counters (this session)" />
302
+ <div className="mt-2 grid grid-cols-2 gap-1 sm:grid-cols-3">
303
+ {[
304
+ ["Fiction chapters", counts["fiction-chapter"] ?? 0, "sky"],
305
+ ["Legacy grade (1-10)", counts["manuscript-grade-legacy"] ?? 0, "zinc"],
306
+ ["Grade v2 (weighted)", counts["manuscript-grade-v2"] ?? 0, "violet"],
307
+ ["Revision v2 calls", counts["ai-revision-v2"] ?? 0, "rose"],
308
+ ["Live research", counts["live-research"] ?? 0, "sky"],
309
+ ["Chapters observed", chapters, "teal"],
310
+ ["Style/reality audits", "(engine-internal)", "amber"],
311
+ ["Total model calls", callLog.length, "zinc"],
312
+ ].map(([label, val, tone]) => (
313
+ <div key={label as string} className="rounded border border-zinc-200 bg-zinc-50 p-1 text-center">
314
+ <div className="text-[9px] uppercase text-zinc-400">{label}</div>
315
+ <div className="text-sm font-bold text-zinc-800">
316
+ <Pill tone={tone as string}>{String(val)}</Pill>
317
+ </div>
318
+ </div>
319
+ ))}
320
+ </div>
321
+ </Card>
322
+ <div className="space-y-2">
323
+ {SAGA_SYSTEM_MAP.map((e) => {
324
+ const evidence = liveEvidenceFor(e.name);
325
+ return (
326
+ <div key={e.id} className="rounded-lg border border-zinc-200 bg-white p-2">
327
+ <div className="flex flex-wrap items-center gap-1">
328
+ <span className="text-[11px] font-bold text-zinc-800">{e.name}</span>
329
+ <Pill tone={SYSTEM_TIER_TONE[e.tier]}>{SYSTEM_TIER_LABEL[e.tier]}</Pill>
330
+ </div>
331
+ <div className="mt-0.5 text-[11px] text-zinc-500">{e.description}</div>
332
+ <div className="mt-0.5 font-mono text-[9px] text-zinc-400">wired via: {e.wiredVia}</div>
333
+ {evidence && <div className="mt-0.5 text-[10px] font-semibold text-teal-700">runtime: {evidence}</div>}
334
+ </div>
335
+ );
336
+ })}
337
+ </div>
338
+ </div>
339
+ );
340
+ }
341
+
342
+ // ═══ TAB 3 — MANUSCRIPT GRADE v2 ═════════════════════════════════════
343
+ type GradeV2State =
344
+ | { status: "idle" }
345
+ | { status: "grading"; modelLabel: string }
346
+ | { status: "done"; report: ManuscriptGradeReportV2 }
347
+ | { status: "error"; error: string };
348
+
349
+ function GradeV2Tab({ manuscript, keys, allowlist }: { manuscript: string | null; keys: Keys; allowlist: string[] }) {
350
+ const [state, setState] = useState<GradeV2State>({ status: "idle" });
351
+
352
+ const onGrade = useCallback(async () => {
353
+ if (!manuscript) return;
354
+ const choice = pickModelWithAllowlist(keys, [], allowlist);
355
+ if (!choice) {
356
+ setState({ status: "error", error: "No model with a configured API key is available. Add a key in Settings." });
357
+ return;
358
+ }
359
+ setState({ status: "grading", modelLabel: `${choice.provider}/${choice.id}` });
360
+ try {
361
+ const report = await gradeManuscriptV2(
362
+ manuscript,
363
+ { provider: choice.provider, modelId: choice.id },
364
+ (userMessage) => generateWith(keys, { provider: choice.provider, modelId: choice.id }, userMessage),
365
+ );
366
+ setState({ status: "done", report });
367
+ } catch (err) {
368
+ setState({ status: "error", error: err instanceof Error ? err.message : String(err) });
369
+ }
370
+ }, [manuscript, keys, allowlist]);
371
+
372
+ const copyTable = useCallback(() => {
373
+ if (state.status !== "done") return;
374
+ try {
375
+ copyText(state.report.tableText);
376
+ } catch {
377
+ /* clipboard optional */
378
+ }
379
+ }, [state]);
380
+
381
+ return (
382
+ <div className="space-y-3 text-[13px]">
383
+ <Card>
384
+ <Head icon="📚" title="Manuscript Grade v2 — weighted 100-point rubric" right={manuscript ? `${wordCount(manuscript).toLocaleString()} words available` : "no manuscript yet"} />
385
+ <div className="mt-2 text-[12px] text-zinc-600">
386
+ Grades the full manuscript (the entire book generated so far, not just the last chapter) with a fresh, randomly-selected
387
+ model from your configured keys.
388
+ </div>
389
+ <button
390
+ type="button"
391
+ disabled={!manuscript || state.status === "grading"}
392
+ onClick={onGrade}
393
+ className="mt-2 rounded bg-violet-700 px-3 py-1.5 text-[11px] font-bold text-white hover:bg-violet-800 disabled:opacity-40"
394
+ >
395
+ {state.status === "grading" ? `Grading with ${state.modelLabel}…` : "📋 Run standalone manuscript grading report"}
396
+ </button>
397
+ {state.status === "error" && <div className="mt-2 text-[11px] font-semibold text-rose-700">Grading failed: {state.error}</div>}
398
+ </Card>
399
+
400
+ {state.status === "done" && (
401
+ <div className="space-y-3">
402
+ <Card>
403
+ <Head icon="🧮" title="Weighted composite" right={<button onClick={copyTable} className="rounded bg-zinc-100 px-2 py-0.5 text-[10px] font-semibold text-zinc-600">copy table as text</button>} />
404
+ <div className="mt-2 flex flex-wrap items-center gap-2">
405
+ <span className="text-xl font-black text-zinc-900">{state.report.weightedComposite.toFixed(2)}/100</span>
406
+ <Pill tone="violet">GRADE: {state.report.compositeLetterBand}</Pill>
407
+ <span className="text-[10px] text-zinc-500">judged by {state.report.provider}/{state.report.modelId}{state.report.truncated ? ` · manuscript truncated from ${state.report.truncatedFromChars.toLocaleString()} chars` : ""}</span>
408
+ </div>
409
+ {state.report.parseError && (
410
+ <div className="mt-2 rounded border border-amber-200 bg-amber-50 p-1 text-[11px] text-amber-800">
411
+ Judge response could not be parsed as structured JSON ({state.report.parseError}); categories shown as neutral 50s. Raw text preserved below.
412
+ </div>
413
+ )}
414
+ <div className="mt-2 space-y-2">
415
+ {state.report.categories.map((c) => (
416
+ <div key={c.key} className="rounded border border-zinc-100 p-1">
417
+ <div className="flex items-center gap-1">
418
+ <span className="w-64 shrink-0 text-[11px] font-bold text-zinc-700">{c.label} ({c.weightPct}%)</span>
419
+ <div className="h-1 flex-1 rounded-full bg-zinc-100">
420
+ <div className="h-full rounded-full" style={{ width: `${c.score}%`, backgroundColor: `hsl(${Math.round((c.score / 100) * 120)},55%,50%)` }} />
421
+ </div>
422
+ <span className="w-16 text-right font-mono text-[11px] text-zinc-700">{c.score}</span>
423
+ <span className="w-24 text-right font-mono text-[10px]">w={c.weightedScore.toFixed(2)}</span>
424
+ <Pill>{c.letterBand} — {c.descriptor}</Pill>
425
+ </div>
426
+ {c.rationale && <div className="mt-0.5 text-[11px] text-zinc-500">{c.rationale}</div>}
427
+ </div>
428
+ ))}
429
+ </div>
430
+ </Card>
431
+ <Card>
432
+ <Head icon="🔎" title="Additional criteria (diagnostic, not weighted)" />
433
+ <div className="mt-1 text-[9px] italic text-amber-700">{state.report.additionalCriteriaSourceNote}</div>
434
+ <div className="mt-1 grid grid-cols-2 gap-1 sm:grid-cols-2">
435
+ {state.report.additionalCriteria.map((a) => (
436
+ <div key={a.key} className="rounded border border-zinc-100 p-1">
437
+ <div className="flex items-center gap-1">
438
+ <span className="text-[11px] font-semibold text-zinc-700">{a.label}</span>
439
+ <span className="font-mono text-[11px] text-zinc-700">{a.score}</span>
440
+ </div>
441
+ {a.note && <div className="mt-0.5 text-[10px] text-zinc-500">{a.note}</div>}
442
+ </div>
443
+ ))}
444
+ </div>
445
+ </Card>
446
+ <Card>
447
+ <Head icon="✏️" title="Exact rewrite suggestions" right={`${state.report.rewriteSuggestions.filter((s) => s.verified).length}/${state.report.rewriteSuggestions.length} quote-verified`} />
448
+ <div className="mt-1 space-y-1">
449
+ {state.report.rewriteSuggestions.length === 0 && <div className="text-[11px]">No rewrite suggestions returned.</div>}
450
+ {state.report.rewriteSuggestions.map((s) => (
451
+ <div key={s.id} className="rounded border border-zinc-100 p-1">
452
+ <div className="flex flex-wrap items-center gap-1">
453
+ <Pill>{s.category}</Pill>
454
+ <span className="text-[10px] font-semibold text-zinc-700">{s.location}</span>
455
+ <Pill tone={s.verified ? "emerald" : "rose"}>{s.verified ? "quote verified" : "quote NOT found verbatim — unverified"}</Pill>
456
+ </div>
457
+ <div className="mt-1 rounded bg-zinc-50/60 p-1 text-[11px] text-zinc-900">
458
+ <b>Original:</b> &quot;{s.originalQuote}&quot;
459
+ </div>
460
+ <div className="mt-0.5 rounded bg-emerald-50/60 p-1 text-[11px] text-emerald-900">
461
+ <b>Rewrite:</b> &quot;{s.rewrittenQuote}&quot;
462
+ </div>
463
+ <div className="mt-0.5 text-[11px] text-zinc-500"><b>Why:</b> {s.rationale}</div>
464
+ </div>
465
+ ))}
466
+ </div>
467
+ </Card>
468
+ <Card>
469
+ <Head icon="🧾" title="Formatted report (copyable plain text)" />
470
+ <pre className="mt-1 max-h-64 overflow-auto whitespace-pre-wrap rounded bg-zinc-50 p-2 font-mono text-[9.5px] leading-snug text-zinc-700">{state.report.tableText}</pre>
471
+ {state.report.parseError && (
472
+ <pre className="mt-1 max-h-48 overflow-auto whitespace-pre-wrap rounded border border-amber-200 bg-amber-50/50 p-2 font-mono text-[9px] text-amber-900">{state.report.rawJudgeText}</pre>
473
+ )}
474
+ </Card>
475
+ </div>
476
+ )}
477
+ </div>
478
+ );
479
+ }
480
+
481
+ // ═══ TAB 4 — RECURSIVE REVISION (WITH HIGHLIGHTED DIFFS) ══════════════
482
+ interface RevisionState {
483
+ status: "idle" | "running" | "done" | "error";
484
+ events: RevisionProgressEvent[];
485
+ result: RevisionLoopResult | null;
486
+ error?: string;
487
+ }
488
+
489
+ function RevisionTab({
490
+ manuscript,
491
+ keys,
492
+ allowlist,
493
+ iframeRef,
494
+ setManuscript,
495
+ }: {
496
+ manuscript: string | null;
497
+ keys: Keys;
498
+ allowlist: string[];
499
+ iframeRef: FrameRef;
500
+ setManuscript: (s: string) => void;
501
+ }) {
502
+ const [targetScore, setTargetScore] = useState(85);
503
+ const [maxIterations, setMaxIterations] = useState(4);
504
+ const [budget, setBudget] = useState<RevisionBudgetOptions>({ ...DEFAULT_REVISION_BUDGET });
505
+ const [state, setState] = useState<RevisionState>({ status: "idle", events: [], result: null });
506
+ const [adoptMsg, setAdoptMsg] = useState("");
507
+ const [showDiffView, setShowDiffView] = useState(true);
508
+
509
+ const pickModelCb = useCallback(
510
+ (exclude: string[]) => {
511
+ // Prefer allowlist; fall back to full keyed roster so a blank allowlist never
512
+ // soft-locks the revision button (honest power: any configured key works).
513
+ const choice =
514
+ pickModelWithAllowlist(keys, exclude, allowlist) ||
515
+ pickRandomAvailableModel(keys as Record<string, string>, exclude);
516
+ return choice ? { provider: choice.provider, modelId: choice.id } : null;
517
+ },
518
+ [keys, allowlist],
519
+ );
520
+
521
+ const generate = useCallback(
522
+ async (userMessage: string, choice: { provider: string; modelId: string }) => generateWith(keys, choice, userMessage),
523
+ [keys],
524
+ );
525
+
526
+ const onRun = useCallback(async () => {
527
+ if (!manuscript?.trim()) {
528
+ setState({ status: "error", events: [], result: null, error: "No manuscript loaded. Load from engine or paste text first." });
529
+ return;
530
+ }
531
+ if (!Object.values(keys).some((v) => !!v && String(v).trim())) {
532
+ setState({ status: "error", events: [], result: null, error: "No API key configured. Add a Gemini key in Settings or the console header." });
533
+ return;
534
+ }
535
+ setAdoptMsg("");
536
+ setState({ status: "running", events: [], result: null });
537
+ try {
538
+ const result = await runRecursiveRevisionLoop({
539
+ manuscriptText: manuscript,
540
+ targetScore,
541
+ maxIterations,
542
+ pickModel: pickModelCb,
543
+ generate,
544
+ onProgress: (e) => setState((s) => ({ ...s, events: [...s.events, e] })),
545
+ budget,
546
+ });
547
+ setState((s) => ({ ...s, status: result.status === "error" ? "error" : "done", result, error: result.error }));
548
+ } catch (err) {
549
+ setState({
550
+ status: "error",
551
+ events: [],
552
+ result: null,
553
+ error: err instanceof Error ? err.message : String(err),
554
+ });
555
+ }
556
+ }, [manuscript, targetScore, maxIterations, pickModelCb, generate, budget, keys]);
557
+
558
+ const canAdopt = state.status === "done" && !!state.result && !!state.result.finalText;
559
+
560
+ const onAdopt = useCallback(() => {
561
+ if (!canAdopt || !state.result) return;
562
+ const text = state.result.finalText;
563
+ setManuscript(text);
564
+ const attempt = attemptAdoptToEngine(iframeRef.current, text);
565
+ copyText(text).then((copied) => {
566
+ setAdoptMsg(
567
+ `${attempt.ok ? "✓ " + attempt.detail : "⚠ engine write-back unavailable: " + attempt.detail}. Working manuscript updated.${copied ? " Full text also copied to clipboard." : ""}`,
568
+ );
569
+ });
570
+ }, [canAdopt, state.result, setManuscript, iframeRef]);
571
+
572
+ // Compute visual word diff between original manuscript and revised draft
573
+ const diffChunks = useMemo(() => {
574
+ if (!manuscript || !state.result?.finalText) return [];
575
+ return computeWordDiff(manuscript, state.result.finalText);
576
+ }, [manuscript, state.result?.finalText]);
577
+
578
+ const diffHtml = useMemo(() => formatDiffAsHtml(diffChunks), [diffChunks]);
579
+
580
+ return (
581
+ <div className="space-y-3 text-[13px]">
582
+ <Card>
583
+ <Head icon="🔁" title="Pareto-Superior Targeted Recursive Revision" right={manuscript ? `${wordCount(manuscript).toLocaleString()} words available` : "no manuscript yet"} />
584
+ <div className="mt-2 text-[12px] text-zinc-600">
585
+ Target-score recursive loop with <b>Weighted-Deficit Targeting</b> (solves the Plot/Character 40% deficit trap), <b>L0→L1→L2 Escalation</b>,
586
+ and a calibrated <b>Noise Margin (+0.5 pt)</b> so stochastic variance never ratchets drafts downhill.
587
+ </div>
588
+ <div className="mt-2 flex flex-wrap items-center gap-3">
589
+ <label className="flex items-center gap-1 text-[11px] text-zinc-700">
590
+ Target score
591
+ <input
592
+ type="number"
593
+ min={1}
594
+ max={100}
595
+ value={targetScore}
596
+ onChange={(e) => setTargetScore(Math.max(1, Math.min(100, Number(e.target.value) || 85)))}
597
+ className="w-16 rounded border border-zinc-300 px-1 py-0.5 font-mono text-[11px]"
598
+ />
599
+ /100
600
+ </label>
601
+ <label className="flex items-center gap-1 text-[11px] text-zinc-700">
602
+ Max iterations
603
+ <input
604
+ type="number"
605
+ min={1}
606
+ max={8}
607
+ value={maxIterations}
608
+ onChange={(e) => setMaxIterations(Math.max(1, Math.min(8, Number(e.target.value) || 4)))}
609
+ className="w-14 rounded border border-zinc-300 px-1 py-0.5 font-mono text-[11px]"
610
+ />
611
+ </label>
612
+ <label className="flex items-center gap-1 text-[11px] text-zinc-700" title="Max fraction of the manuscript that may change per pass (hard cap)">
613
+ Rewrite budget
614
+ <input
615
+ type="range" min={5} max={50} step={1} value={budget.maxRewritePct}
616
+ onChange={(e) => setBudget((b) => ({ ...b, maxRewritePct: Number(e.target.value) }))}
617
+ className="w-24 accent-rose-600"
618
+ />
619
+ <span className="font-mono font-bold">{budget.maxRewritePct}%</span>
620
+ </label>
621
+ <label className="flex items-center gap-1 text-[11px] text-zinc-700" title="Longest single contiguous replacement (standard 10 paragraphs; max 1 chapter)">
622
+ Max replacement
623
+ <input
624
+ type="range" min={1} max={30} step={1} value={budget.maxReplacementParagraphs}
625
+ onChange={(e) => setBudget((b) => ({ ...b, maxReplacementParagraphs: Number(e.target.value) }))}
626
+ className="w-24 accent-rose-600"
627
+ />
628
+ <span className="font-mono font-bold">{budget.maxReplacementParagraphs}¶</span>
629
+ </label>
630
+ <label className="flex items-center gap-1 text-[11px] text-zinc-700" title="Permit one full-chapter replacement as the absolute maximum">
631
+ <input
632
+ type="checkbox" checked={budget.allowChapterReplacement}
633
+ onChange={(e) => setBudget((b) => ({ ...b, allowChapterReplacement: e.target.checked }))}
634
+ className="h-3.5 w-3.5 accent-rose-600"
635
+ />
636
+ allow 1-chapter max
637
+ </label>
638
+ <button
639
+ type="button"
640
+ disabled={!manuscript || state.status === "running"}
641
+ onClick={onRun}
642
+ className="rounded bg-rose-700 px-3 py-1.5 text-[11px] font-bold text-white hover:bg-rose-800 disabled:opacity-40"
643
+ >
644
+ {state.status === "running" ? "Running Targeted Revision…" : "🚀 Start Targeted Revision"}
645
+ </button>
646
+ </div>
647
+ </Card>
648
+
649
+ {state.events.length > 0 && (
650
+ <Card>
651
+ <Head icon="📡" title="Live status &amp; Diagnostics" right={state.status} />
652
+ <div className="mt-1 max-h-56 space-y-0.5 overflow-y-auto font-mono text-[9.5px] text-zinc-700">
653
+ {state.events.map((e, i) => (
654
+ <div key={i}>
655
+ {e.phase === "grading-initial" && "→ grading initial manuscript with fresh context…"}
656
+ {e.phase === "grading-initial-done" && `✓ initial score: ${e.report.weightedComposite.toFixed(2)}/100 (${e.report.compositeLetterBand})`}
657
+ {e.phase === "revising" && `→ iteration ${e.iteration} [${e.escalation}]: targeting ${e.targetCategory} with ${e.model.provider}/${e.model.modelId}…`}
658
+ {e.phase === "revising-done" && `✓ iteration ${e.iteration}: ~${e.changedRatioPct}% changed${e.stagnationDiagnosis ? ` — ${e.stagnationDiagnosis}` : ""}${e.anomaly ? ` (⚠ ${e.anomaly})` : ""}`}
659
+ {e.phase === "grading" && `→ iteration ${e.iteration}: independent re-grading with ${e.model.provider}/${e.model.modelId}…`}
660
+ {e.phase === "grading-done" && `✓ iteration ${e.iteration}: score ${e.report.weightedComposite.toFixed(2)}/100 (${e.report.compositeLetterBand}) -> ${e.accepted ? "PROMOTED ✅" : "HELD BACK (Best-Kept) 🛡️: " + (e.rejectionReason || "")}`}
661
+ {e.phase === "error" && `✗ ERROR: ${e.message}`}
662
+ {e.phase === "done" && `● loop finished — status: ${e.result.status}`}
663
+ </div>
664
+ ))}
665
+ </div>
666
+ </Card>
667
+ )}
668
+
669
+ {state.result && (
670
+ <Card>
671
+ <Head icon="🏁" title="Revision Result &amp; Diff Comparison" right={state.result.status} />
672
+ <div className="mt-1 flex flex-wrap items-center gap-1 text-[11px]">
673
+ {state.result.initialReport && <Pill tone="zinc">start: {state.result.initialReport.weightedComposite.toFixed(2)}</Pill>}
674
+ {state.result.finalReport && (
675
+ <Pill tone={state.result.finalReport.weightedComposite >= targetScore ? "emerald" : "amber"}>
676
+ final: {state.result.finalReport.weightedComposite.toFixed(2)}/100 ({state.result.finalReport.compositeLetterBand})
677
+ </Pill>
678
+ )}
679
+ <Pill>{state.result.iterations.length} revision iteration(s)</Pill>
680
+ {state.result.plateauDetected && <Pill tone="amber">Plateau Escalation Triggered</Pill>}
681
+ </div>
682
+
683
+ {state.result.iterations.length > 0 && (
684
+ <div className="mt-2 space-y-1">
685
+ {state.result.iterations.map((it) => (
686
+ <div key={it.iteration} className="rounded border border-zinc-100 p-1.5 text-[11px] text-zinc-700 bg-zinc-50/50">
687
+ <div className="flex items-center gap-2">
688
+ <b>Iteration {it.iteration} ({it.escalation})</b>
689
+ <span>{it.beforeScore.toFixed(2)} → {it.afterScore.toFixed(2)}</span>
690
+ <Pill tone={it.accepted ? "emerald" : "zinc"}>{it.accepted ? "Promoted" : "Rejected"}</Pill>
691
+ <span className="text-[10px] text-zinc-500">revised: {it.reviseModel.modelId} · graded: {it.gradeModel.modelId} · ~{it.changedRatioPct}% diff</span>
692
+ </div>
693
+ {it.unitCounts && (
694
+ <div className="mt-0.5 font-mono text-[10px] text-zinc-500">
695
+ units — word:{it.unitCounts.word} phrase:{it.unitCounts.phrase} sent:{it.unitCounts.sentence} ¶:{it.unitCounts.paragraph} page:{it.unitCounts.page} ch:{it.unitCounts.chapter}
696
+ {typeof it.budgetUsedPct === "number" && <span> · budget used {it.budgetUsedPct}%</span>}
697
+ </div>
698
+ )}
699
+ {it.rejectionReason && <div className="mt-0.5 text-[10px] text-zinc-500 italic">{it.rejectionReason}</div>}
700
+ {it.stagnationDiagnosis && <div className="mt-0.5 text-[10px] text-indigo-700 font-medium">{it.stagnationDiagnosis}</div>}
701
+ {it.anomaly && <div className="mt-0.5 text-amber-700">⚠ {it.anomaly}</div>}
702
+ </div>
703
+ ))}
704
+ </div>
705
+ )}
706
+
707
+ {/* Diff view toggle */}
708
+ <div className="mt-3 flex items-center justify-between border-t border-zinc-100 pt-2">
709
+ <div className="flex items-center gap-2">
710
+ <span className="text-xs font-bold text-zinc-800">Draft Inspector:</span>
711
+ <button
712
+ type="button"
713
+ onClick={() => setShowDiffView(!showDiffView)}
714
+ className="rounded border border-zinc-300 bg-zinc-100 px-2 py-0.5 text-xs font-semibold text-zinc-700 hover:bg-zinc-200"
715
+ >
716
+ {showDiffView ? "Switch to Plain Text" : "Switch to Highlighted Diffs"}
717
+ </button>
718
+ </div>
719
+ <div className="flex gap-2">
720
+ <button
721
+ type="button"
722
+ disabled={!canAdopt}
723
+ onClick={onAdopt}
724
+ className="rounded bg-emerald-700 px-3 py-1.5 text-[11px] font-bold text-white hover:bg-emerald-800 disabled:opacity-40"
725
+ >
726
+ ✅ Adopt Revised Text
727
+ </button>
728
+ </div>
729
+ </div>
730
+
731
+ {/* Highlighted diff display */}
732
+ <div className="mt-2 max-h-72 overflow-y-auto rounded-lg border border-zinc-200 bg-white p-3 font-mono text-[11px] leading-relaxed text-zinc-800">
733
+ {showDiffView ? (
734
+ <div dangerouslySetInnerHTML={{ __html: diffHtml }} />
735
+ ) : (
736
+ <pre className="whitespace-pre-wrap">{state.result.finalText}</pre>
737
+ )}
738
+ </div>
739
+
740
+ {adoptMsg && <div className="mt-1 text-[11px] text-zinc-600">{adoptMsg}</div>}
741
+ </Card>
742
+ )}
743
+
744
+ {state.status === "error" && (
745
+ <Card>
746
+ <div className="text-[11px] font-semibold text-rose-700">Loop error: {state.error}</div>
747
+ </Card>
748
+ )}
749
+ </div>
750
+ );
751
+ }
752
+
753
+ // ═══ MAIN — tabs ═══════════════════════════════════════════════════
754
+ export function FictionSystemPanels({
755
+ iframeRef,
756
+ manuscript,
757
+ setManuscript,
758
+ }: {
759
+ iframeRef: FrameRef;
760
+ manuscript: string;
761
+ setManuscript: (s: string) => void;
762
+ }) {
763
+ const [tab, setTab] = useState<Tab>("story");
764
+ const [keysTick, setKeysTick] = useState(0);
765
+ const [geminiOverride, setGeminiOverride] = useState("");
766
+ const [allowlistText, setAllowlistText] = useState("");
767
+ const [toggles, setToggles] = useState<ContinuityToggles>(DEFAULT_CONTINUITY_TOGGLES);
768
+ const [seedControl, setSeedControl] = useState<SeedControl>(() => loadSeedControl());
769
+ useEffect(() => { saveSeedControl(seedControl); }, [seedControl]);
770
+
771
+ const bridgeKeys = useMemo(() => getKeys(iframeRef.current) as Keys, [iframeRef, keysTick]);
772
+ const keys: Keys = useMemo(
773
+ () => ({ ...bridgeKeys, ...(geminiOverride.trim() ? { gemini: geminiOverride.trim() } : {}) }),
774
+ [bridgeKeys, geminiOverride],
775
+ );
776
+ const allowlist = useMemo(() => allowlistText.split(",").map((s) => s.trim()).filter(Boolean), [allowlistText]);
777
+
778
+ useEffect(() => {
779
+ const t = window.setInterval(() => setKeysTick((n) => n + 1), 3000);
780
+ return () => window.clearInterval(t);
781
+ }, []);
782
+
783
+ const configured = ["gemini", "claude", "grok", "deepseek"].filter((p) => !!keys[p]);
784
+ const modelPool = useMemo(() => MODELS.map((m) => m.id), []);
785
+
786
+ const TABS: { id: Tab; label: string }[] = [
787
+ { id: "story", label: "🚀 Story Engine" },
788
+ { id: "seed", label: "🌱 Seed & Locks" },
789
+ { id: "diagnostics", label: "🔬 Diagnostics & Recovery" },
790
+ { id: "input", label: "📥 Fiction Input" },
791
+ { id: "map", label: "🗺 System Map" },
792
+ { id: "grade", label: "📚 Manuscript Grade v2" },
793
+ { id: "revise", label: "🔁 Targeted Revision" },
794
+ ];
795
+
796
+ return (
797
+ <div className="space-y-3">
798
+ <div className="rounded-xl border border-violet-300 bg-white p-3">
799
+ <div className="mb-2 text-[11px] font-bold uppercase tracking-wide text-violet-700">
800
+ Fiction System Transparency &amp; Active Controller (Additive)
801
+ </div>
802
+ <div className="flex flex-wrap items-center gap-2">
803
+ <input
804
+ type="password"
805
+ value={geminiOverride}
806
+ onChange={(e) => setGeminiOverride(e.target.value)}
807
+ placeholder="Gemini key override (else auto-read from engine)"
808
+ className="min-w-[200px] flex-1 rounded-lg border border-zinc-300 px-2 py-1 font-mono text-[11px]"
809
+ />
810
+ <input
811
+ value={allowlistText}
812
+ onChange={(e) => setAllowlistText(e.target.value)}
813
+ placeholder="model allowlist, comma-separated (blank = all models)"
814
+ className="min-w-[200px] flex-1 rounded-lg border border-zinc-300 px-2 py-1 font-mono text-[11px]"
815
+ title="Restrict random rotation to these model IDs"
816
+ />
817
+ </div>
818
+ <div className="mt-1 text-[10px] text-zinc-500">
819
+ keys: {configured.length ? configured.join(", ") : "(none detected — add in engine Settings)"} · rotation: {allowlist.length ? allowlist.join(", ") : `full registry (${MODELS.length} models)`}
820
+ </div>
821
+ </div>
822
+
823
+ <div className="flex flex-wrap gap-1">
824
+ {TABS.map((t) => (
825
+ <button
826
+ key={t.id}
827
+ onClick={() => setTab(t.id)}
828
+ className={`rounded-md px-2 py-1 text-[11px] font-semibold ${tab === t.id ? "bg-violet-700 text-white" : "bg-zinc-100 text-zinc-600 hover:bg-zinc-200"}`}
829
+ >
830
+ {t.label}
831
+ </button>
832
+ ))}
833
+ </div>
834
+
835
+ {tab === "story" && (
836
+ <StoryControllerPanel
837
+ manuscript={manuscript}
838
+ setManuscript={setManuscript}
839
+ apiKey={keys.gemini || ""}
840
+ toggles={toggles}
841
+ iframeRef={iframeRef}
842
+ modelPool={modelPool}
843
+ seedControl={seedControl}
844
+ />
845
+ )}
846
+
847
+ {tab === "seed" && (
848
+ <SeedControlPanel
849
+ control={seedControl}
850
+ setControl={setSeedControl}
851
+ seed={getPersonaSeed(iframeRef.current) ?? 0}
852
+ previewChapter={(manuscript.match(/^##\s+/gim) || []).length + 1}
853
+ />
854
+ )}
855
+
856
+ {tab === "diagnostics" && (
857
+ <DiagnosticRecoveryPanel
858
+ manuscript={manuscript}
859
+ setManuscript={setManuscript}
860
+ toggles={toggles}
861
+ setToggles={setToggles}
862
+ apiKey={keys.gemini || ""}
863
+ modelPool={modelPool}
864
+ iframeRef={iframeRef}
865
+ />
866
+ )}
867
+
868
+ {tab === "input" && <FictionInputTab iframeRef={iframeRef} />}
869
+ {tab === "map" && <SystemMapTab iframeRef={iframeRef} />}
870
+ {tab === "grade" && <GradeV2Tab manuscript={manuscript || null} keys={keys} allowlist={allowlist} />}
871
+ {tab === "revise" && (
872
+ <RevisionTab
873
+ manuscript={manuscript || null}
874
+ keys={keys}
875
+ allowlist={allowlist}
876
+ iframeRef={iframeRef}
877
+ setManuscript={setManuscript}
878
+ />
879
+ )}
880
+ </div>
881
+ );
882
+ }