igniter 0.5.0 → 0.5.1

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 (657) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +3 -3
  3. data/README.md +162 -624
  4. data/bin/igniter-stack +94 -0
  5. data/docs/README.md +62 -0
  6. data/examples/README.md +74 -349
  7. data/examples/agent_orchestration.rb +76 -0
  8. data/examples/agents.rb +2 -1
  9. data/examples/catalog.rb +412 -0
  10. data/examples/consensus.rb +13 -11
  11. data/examples/dataflow.rb +3 -2
  12. data/examples/distributed_workflow.rb +1 -1
  13. data/examples/effects.rb +10 -9
  14. data/examples/incremental.rb +4 -3
  15. data/examples/introspection.rb +49 -0
  16. data/examples/invariants.rb +4 -3
  17. data/examples/llm_tools.rb +18 -18
  18. data/examples/mesh.rb +16 -14
  19. data/examples/mesh_discovery.rb +41 -21
  20. data/examples/mesh_gossip.rb +19 -17
  21. data/examples/reactive_auditing.rb +50 -0
  22. data/examples/run.rb +163 -0
  23. data/lib/igniter/monorepo_packages.rb +17 -0
  24. data/lib/igniter/stack.rb +5 -0
  25. data/lib/igniter.rb +33 -17
  26. data/packages/igniter-agents/README.md +22 -0
  27. data/{lib → packages/igniter-agents/lib}/igniter/agent/ref.rb +1 -0
  28. data/{lib → packages/igniter-agents/lib}/igniter/agent/runner.rb +12 -0
  29. data/{lib → packages/igniter-agents/lib}/igniter/agent.rb +6 -0
  30. data/{lib → packages/igniter-agents/lib}/igniter/agents/proactive_agent.rb +1 -1
  31. data/packages/igniter-agents/lib/igniter/agents.rb +23 -0
  32. data/{lib/igniter/agents/ai → packages/igniter-agents/lib/igniter/ai/agents}/chain_agent.rb +4 -2
  33. data/{lib/igniter/agents/ai → packages/igniter-agents/lib/igniter/ai/agents}/critic_agent.rb +4 -2
  34. data/{lib/igniter/agents/ai → packages/igniter-agents/lib/igniter/ai/agents}/evaluator_agent.rb +4 -2
  35. data/{lib/igniter/agents/ai → packages/igniter-agents/lib/igniter/ai/agents}/evolution_agent.rb +4 -2
  36. data/{lib/igniter/agents/ai → packages/igniter-agents/lib/igniter/ai/agents}/observer_agent.rb +4 -2
  37. data/{lib/igniter/agents/ai → packages/igniter-agents/lib/igniter/ai/agents}/planner_agent.rb +4 -2
  38. data/{lib/igniter/agents/ai → packages/igniter-agents/lib/igniter/ai/agents}/router_agent.rb +4 -2
  39. data/{lib/igniter/agents/ai → packages/igniter-agents/lib/igniter/ai/agents}/self_reflection_agent.rb +4 -2
  40. data/packages/igniter-agents/lib/igniter/ai/agents.rb +25 -0
  41. data/{lib → packages/igniter-agents/lib}/igniter/registry.rb +2 -0
  42. data/packages/igniter-agents/lib/igniter/runtime/registry_agent_adapter.rb +102 -0
  43. data/{lib → packages/igniter-agents/lib}/igniter/supervisor.rb +3 -0
  44. data/packages/igniter-agents/lib/igniter-agents.rb +7 -0
  45. data/packages/igniter-ai/README.md +20 -0
  46. data/{lib/igniter/integrations/llm → packages/igniter-ai/lib/igniter/ai}/config.rb +1 -1
  47. data/{lib/igniter/integrations/llm → packages/igniter-ai/lib/igniter/ai}/context.rb +2 -2
  48. data/{lib/igniter/integrations/llm → packages/igniter-ai/lib/igniter/ai}/executor.rb +14 -14
  49. data/{lib/igniter/integrations/llm → packages/igniter-ai/lib/igniter/ai}/providers/anthropic.rb +5 -5
  50. data/{lib/igniter/integrations/llm → packages/igniter-ai/lib/igniter/ai}/providers/base.rb +1 -1
  51. data/{lib/igniter/integrations/llm → packages/igniter-ai/lib/igniter/ai}/providers/ollama.rb +4 -4
  52. data/{lib/igniter/integrations/llm → packages/igniter-ai/lib/igniter/ai}/providers/openai.rb +5 -5
  53. data/{lib/igniter → packages/igniter-ai/lib/igniter/ai}/skill/feedback.rb +4 -4
  54. data/{lib/igniter → packages/igniter-ai/lib/igniter/ai}/skill/output_schema.rb +6 -6
  55. data/packages/igniter-ai/lib/igniter/ai/skill/runtime_contract.rb +87 -0
  56. data/packages/igniter-ai/lib/igniter/ai/skill.rb +107 -0
  57. data/packages/igniter-ai/lib/igniter/ai/tool_registry.rb +79 -0
  58. data/{lib/igniter/integrations/llm → packages/igniter-ai/lib/igniter/ai}/transcription/providers/assemblyai.rb +8 -8
  59. data/{lib/igniter/integrations/llm → packages/igniter-ai/lib/igniter/ai}/transcription/providers/base.rb +3 -3
  60. data/{lib/igniter/integrations/llm → packages/igniter-ai/lib/igniter/ai}/transcription/providers/deepgram.rb +3 -3
  61. data/{lib/igniter/integrations/llm → packages/igniter-ai/lib/igniter/ai}/transcription/providers/openai.rb +3 -3
  62. data/{lib/igniter/integrations/llm → packages/igniter-ai/lib/igniter/ai}/transcription/transcriber.rb +6 -6
  63. data/{lib/igniter/integrations/llm → packages/igniter-ai/lib/igniter/ai}/transcription/transcript_result.rb +1 -1
  64. data/{lib/igniter/integrations/llm.rb → packages/igniter-ai/lib/igniter/ai.rb} +18 -15
  65. data/packages/igniter-ai/lib/igniter-ai.rb +3 -0
  66. data/packages/igniter-app/README.md +19 -0
  67. data/packages/igniter-app/lib/igniter/app/app_config.rb +43 -0
  68. data/packages/igniter-app/lib/igniter/app/app_host.rb +56 -0
  69. data/packages/igniter-app/lib/igniter/app/app_host_config.rb +27 -0
  70. data/packages/igniter-app/lib/igniter/app/app_host_pack.rb +13 -0
  71. data/{lib/igniter/application → packages/igniter-app/lib/igniter/app}/autoloader.rb +2 -2
  72. data/packages/igniter-app/lib/igniter/app/cluster_app_host.rb +95 -0
  73. data/packages/igniter-app/lib/igniter/app/cluster_app_host_config.rb +78 -0
  74. data/packages/igniter-app/lib/igniter/app/credentials/config_loader.rb +152 -0
  75. data/packages/igniter-app/lib/igniter/app/credentials/credential.rb +48 -0
  76. data/packages/igniter-app/lib/igniter/app/credentials/credential_policy.rb +38 -0
  77. data/packages/igniter-app/lib/igniter/app/credentials/events/credential_event.rb +179 -0
  78. data/packages/igniter-app/lib/igniter/app/credentials/events.rb +12 -0
  79. data/packages/igniter-app/lib/igniter/app/credentials/lease_request.rb +153 -0
  80. data/packages/igniter-app/lib/igniter/app/credentials/policies/ephemeral_lease_policy.rb +35 -0
  81. data/packages/igniter-app/lib/igniter/app/credentials/policies/local_only_policy.rb +33 -0
  82. data/packages/igniter-app/lib/igniter/app/credentials/policies.rb +13 -0
  83. data/packages/igniter-app/lib/igniter/app/credentials/store.rb +21 -0
  84. data/packages/igniter-app/lib/igniter/app/credentials/stores/file_store.rb +114 -0
  85. data/packages/igniter-app/lib/igniter/app/credentials/trail.rb +254 -0
  86. data/packages/igniter-app/lib/igniter/app/credentials.rb +20 -0
  87. data/packages/igniter-app/lib/igniter/app/dev_output_sync.rb +4 -0
  88. data/packages/igniter-app/lib/igniter/app/diagnostics/app_host_contributor.rb +71 -0
  89. data/packages/igniter-app/lib/igniter/app/diagnostics/cluster_app_host_contributor.rb +97 -0
  90. data/packages/igniter-app/lib/igniter/app/diagnostics/credential_contributor.rb +66 -0
  91. data/packages/igniter-app/lib/igniter/app/diagnostics/evolution_contributor.rb +74 -0
  92. data/packages/igniter-app/lib/igniter/app/diagnostics/ignite_contributor.rb +121 -0
  93. data/packages/igniter-app/lib/igniter/app/diagnostics/loader_contributor.rb +68 -0
  94. data/packages/igniter-app/lib/igniter/app/diagnostics/orchestration_contributor.rb +200 -0
  95. data/packages/igniter-app/lib/igniter/app/diagnostics/runtime_contributor.rb +68 -0
  96. data/packages/igniter-app/lib/igniter/app/diagnostics/scheduler_contributor.rb +72 -0
  97. data/packages/igniter-app/lib/igniter/app/diagnostics/sdk_contributor.rb +284 -0
  98. data/packages/igniter-app/lib/igniter/app/diagnostics.rb +62 -0
  99. data/packages/igniter-app/lib/igniter/app/evolution/approval_decision.rb +115 -0
  100. data/packages/igniter-app/lib/igniter/app/evolution/approval_request.rb +36 -0
  101. data/packages/igniter-app/lib/igniter/app/evolution/plan.rb +72 -0
  102. data/packages/igniter-app/lib/igniter/app/evolution/planner.rb +85 -0
  103. data/packages/igniter-app/lib/igniter/app/evolution/result.rb +45 -0
  104. data/packages/igniter-app/lib/igniter/app/evolution/runner.rb +102 -0
  105. data/packages/igniter-app/lib/igniter/app/evolution/store.rb +21 -0
  106. data/packages/igniter-app/lib/igniter/app/evolution/stores/file_store.rb +241 -0
  107. data/packages/igniter-app/lib/igniter/app/evolution/trail.rb +108 -0
  108. data/packages/igniter-app/lib/igniter/app/evolution.rb +11 -0
  109. data/packages/igniter-app/lib/igniter/app/filesystem_loader_adapter.rb +21 -0
  110. data/packages/igniter-app/lib/igniter/app/generator.rb +636 -0
  111. data/packages/igniter-app/lib/igniter/app/generators/cluster.rb +1367 -0
  112. data/packages/igniter-app/lib/igniter/app/generators/dashboard.rb +152 -0
  113. data/packages/igniter-app/lib/igniter/app/generators/playground.rb +1227 -0
  114. data/packages/igniter-app/lib/igniter/app/generators/templates/dashboard/README.md.erb +37 -0
  115. data/packages/igniter-app/lib/igniter/app/generators/templates/dashboard/app.rb.erb +19 -0
  116. data/packages/igniter-app/lib/igniter/app/generators/templates/dashboard/contexts/home_context.rb.erb +54 -0
  117. data/packages/igniter-app/lib/igniter/app/generators/templates/dashboard/frontend/application.js.erb +3 -0
  118. data/packages/igniter-app/lib/igniter/app/generators/templates/dashboard/spec/dashboard_app_spec.rb.erb +79 -0
  119. data/packages/igniter-app/lib/igniter/app/generators/templates/dashboard/support/stack_overview.rb.erb +23 -0
  120. data/packages/igniter-app/lib/igniter/app/generators/templates/dashboard/web/handlers/home_handler.rb.erb +27 -0
  121. data/packages/igniter-app/lib/igniter/app/generators/templates/dashboard/web/views/home_page.arb.erb +44 -0
  122. data/packages/igniter-app/lib/igniter/app/generators/templates/dashboard/web/views/home_page.rb.erb +56 -0
  123. data/packages/igniter-app/lib/igniter/app/generators/templates/dashboard/web/views/layout.arb.erb +17 -0
  124. data/packages/igniter-app/lib/igniter/app/host_adapter.rb +26 -0
  125. data/packages/igniter-app/lib/igniter/app/host_config.rb +40 -0
  126. data/packages/igniter-app/lib/igniter/app/host_registry.rb +43 -0
  127. data/packages/igniter-app/lib/igniter/app/loader_adapter.rb +15 -0
  128. data/packages/igniter-app/lib/igniter/app/loader_pack.rb +8 -0
  129. data/packages/igniter-app/lib/igniter/app/loader_registry.rb +39 -0
  130. data/packages/igniter-app/lib/igniter/app/observability/operator_action_handler.rb +147 -0
  131. data/packages/igniter-app/lib/igniter/app/observability/operator_console_handler.rb +747 -0
  132. data/packages/igniter-app/lib/igniter/app/observability/operator_overview_handler.rb +350 -0
  133. data/packages/igniter-app/lib/igniter/app/observability.rb +5 -0
  134. data/packages/igniter-app/lib/igniter/app/observability_pack.rb +71 -0
  135. data/packages/igniter-app/lib/igniter/app/operator/dispatcher.rb +40 -0
  136. data/packages/igniter-app/lib/igniter/app/operator/handler_registry.rb +40 -0
  137. data/packages/igniter-app/lib/igniter/app/operator/handler_result.rb +67 -0
  138. data/packages/igniter-app/lib/igniter/app/operator/handlers/base.rb +79 -0
  139. data/packages/igniter-app/lib/igniter/app/operator/handlers/ignite_handler.rb +108 -0
  140. data/packages/igniter-app/lib/igniter/app/operator/handlers/orchestration_handler.rb +33 -0
  141. data/packages/igniter-app/lib/igniter/app/operator/handlers.rb +5 -0
  142. data/packages/igniter-app/lib/igniter/app/operator/lifecycle_contract.rb +55 -0
  143. data/packages/igniter-app/lib/igniter/app/operator/policy.rb +157 -0
  144. data/packages/igniter-app/lib/igniter/app/operator.rb +17 -0
  145. data/packages/igniter-app/lib/igniter/app/orchestration/action_result_builder.rb +65 -0
  146. data/packages/igniter-app/lib/igniter/app/orchestration/followup_request.rb +36 -0
  147. data/packages/igniter-app/lib/igniter/app/orchestration/handler_registry.rb +58 -0
  148. data/packages/igniter-app/lib/igniter/app/orchestration/handlers.rb +106 -0
  149. data/packages/igniter-app/lib/igniter/app/orchestration/inbox.rb +283 -0
  150. data/packages/igniter-app/lib/igniter/app/orchestration/inbox_query.rb +293 -0
  151. data/packages/igniter-app/lib/igniter/app/orchestration/lane_registry.rb +100 -0
  152. data/packages/igniter-app/lib/igniter/app/orchestration/operator_query.rb +449 -0
  153. data/packages/igniter-app/lib/igniter/app/orchestration/plan.rb +68 -0
  154. data/packages/igniter-app/lib/igniter/app/orchestration/planner.rb +89 -0
  155. data/packages/igniter-app/lib/igniter/app/orchestration/policies.rb +125 -0
  156. data/packages/igniter-app/lib/igniter/app/orchestration/policy_registry.rb +63 -0
  157. data/packages/igniter-app/lib/igniter/app/orchestration/result.rb +43 -0
  158. data/packages/igniter-app/lib/igniter/app/orchestration/routing_registry.rb +43 -0
  159. data/packages/igniter-app/lib/igniter/app/orchestration/runner.rb +50 -0
  160. data/packages/igniter-app/lib/igniter/app/orchestration/runtime_event_query.rb +205 -0
  161. data/packages/igniter-app/lib/igniter/app/orchestration/runtime_overview_builder.rb +286 -0
  162. data/packages/igniter-app/lib/igniter/app/orchestration/runtime_query_overview_builder.rb +20 -0
  163. data/packages/igniter-app/lib/igniter/app/orchestration/runtime_result_builder.rb +23 -0
  164. data/packages/igniter-app/lib/igniter/app/orchestration.rb +113 -0
  165. data/packages/igniter-app/lib/igniter/app/runtime.rb +4 -0
  166. data/packages/igniter-app/lib/igniter/app/runtime_context.rb +101 -0
  167. data/packages/igniter-app/lib/igniter/app/runtime_pack.rb +16 -0
  168. data/packages/igniter-app/lib/igniter/app/scaffold_pack.rb +6 -0
  169. data/{lib/igniter/application → packages/igniter-app/lib/igniter/app}/scheduler.rb +2 -2
  170. data/packages/igniter-app/lib/igniter/app/scheduler_adapter.rb +17 -0
  171. data/packages/igniter-app/lib/igniter/app/scheduler_pack.rb +8 -0
  172. data/packages/igniter-app/lib/igniter/app/scheduler_registry.rb +39 -0
  173. data/packages/igniter-app/lib/igniter/app/stack.rb +1726 -0
  174. data/packages/igniter-app/lib/igniter/app/stack_pack.rb +3 -0
  175. data/packages/igniter-app/lib/igniter/app/threaded_scheduler_adapter.rb +35 -0
  176. data/packages/igniter-app/lib/igniter/app/yml_loader.rb +43 -0
  177. data/packages/igniter-app/lib/igniter/app.rb +2367 -0
  178. data/packages/igniter-app/lib/igniter/ignite/bootstrap_agent.rb +334 -0
  179. data/packages/igniter-app/lib/igniter/ignite/bootstrap_target.rb +79 -0
  180. data/packages/igniter-app/lib/igniter/ignite/deployment_intent.rb +82 -0
  181. data/packages/igniter-app/lib/igniter/ignite/ignition_agent.rb +1011 -0
  182. data/packages/igniter-app/lib/igniter/ignite/ignition_plan.rb +83 -0
  183. data/packages/igniter-app/lib/igniter/ignite/ignition_report.rb +144 -0
  184. data/packages/igniter-app/lib/igniter/ignite/store.rb +19 -0
  185. data/packages/igniter-app/lib/igniter/ignite/stores/file_store.rb +112 -0
  186. data/packages/igniter-app/lib/igniter/ignite/trail.rb +215 -0
  187. data/packages/igniter-app/lib/igniter/ignite.rb +11 -0
  188. data/packages/igniter-app/lib/igniter-app.rb +5 -0
  189. data/packages/igniter-cluster/README.md +9 -0
  190. data/packages/igniter-cluster/lib/igniter/cluster/agent_route_resolver.rb +58 -0
  191. data/{lib/igniter → packages/igniter-cluster/lib/igniter/cluster}/consensus/cluster.rb +8 -4
  192. data/{lib/igniter → packages/igniter-cluster/lib/igniter/cluster}/consensus/errors.rb +3 -1
  193. data/{lib/igniter → packages/igniter-cluster/lib/igniter/cluster}/consensus/executors.rb +3 -1
  194. data/{lib/igniter → packages/igniter-cluster/lib/igniter/cluster}/consensus/node.rb +3 -1
  195. data/{lib/igniter → packages/igniter-cluster/lib/igniter/cluster}/consensus/read_query.rb +5 -3
  196. data/{lib/igniter → packages/igniter-cluster/lib/igniter/cluster}/consensus/state_machine.rb +4 -2
  197. data/packages/igniter-cluster/lib/igniter/cluster/consensus.rb +18 -0
  198. data/packages/igniter-cluster/lib/igniter/cluster/diagnostics/governance_contributor.rb +90 -0
  199. data/packages/igniter-cluster/lib/igniter/cluster/diagnostics/identity_contributor.rb +98 -0
  200. data/packages/igniter-cluster/lib/igniter/cluster/diagnostics/routing_contributor.rb +674 -0
  201. data/packages/igniter-cluster/lib/igniter/cluster/diagnostics.rb +24 -0
  202. data/packages/igniter-cluster/lib/igniter/cluster/events/envelope.rb +136 -0
  203. data/packages/igniter-cluster/lib/igniter/cluster/events/hook_support.rb +33 -0
  204. data/packages/igniter-cluster/lib/igniter/cluster/events/log.rb +102 -0
  205. data/packages/igniter-cluster/lib/igniter/cluster/events/projection_feed.rb +98 -0
  206. data/packages/igniter-cluster/lib/igniter/cluster/events/read_model_projector.rb +32 -0
  207. data/packages/igniter-cluster/lib/igniter/cluster/events.rb +131 -0
  208. data/packages/igniter-cluster/lib/igniter/cluster/governance/admission_decision.rb +41 -0
  209. data/packages/igniter-cluster/lib/igniter/cluster/governance/admission_policy.rb +66 -0
  210. data/packages/igniter-cluster/lib/igniter/cluster/governance/admission_queue.rb +88 -0
  211. data/packages/igniter-cluster/lib/igniter/cluster/governance/admission_request.rb +62 -0
  212. data/packages/igniter-cluster/lib/igniter/cluster/governance/admission_workflow.rb +214 -0
  213. data/packages/igniter-cluster/lib/igniter/cluster/governance/checkpoint.rb +141 -0
  214. data/packages/igniter-cluster/lib/igniter/cluster/governance/compaction_record.rb +33 -0
  215. data/packages/igniter-cluster/lib/igniter/cluster/governance/stores/checkpoint_store.rb +89 -0
  216. data/packages/igniter-cluster/lib/igniter/cluster/governance/stores/file_store.rb +249 -0
  217. data/packages/igniter-cluster/lib/igniter/cluster/governance/trail.rb +164 -0
  218. data/packages/igniter-cluster/lib/igniter/cluster/governance.rb +12 -0
  219. data/packages/igniter-cluster/lib/igniter/cluster/identity/capability_attestation.rb +114 -0
  220. data/packages/igniter-cluster/lib/igniter/cluster/identity/manifest.rb +139 -0
  221. data/packages/igniter-cluster/lib/igniter/cluster/identity/node_identity.rb +106 -0
  222. data/packages/igniter-cluster/lib/igniter/cluster/identity.rb +5 -0
  223. data/{lib/igniter → packages/igniter-cluster/lib/igniter/cluster}/mesh/announcer.rb +37 -4
  224. data/packages/igniter-cluster/lib/igniter/cluster/mesh/checkpoint_gossip.rb +60 -0
  225. data/packages/igniter-cluster/lib/igniter/cluster/mesh/config.rb +146 -0
  226. data/{lib/igniter → packages/igniter-cluster/lib/igniter/cluster}/mesh/discovery.rb +7 -2
  227. data/{lib/igniter → packages/igniter-cluster/lib/igniter/cluster}/mesh/errors.rb +10 -5
  228. data/{lib/igniter → packages/igniter-cluster/lib/igniter/cluster}/mesh/gossip.rb +19 -4
  229. data/packages/igniter-cluster/lib/igniter/cluster/mesh/mesh_ql.rb +470 -0
  230. data/packages/igniter-cluster/lib/igniter/cluster/mesh/node_observation.rb +281 -0
  231. data/packages/igniter-cluster/lib/igniter/cluster/mesh/observation_query.rb +284 -0
  232. data/packages/igniter-cluster/lib/igniter/cluster/mesh/peer.rb +51 -0
  233. data/packages/igniter-cluster/lib/igniter/cluster/mesh/peer_capacity_report.rb +42 -0
  234. data/packages/igniter-cluster/lib/igniter/cluster/mesh/peer_identity_envelope.rb +158 -0
  235. data/packages/igniter-cluster/lib/igniter/cluster/mesh/peer_metadata.rb +122 -0
  236. data/packages/igniter-cluster/lib/igniter/cluster/mesh/peer_registry.rb +81 -0
  237. data/packages/igniter-cluster/lib/igniter/cluster/mesh/placement_decision.rb +64 -0
  238. data/packages/igniter-cluster/lib/igniter/cluster/mesh/placement_planner.rb +154 -0
  239. data/packages/igniter-cluster/lib/igniter/cluster/mesh/placement_policy.rb +103 -0
  240. data/{lib/igniter → packages/igniter-cluster/lib/igniter/cluster}/mesh/poller.rb +19 -4
  241. data/packages/igniter-cluster/lib/igniter/cluster/mesh/rebalance_plan.rb +66 -0
  242. data/packages/igniter-cluster/lib/igniter/cluster/mesh/rebalance_planner.rb +153 -0
  243. data/packages/igniter-cluster/lib/igniter/cluster/mesh/repair_loop.rb +169 -0
  244. data/packages/igniter-cluster/lib/igniter/cluster/mesh/router.rb +306 -0
  245. data/packages/igniter-cluster/lib/igniter/cluster/mesh/workload_signal.rb +46 -0
  246. data/packages/igniter-cluster/lib/igniter/cluster/mesh/workload_tracker.rb +215 -0
  247. data/packages/igniter-cluster/lib/igniter/cluster/mesh.rb +452 -0
  248. data/packages/igniter-cluster/lib/igniter/cluster/ownership/claim.rb +69 -0
  249. data/packages/igniter-cluster/lib/igniter/cluster/ownership/errors.rb +19 -0
  250. data/packages/igniter-cluster/lib/igniter/cluster/ownership/owner_client.rb +76 -0
  251. data/packages/igniter-cluster/lib/igniter/cluster/ownership/registry.rb +98 -0
  252. data/packages/igniter-cluster/lib/igniter/cluster/ownership/resolver.rb +62 -0
  253. data/packages/igniter-cluster/lib/igniter/cluster/ownership.rb +81 -0
  254. data/packages/igniter-cluster/lib/igniter/cluster/projection_store.rb +62 -0
  255. data/packages/igniter-cluster/lib/igniter/cluster/rag/chunk.rb +49 -0
  256. data/packages/igniter-cluster/lib/igniter/cluster/rag/fanout_retriever.rb +93 -0
  257. data/packages/igniter-cluster/lib/igniter/cluster/rag/knowledge_shard.rb +140 -0
  258. data/packages/igniter-cluster/lib/igniter/cluster/rag/net_http_adapter.rb +85 -0
  259. data/packages/igniter-cluster/lib/igniter/cluster/rag/ranker.rb +46 -0
  260. data/packages/igniter-cluster/lib/igniter/cluster/rag/retrieval_query.rb +30 -0
  261. data/packages/igniter-cluster/lib/igniter/cluster/rag/retrieval_result.rb +77 -0
  262. data/packages/igniter-cluster/lib/igniter/cluster/rag.rb +38 -0
  263. data/packages/igniter-cluster/lib/igniter/cluster/remote_adapter.rb +101 -0
  264. data/{lib/igniter → packages/igniter-cluster/lib/igniter/cluster}/replication/bootstrapper.rb +3 -1
  265. data/{lib/igniter → packages/igniter-cluster/lib/igniter/cluster}/replication/bootstrappers/gem.rb +11 -4
  266. data/{lib/igniter → packages/igniter-cluster/lib/igniter/cluster}/replication/bootstrappers/git.rb +9 -2
  267. data/{lib/igniter → packages/igniter-cluster/lib/igniter/cluster}/replication/bootstrappers/tarball.rb +8 -2
  268. data/packages/igniter-cluster/lib/igniter/cluster/replication/capability_query.rb +675 -0
  269. data/{lib/igniter → packages/igniter-cluster/lib/igniter/cluster}/replication/expansion_plan.rb +5 -3
  270. data/{lib/igniter → packages/igniter-cluster/lib/igniter/cluster}/replication/expansion_planner.rb +41 -29
  271. data/{lib/igniter → packages/igniter-cluster/lib/igniter/cluster}/replication/manifest.rb +3 -1
  272. data/{lib/igniter → packages/igniter-cluster/lib/igniter/cluster}/replication/network_topology.rb +44 -17
  273. data/packages/igniter-cluster/lib/igniter/cluster/replication/node_profile.rb +134 -0
  274. data/{lib/igniter → packages/igniter-cluster/lib/igniter/cluster}/replication/reflective_replication_agent.rb +50 -29
  275. data/{lib/igniter → packages/igniter-cluster/lib/igniter/cluster}/replication/replication_agent.rb +4 -2
  276. data/{lib/igniter → packages/igniter-cluster/lib/igniter/cluster}/replication/ssh_session.rb +3 -1
  277. data/packages/igniter-cluster/lib/igniter/cluster/replication.rb +38 -0
  278. data/packages/igniter-cluster/lib/igniter/cluster/routed_agent_adapter.rb +79 -0
  279. data/packages/igniter-cluster/lib/igniter/cluster/routing_plan_executor.rb +427 -0
  280. data/packages/igniter-cluster/lib/igniter/cluster/routing_plan_result.rb +38 -0
  281. data/packages/igniter-cluster/lib/igniter/cluster/trust/admission_plan.rb +34 -0
  282. data/packages/igniter-cluster/lib/igniter/cluster/trust/admission_planner.rb +76 -0
  283. data/packages/igniter-cluster/lib/igniter/cluster/trust/admission_result.rb +34 -0
  284. data/packages/igniter-cluster/lib/igniter/cluster/trust/admission_runner.rb +125 -0
  285. data/packages/igniter-cluster/lib/igniter/cluster/trust/trust_assessment.rb +37 -0
  286. data/packages/igniter-cluster/lib/igniter/cluster/trust/trust_store.rb +58 -0
  287. data/packages/igniter-cluster/lib/igniter/cluster/trust/verifier.rb +80 -0
  288. data/packages/igniter-cluster/lib/igniter/cluster/trust.rb +9 -0
  289. data/packages/igniter-cluster/lib/igniter/cluster.rb +71 -0
  290. data/packages/igniter-cluster/lib/igniter-cluster.rb +3 -0
  291. data/packages/igniter-core/README.md +21 -0
  292. data/{lib/igniter → packages/igniter-core/lib/igniter/core}/capabilities.rb +3 -1
  293. data/{lib/igniter → packages/igniter-core/lib/igniter/core}/compiler/compiled_graph.rb +40 -2
  294. data/{lib/igniter → packages/igniter-core/lib/igniter/core}/compiler/validation_pipeline.rb +1 -0
  295. data/packages/igniter-core/lib/igniter/core/compiler/validators/agent_validator.rb +142 -0
  296. data/{lib/igniter → packages/igniter-core/lib/igniter/core}/compiler/validators/dependencies_validator.rb +40 -1
  297. data/{lib/igniter → packages/igniter-core/lib/igniter/core}/compiler.rb +1 -0
  298. data/{lib/igniter → packages/igniter-core/lib/igniter/core}/contract.rb +76 -6
  299. data/{lib/igniter → packages/igniter-core/lib/igniter/core}/dataflow.rb +7 -7
  300. data/packages/igniter-core/lib/igniter/core/diagnostics/agent_contributor.rb +241 -0
  301. data/packages/igniter-core/lib/igniter/core/diagnostics/capability_contributor.rb +162 -0
  302. data/packages/igniter-core/lib/igniter/core/diagnostics/orchestration_contributor.rb +75 -0
  303. data/{lib/igniter → packages/igniter-core/lib/igniter/core}/diagnostics/report.rb +81 -6
  304. data/packages/igniter-core/lib/igniter/core/diagnostics.rb +58 -0
  305. data/{lib/igniter → packages/igniter-core/lib/igniter/core}/dsl/contract_builder.rb +163 -6
  306. data/packages/igniter-core/lib/igniter/core/dto/record.rb +189 -0
  307. data/packages/igniter-core/lib/igniter/core/dto.rb +8 -0
  308. data/{lib/igniter → packages/igniter-core/lib/igniter/core}/effect.rb +4 -0
  309. data/{lib/igniter → packages/igniter-core/lib/igniter/core}/errors.rb +26 -3
  310. data/{lib/igniter → packages/igniter-core/lib/igniter/core}/extensions/introspection/graph_formatter.rb +32 -1
  311. data/packages/igniter-core/lib/igniter/core/extensions/introspection/plan_formatter.rb +85 -0
  312. data/{lib/igniter → packages/igniter-core/lib/igniter/core}/extensions/introspection/runtime_formatter.rb +26 -0
  313. data/packages/igniter-core/lib/igniter/core/extensions/invariants.rb +70 -0
  314. data/{lib/igniter → packages/igniter-core/lib/igniter/core}/incremental.rb +4 -4
  315. data/{lib/igniter → packages/igniter-core/lib/igniter/core}/memory/memorable.rb +1 -1
  316. data/{lib/igniter → packages/igniter-core/lib/igniter/core}/memory/stores/sqlite.rb +5 -3
  317. data/{lib/igniter → packages/igniter-core/lib/igniter/core}/memory.rb +11 -11
  318. data/{lib/igniter → packages/igniter-core/lib/igniter/core}/metrics/prometheus_exporter.rb +1 -1
  319. data/{lib/igniter → packages/igniter-core/lib/igniter/core}/metrics.rb +8 -8
  320. data/packages/igniter-core/lib/igniter/core/model/agent_interaction_contract.rb +172 -0
  321. data/packages/igniter-core/lib/igniter/core/model/agent_node.rb +86 -0
  322. data/{lib/igniter → packages/igniter-core/lib/igniter/core}/model/branch_node.rb +37 -1
  323. data/packages/igniter-core/lib/igniter/core/model/remote_node.rb +91 -0
  324. data/{lib/igniter → packages/igniter-core/lib/igniter/core}/model.rb +2 -0
  325. data/{lib/igniter → packages/igniter-core/lib/igniter/core}/node_cache.rb +1 -1
  326. data/{lib/igniter → packages/igniter-core/lib/igniter/core}/property_testing.rb +8 -8
  327. data/{lib/igniter → packages/igniter-core/lib/igniter/core}/provenance/builder.rb +30 -1
  328. data/packages/igniter-core/lib/igniter/core/runtime/agent_adapter.rb +41 -0
  329. data/packages/igniter-core/lib/igniter/core/runtime/agent_result_contract.rb +91 -0
  330. data/packages/igniter-core/lib/igniter/core/runtime/agent_route.rb +60 -0
  331. data/packages/igniter-core/lib/igniter/core/runtime/agent_route_resolver.rb +26 -0
  332. data/packages/igniter-core/lib/igniter/core/runtime/agent_session.rb +922 -0
  333. data/packages/igniter-core/lib/igniter/core/runtime/agent_session_query.rb +379 -0
  334. data/packages/igniter-core/lib/igniter/core/runtime/agent_transport.rb +30 -0
  335. data/{lib/igniter → packages/igniter-core/lib/igniter/core}/runtime/cache.rb +6 -3
  336. data/{lib/igniter → packages/igniter-core/lib/igniter/core}/runtime/deferred_result.rb +27 -1
  337. data/packages/igniter-core/lib/igniter/core/runtime/execution.rb +913 -0
  338. data/packages/igniter-core/lib/igniter/core/runtime/job_worker.rb +39 -0
  339. data/{lib/igniter → packages/igniter-core/lib/igniter/core}/runtime/node_state.rb +4 -2
  340. data/packages/igniter-core/lib/igniter/core/runtime/orchestration_overview.rb +213 -0
  341. data/packages/igniter-core/lib/igniter/core/runtime/orchestration_runtime_state.rb +176 -0
  342. data/packages/igniter-core/lib/igniter/core/runtime/orchestration_transition_query.rb +208 -0
  343. data/packages/igniter-core/lib/igniter/core/runtime/planner.rb +301 -0
  344. data/packages/igniter-core/lib/igniter/core/runtime/proxy_agent_adapter.rb +124 -0
  345. data/packages/igniter-core/lib/igniter/core/runtime/remote_adapter.rb +26 -0
  346. data/{lib/igniter → packages/igniter-core/lib/igniter/core}/runtime/resolver.rb +250 -57
  347. data/{lib/igniter → packages/igniter-core/lib/igniter/core}/runtime/result.rb +2 -0
  348. data/packages/igniter-core/lib/igniter/core/runtime/stores/sqlite_store.rb +155 -0
  349. data/packages/igniter-core/lib/igniter/core/runtime/stream_result.rb +171 -0
  350. data/{lib/igniter → packages/igniter-core/lib/igniter/core}/runtime.rb +15 -0
  351. data/{lib/igniter → packages/igniter-core/lib/igniter/core}/temporal.rb +1 -1
  352. data/{lib/igniter → packages/igniter-core/lib/igniter/core}/tool/discoverable.rb +3 -3
  353. data/{lib/igniter → packages/igniter-core/lib/igniter/core}/tool.rb +6 -2
  354. data/{lib/igniter → packages/igniter-core/lib/igniter/core}/version.rb +1 -1
  355. data/packages/igniter-core/lib/igniter/core.rb +23 -0
  356. data/packages/igniter-core/lib/igniter-core.rb +3 -0
  357. data/packages/igniter-extensions/README.md +21 -0
  358. data/packages/igniter-extensions/lib/igniter/extensions/auditing.rb +3 -0
  359. data/{lib → packages/igniter-extensions/lib}/igniter/extensions/capabilities.rb +1 -1
  360. data/{lib → packages/igniter-extensions/lib}/igniter/extensions/content_addressing.rb +1 -1
  361. data/{lib → packages/igniter-extensions/lib}/igniter/extensions/dataflow.rb +1 -1
  362. data/{lib → packages/igniter-extensions/lib}/igniter/extensions/differential.rb +1 -1
  363. data/{lib → packages/igniter-extensions/lib}/igniter/extensions/execution_report.rb +1 -1
  364. data/{lib → packages/igniter-extensions/lib}/igniter/extensions/incremental.rb +1 -1
  365. data/packages/igniter-extensions/lib/igniter/extensions/introspection.rb +3 -0
  366. data/packages/igniter-extensions/lib/igniter/extensions/invariants.rb +3 -0
  367. data/{lib → packages/igniter-extensions/lib}/igniter/extensions/provenance.rb +1 -1
  368. data/packages/igniter-extensions/lib/igniter/extensions/reactive.rb +3 -0
  369. data/{lib → packages/igniter-extensions/lib}/igniter/extensions/saga.rb +1 -1
  370. data/packages/igniter-extensions/lib/igniter/extensions.rb +8 -0
  371. data/packages/igniter-extensions/lib/igniter-extensions.rb +3 -0
  372. data/packages/igniter-frontend/README.md +224 -0
  373. data/packages/igniter-frontend/lib/igniter/frontend/app.rb +90 -0
  374. data/packages/igniter-frontend/lib/igniter/frontend/app_access.rb +36 -0
  375. data/packages/igniter-frontend/lib/igniter/frontend/arbre/component.rb +120 -0
  376. data/packages/igniter-frontend/lib/igniter/frontend/arbre/components/action_group.rb +53 -0
  377. data/packages/igniter-frontend/lib/igniter/frontend/arbre/components/badge.rb +91 -0
  378. data/packages/igniter-frontend/lib/igniter/frontend/arbre/components/boolean.rb +53 -0
  379. data/packages/igniter-frontend/lib/igniter/frontend/arbre/components/breadcrumbs.rb +71 -0
  380. data/packages/igniter-frontend/lib/igniter/frontend/arbre/components/card.rb +114 -0
  381. data/packages/igniter-frontend/lib/igniter/frontend/arbre/components/conversation_panel.rb +61 -0
  382. data/packages/igniter-frontend/lib/igniter/frontend/arbre/components/datetime.rb +42 -0
  383. data/packages/igniter-frontend/lib/igniter/frontend/arbre/components/display_value_support.rb +38 -0
  384. data/packages/igniter-frontend/lib/igniter/frontend/arbre/components/empty_state.rb +39 -0
  385. data/packages/igniter-frontend/lib/igniter/frontend/arbre/components/event_list.rb +44 -0
  386. data/packages/igniter-frontend/lib/igniter/frontend/arbre/components/filters.rb +183 -0
  387. data/packages/igniter-frontend/lib/igniter/frontend/arbre/components/indicator.rb +59 -0
  388. data/packages/igniter-frontend/lib/igniter/frontend/arbre/components/json_panel.rb +36 -0
  389. data/packages/igniter-frontend/lib/igniter/frontend/arbre/components/key_value_list.rb +40 -0
  390. data/packages/igniter-frontend/lib/igniter/frontend/arbre/components/loading_state.rb +43 -0
  391. data/packages/igniter-frontend/lib/igniter/frontend/arbre/components/metric_grid.rb +37 -0
  392. data/packages/igniter-frontend/lib/igniter/frontend/arbre/components/number.rb +53 -0
  393. data/packages/igniter-frontend/lib/igniter/frontend/arbre/components/page_header.rb +53 -0
  394. data/packages/igniter-frontend/lib/igniter/frontend/arbre/components/pagination.rb +143 -0
  395. data/packages/igniter-frontend/lib/igniter/frontend/arbre/components/panel.rb +67 -0
  396. data/packages/igniter-frontend/lib/igniter/frontend/arbre/components/percentage.rb +79 -0
  397. data/packages/igniter-frontend/lib/igniter/frontend/arbre/components/resource_list.rb +38 -0
  398. data/packages/igniter-frontend/lib/igniter/frontend/arbre/components/scenario_card.rb +48 -0
  399. data/packages/igniter-frontend/lib/igniter/frontend/arbre/components/shell_columns.rb +67 -0
  400. data/packages/igniter-frontend/lib/igniter/frontend/arbre/components/sidebar_shell.rb +106 -0
  401. data/packages/igniter-frontend/lib/igniter/frontend/arbre/components/table_with.rb +203 -0
  402. data/packages/igniter-frontend/lib/igniter/frontend/arbre/components/tabs.rb +147 -0
  403. data/packages/igniter-frontend/lib/igniter/frontend/arbre/components/viz.rb +185 -0
  404. data/packages/igniter-frontend/lib/igniter/frontend/arbre/page.rb +74 -0
  405. data/packages/igniter-frontend/lib/igniter/frontend/arbre/raw_text_node.rb +40 -0
  406. data/packages/igniter-frontend/lib/igniter/frontend/arbre/template_page.rb +243 -0
  407. data/packages/igniter-frontend/lib/igniter/frontend/arbre.rb +48 -0
  408. data/packages/igniter-frontend/lib/igniter/frontend/arbre_page.rb +7 -0
  409. data/packages/igniter-frontend/lib/igniter/frontend/assets.rb +101 -0
  410. data/packages/igniter-frontend/lib/igniter/frontend/builder.rb +124 -0
  411. data/packages/igniter-frontend/lib/igniter/frontend/component.rb +24 -0
  412. data/packages/igniter-frontend/lib/igniter/frontend/components.rb +7 -0
  413. data/packages/igniter-frontend/lib/igniter/frontend/context.rb +53 -0
  414. data/packages/igniter-frontend/lib/igniter/frontend/form_builder.rb +63 -0
  415. data/packages/igniter-frontend/lib/igniter/frontend/handler.rb +92 -0
  416. data/packages/igniter-frontend/lib/igniter/frontend/javascript.rb +353 -0
  417. data/packages/igniter-frontend/lib/igniter/frontend/page.rb +24 -0
  418. data/packages/igniter-frontend/lib/igniter/frontend/request.rb +61 -0
  419. data/packages/igniter-frontend/lib/igniter/frontend/response.rb +67 -0
  420. data/packages/igniter-frontend/lib/igniter/frontend/tailwind/realtime/adapters.rb +226 -0
  421. data/packages/igniter-frontend/lib/igniter/frontend/tailwind/realtime/presets.rb +147 -0
  422. data/packages/igniter-frontend/lib/igniter/frontend/tailwind/realtime.rb +259 -0
  423. data/packages/igniter-frontend/lib/igniter/frontend/tailwind/surfaces.rb +1074 -0
  424. data/packages/igniter-frontend/lib/igniter/frontend/tailwind/ui.rb +1438 -0
  425. data/packages/igniter-frontend/lib/igniter/frontend/tailwind.rb +180 -0
  426. data/packages/igniter-frontend/lib/igniter/frontend/version.rb +9 -0
  427. data/packages/igniter-frontend/lib/igniter/frontend.rb +35 -0
  428. data/packages/igniter-frontend/lib/igniter-frontend.rb +3 -0
  429. data/packages/igniter-rails/README.md +96 -0
  430. data/packages/igniter-rails/lib/igniter/plugins/rails/generators/contract/templates/contract.rb.tt +22 -0
  431. data/packages/igniter-rails/lib/igniter/plugins/rails/generators/install/templates/igniter.rb.tt +16 -0
  432. data/packages/igniter-rails/lib/igniter-rails.rb +3 -0
  433. data/packages/igniter-schema-rendering/README.md +27 -0
  434. data/packages/igniter-schema-rendering/lib/igniter/schema_rendering/page.rb +35 -0
  435. data/packages/igniter-schema-rendering/lib/igniter/schema_rendering/patcher.rb +47 -0
  436. data/packages/igniter-schema-rendering/lib/igniter/schema_rendering/renderer.rb +268 -0
  437. data/packages/igniter-schema-rendering/lib/igniter/schema_rendering/schema.rb +172 -0
  438. data/packages/igniter-schema-rendering/lib/igniter/schema_rendering/store.rb +53 -0
  439. data/packages/igniter-schema-rendering/lib/igniter/schema_rendering/submission_normalizer.rb +117 -0
  440. data/packages/igniter-schema-rendering/lib/igniter/schema_rendering/submission_processor.rb +91 -0
  441. data/packages/igniter-schema-rendering/lib/igniter/schema_rendering/submission_validator.rb +62 -0
  442. data/packages/igniter-schema-rendering/lib/igniter/schema_rendering/version.rb +9 -0
  443. data/packages/igniter-schema-rendering/lib/igniter/schema_rendering.rb +20 -0
  444. data/packages/igniter-schema-rendering/lib/igniter-schema-rendering.rb +3 -0
  445. data/packages/igniter-sdk/README.md +25 -0
  446. data/packages/igniter-sdk/lib/igniter/sdk/channels/base.rb +84 -0
  447. data/packages/igniter-sdk/lib/igniter/sdk/channels/delivery_result.rb +61 -0
  448. data/packages/igniter-sdk/lib/igniter/sdk/channels/message.rb +101 -0
  449. data/packages/igniter-sdk/lib/igniter/sdk/channels/telegram.rb +161 -0
  450. data/packages/igniter-sdk/lib/igniter/sdk/channels/webhook.rb +213 -0
  451. data/packages/igniter-sdk/lib/igniter/sdk/channels.rb +17 -0
  452. data/packages/igniter-sdk/lib/igniter/sdk/data/store.rb +31 -0
  453. data/packages/igniter-sdk/lib/igniter/sdk/data/stores/file.rb +113 -0
  454. data/packages/igniter-sdk/lib/igniter/sdk/data/stores/in_memory.rb +63 -0
  455. data/packages/igniter-sdk/lib/igniter/sdk/data/stores/sqlite.rb +144 -0
  456. data/packages/igniter-sdk/lib/igniter/sdk/data.rb +34 -0
  457. data/packages/igniter-sdk/lib/igniter/sdk/tools/agent_bootstrap_tool.rb +151 -0
  458. data/packages/igniter-sdk/lib/igniter/sdk/tools/local_workflow_selector_tool.rb +269 -0
  459. data/packages/igniter-sdk/lib/igniter/sdk/tools/system_discovery_tool.rb +198 -0
  460. data/packages/igniter-sdk/lib/igniter/sdk/tools.rb +9 -0
  461. data/packages/igniter-sdk/lib/igniter/sdk.rb +86 -0
  462. data/packages/igniter-sdk/lib/igniter-sdk.rb +3 -0
  463. data/packages/igniter-server/README.md +9 -0
  464. data/packages/igniter-server/lib/igniter/server/agent_session_store.rb +98 -0
  465. data/packages/igniter-server/lib/igniter/server/agent_transport.rb +95 -0
  466. data/packages/igniter-server/lib/igniter/server/app_host.rb +3 -0
  467. data/{lib → packages/igniter-server/lib}/igniter/server/client.rb +96 -6
  468. data/packages/igniter-server/lib/igniter/server/config.rb +70 -0
  469. data/packages/igniter-server/lib/igniter/server/handlers/agent_message_handler.rb +107 -0
  470. data/packages/igniter-server/lib/igniter/server/handlers/agent_session_handler.rb +125 -0
  471. data/packages/igniter-server/lib/igniter/server/handlers/manifest_handler.rb +77 -0
  472. data/{lib → packages/igniter-server/lib}/igniter/server/handlers/metrics_handler.rb +1 -1
  473. data/{lib → packages/igniter-server/lib}/igniter/server/handlers/peers_handler.rb +38 -17
  474. data/{lib → packages/igniter-server/lib}/igniter/server/http_server.rb +91 -15
  475. data/{lib → packages/igniter-server/lib}/igniter/server/rack_app.rb +27 -2
  476. data/packages/igniter-server/lib/igniter/server/remote_adapter.rb +27 -0
  477. data/packages/igniter-server/lib/igniter/server/router.rb +291 -0
  478. data/{lib → packages/igniter-server/lib}/igniter/server/server_logger.rb +3 -1
  479. data/{lib → packages/igniter-server/lib}/igniter/server.rb +58 -1
  480. data/packages/igniter-server/lib/igniter-server.rb +3 -0
  481. metadata +631 -282
  482. data/docs/API_V2.md +0 -537
  483. data/docs/APPLICATION_V1.md +0 -253
  484. data/docs/ARCHITECTURE_V2.md +0 -317
  485. data/docs/BACKLOG.md +0 -166
  486. data/docs/BRANCHES_V1.md +0 -213
  487. data/docs/CAPABILITIES_V1.md +0 -207
  488. data/docs/COLLECTIONS_V1.md +0 -303
  489. data/docs/CONSENSUS_V1.md +0 -477
  490. data/docs/CONTENT_ADDRESSING_V1.md +0 -221
  491. data/docs/DATAFLOW_V1.md +0 -274
  492. data/docs/DISTRIBUTED_CONTRACTS_V1.md +0 -493
  493. data/docs/EXECUTION_MODEL_V2.md +0 -324
  494. data/docs/IGNITER_CONCEPTS.md +0 -81
  495. data/docs/LLM_V1.md +0 -335
  496. data/docs/MESH_V1.md +0 -732
  497. data/docs/NODE_CACHE_V1.md +0 -324
  498. data/docs/PATTERNS.md +0 -411
  499. data/docs/PROACTIVE_AGENTS_V1.md +0 -293
  500. data/docs/SERVER_V1.md +0 -512
  501. data/docs/SKILLS_V1.md +0 -213
  502. data/docs/STORE_ADAPTERS.md +0 -154
  503. data/docs/TEMPORAL_V1.md +0 -174
  504. data/docs/TOOLS_V1.md +0 -347
  505. data/docs/TRANSCRIPTION_V1.md +0 -403
  506. data/lib/igniter/agents.rb +0 -56
  507. data/lib/igniter/application/app_config.rb +0 -32
  508. data/lib/igniter/application/generator.rb +0 -157
  509. data/lib/igniter/application/yml_loader.rb +0 -39
  510. data/lib/igniter/application.rb +0 -174
  511. data/lib/igniter/consensus.rb +0 -58
  512. data/lib/igniter/diagnostics.rb +0 -8
  513. data/lib/igniter/extensions/introspection/plan_formatter.rb +0 -55
  514. data/lib/igniter/extensions/invariants.rb +0 -116
  515. data/lib/igniter/extensions/mesh.rb +0 -31
  516. data/lib/igniter/integrations/agents.rb +0 -18
  517. data/lib/igniter/mesh/config.rb +0 -45
  518. data/lib/igniter/mesh/peer.rb +0 -21
  519. data/lib/igniter/mesh/peer_registry.rb +0 -51
  520. data/lib/igniter/mesh/router.rb +0 -109
  521. data/lib/igniter/mesh.rb +0 -85
  522. data/lib/igniter/model/remote_node.rb +0 -42
  523. data/lib/igniter/replication/node_role.rb +0 -42
  524. data/lib/igniter/replication/role_registry.rb +0 -73
  525. data/lib/igniter/replication.rb +0 -54
  526. data/lib/igniter/runtime/execution.rb +0 -416
  527. data/lib/igniter/runtime/job_worker.rb +0 -18
  528. data/lib/igniter/runtime/planner.rb +0 -126
  529. data/lib/igniter/server/config.rb +0 -34
  530. data/lib/igniter/server/handlers/manifest_handler.rb +0 -34
  531. data/lib/igniter/server/router.rb +0 -108
  532. data/lib/igniter/skill.rb +0 -218
  533. data/lib/igniter/tool_registry.rb +0 -144
  534. /data/{lib → packages/igniter-agents/lib}/igniter/agent/mailbox.rb +0 -0
  535. /data/{lib → packages/igniter-agents/lib}/igniter/agent/message.rb +0 -0
  536. /data/{lib → packages/igniter-agents/lib}/igniter/agent/state_holder.rb +0 -0
  537. /data/{lib → packages/igniter-agents/lib}/igniter/agents/observability/metrics_agent.rb +0 -0
  538. /data/{lib → packages/igniter-agents/lib}/igniter/agents/pipeline/batch_processor_agent.rb +0 -0
  539. /data/{lib/igniter/agents/ai → packages/igniter-agents/lib/igniter/agents/proactive}/alert_agent.rb +0 -0
  540. /data/{lib/igniter/agents/ai → packages/igniter-agents/lib/igniter/agents/proactive}/health_check_agent.rb +0 -0
  541. /data/{lib → packages/igniter-agents/lib}/igniter/agents/reliability/retry_agent.rb +0 -0
  542. /data/{lib → packages/igniter-agents/lib}/igniter/agents/scheduling/cron_agent.rb +0 -0
  543. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/compiler/graph_compiler.rb +0 -0
  544. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/compiler/type_resolver.rb +0 -0
  545. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/compiler/validation_context.rb +0 -0
  546. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/compiler/validator.rb +0 -0
  547. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/compiler/validators/await_validator.rb +0 -0
  548. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/compiler/validators/callable_validator.rb +0 -0
  549. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/compiler/validators/outputs_validator.rb +0 -0
  550. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/compiler/validators/remote_validator.rb +0 -0
  551. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/compiler/validators/type_compatibility_validator.rb +0 -0
  552. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/compiler/validators/uniqueness_validator.rb +0 -0
  553. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/content_addressing.rb +0 -0
  554. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/dataflow/aggregate_operators.rb +0 -0
  555. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/dataflow/aggregate_state.rb +0 -0
  556. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/dataflow/diff.rb +0 -0
  557. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/dataflow/diff_state.rb +0 -0
  558. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/dataflow/incremental_collection_result.rb +0 -0
  559. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/dataflow/window_filter.rb +0 -0
  560. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/diagnostics/auditing/report/console_formatter.rb +0 -0
  561. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/diagnostics/auditing/report/markdown_formatter.rb +0 -0
  562. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/diagnostics/introspection/formatters/mermaid_formatter.rb +0 -0
  563. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/diagnostics/introspection/formatters/text_tree_formatter.rb +0 -0
  564. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/differential/divergence.rb +0 -0
  565. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/differential/formatter.rb +0 -0
  566. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/differential/report.rb +0 -0
  567. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/differential/runner.rb +0 -0
  568. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/differential.rb +0 -0
  569. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/dsl/schema_builder.rb +0 -0
  570. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/dsl.rb +0 -0
  571. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/effect_registry.rb +0 -0
  572. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/events/bus.rb +0 -0
  573. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/events/event.rb +0 -0
  574. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/events.rb +0 -0
  575. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/execution_report/builder.rb +0 -0
  576. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/execution_report/formatter.rb +0 -0
  577. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/execution_report/node_entry.rb +0 -0
  578. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/execution_report/report.rb +0 -0
  579. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/execution_report.rb +0 -0
  580. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/executor.rb +0 -0
  581. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/executor_registry.rb +0 -0
  582. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/extensions/auditing/timeline.rb +0 -0
  583. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/extensions/auditing.rb +0 -0
  584. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/extensions/introspection.rb +0 -0
  585. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/extensions/reactive/engine.rb +0 -0
  586. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/extensions/reactive/matcher.rb +0 -0
  587. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/extensions/reactive/reaction.rb +0 -0
  588. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/extensions/reactive.rb +0 -0
  589. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/extensions.rb +0 -0
  590. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/fingerprint.rb +0 -0
  591. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/incremental/formatter.rb +0 -0
  592. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/incremental/result.rb +0 -0
  593. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/incremental/tracker.rb +0 -0
  594. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/invariant.rb +0 -0
  595. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/memory/agent_memory.rb +0 -0
  596. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/memory/episode.rb +0 -0
  597. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/memory/fact.rb +0 -0
  598. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/memory/reflection_cycle.rb +0 -0
  599. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/memory/reflection_record.rb +0 -0
  600. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/memory/store.rb +0 -0
  601. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/memory/stores/in_memory.rb +0 -0
  602. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/metrics/collector.rb +0 -0
  603. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/metrics/snapshot.rb +0 -0
  604. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/model/aggregate_node.rb +0 -0
  605. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/model/await_node.rb +0 -0
  606. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/model/collection_node.rb +0 -0
  607. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/model/composition_node.rb +0 -0
  608. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/model/compute_node.rb +0 -0
  609. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/model/effect_node.rb +0 -0
  610. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/model/graph.rb +0 -0
  611. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/model/input_node.rb +0 -0
  612. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/model/node.rb +0 -0
  613. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/model/output_node.rb +0 -0
  614. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/property_testing/formatter.rb +0 -0
  615. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/property_testing/generators.rb +0 -0
  616. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/property_testing/result.rb +0 -0
  617. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/property_testing/run.rb +0 -0
  618. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/property_testing/runner.rb +0 -0
  619. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/provenance/lineage.rb +0 -0
  620. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/provenance/node_trace.rb +0 -0
  621. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/provenance/text_formatter.rb +0 -0
  622. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/provenance.rb +0 -0
  623. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/runtime/collection_result.rb +0 -0
  624. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/runtime/input_validator.rb +0 -0
  625. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/runtime/invalidator.rb +0 -0
  626. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/runtime/runner_factory.rb +0 -0
  627. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/runtime/runners/inline_runner.rb +0 -0
  628. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/runtime/runners/store_runner.rb +0 -0
  629. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/runtime/runners/thread_pool_runner.rb +0 -0
  630. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/runtime/stores/active_record_store.rb +0 -0
  631. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/runtime/stores/file_store.rb +0 -0
  632. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/runtime/stores/memory_store.rb +0 -0
  633. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/runtime/stores/redis_store.rb +0 -0
  634. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/saga/compensation.rb +0 -0
  635. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/saga/compensation_record.rb +0 -0
  636. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/saga/executor.rb +0 -0
  637. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/saga/formatter.rb +0 -0
  638. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/saga/result.rb +0 -0
  639. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/saga.rb +0 -0
  640. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/stream_loop.rb +0 -0
  641. /data/{lib/igniter → packages/igniter-core/lib/igniter/core}/type_system.rb +0 -0
  642. /data/{lib/igniter/integrations → packages/igniter-rails/lib/igniter/plugins}/rails/cable_adapter.rb +0 -0
  643. /data/{lib/igniter/integrations → packages/igniter-rails/lib/igniter/plugins}/rails/contract_job.rb +0 -0
  644. /data/{lib/igniter/integrations → packages/igniter-rails/lib/igniter/plugins}/rails/generators/contract/contract_generator.rb +0 -0
  645. /data/{lib/igniter/integrations → packages/igniter-rails/lib/igniter/plugins}/rails/generators/install/install_generator.rb +0 -0
  646. /data/{lib/igniter/integrations → packages/igniter-rails/lib/igniter/plugins}/rails/railtie.rb +0 -0
  647. /data/{lib/igniter/integrations → packages/igniter-rails/lib/igniter/plugins}/rails/webhook_concern.rb +0 -0
  648. /data/{lib/igniter/integrations → packages/igniter-rails/lib/igniter/plugins}/rails.rb +0 -0
  649. /data/{lib → packages/igniter-server/lib}/igniter/server/handlers/base.rb +0 -0
  650. /data/{lib → packages/igniter-server/lib}/igniter/server/handlers/contracts_handler.rb +0 -0
  651. /data/{lib → packages/igniter-server/lib}/igniter/server/handlers/event_handler.rb +0 -0
  652. /data/{lib → packages/igniter-server/lib}/igniter/server/handlers/execute_handler.rb +0 -0
  653. /data/{lib → packages/igniter-server/lib}/igniter/server/handlers/health_handler.rb +0 -0
  654. /data/{lib → packages/igniter-server/lib}/igniter/server/handlers/liveness_handler.rb +0 -0
  655. /data/{lib → packages/igniter-server/lib}/igniter/server/handlers/readiness_handler.rb +0 -0
  656. /data/{lib → packages/igniter-server/lib}/igniter/server/handlers/status_handler.rb +0 -0
  657. /data/{lib → packages/igniter-server/lib}/igniter/server/registry.rb +0 -0
@@ -0,0 +1,1438 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module Igniter
6
+ module Frontend
7
+ module Tailwind
8
+ module UI
9
+ module Tokens
10
+ THEMES = {
11
+ orange: {
12
+ strong_border: "border-orange-300/20",
13
+ strong_bg: "bg-orange-300/90",
14
+ strong_hover: "hover:bg-orange-200",
15
+ soft_border: "border-orange-300/20",
16
+ soft_bg: "bg-orange-300/10",
17
+ soft_text: "text-orange-100",
18
+ soft_hover: "hover:bg-orange-300/20",
19
+ ghost_hover_border: "hover:border-orange-200/35",
20
+ ghost_hover_bg: "hover:bg-orange-300/10",
21
+ ghost_hover_text: "hover:text-orange-100",
22
+ underline_text: "text-orange-200",
23
+ underline_decoration: "decoration-orange-200/30"
24
+ },
25
+ amber: {
26
+ strong_border: "border-amber-300/20",
27
+ strong_bg: "bg-amber-300/90",
28
+ strong_hover: "hover:bg-amber-200",
29
+ soft_border: "border-amber-200/15",
30
+ soft_bg: "bg-amber-300/10",
31
+ soft_text: "text-amber-100",
32
+ soft_hover: "hover:bg-amber-300/20",
33
+ ghost_hover_border: "hover:border-amber-200/35",
34
+ ghost_hover_bg: "hover:bg-amber-300/10",
35
+ ghost_hover_text: "hover:text-amber-100",
36
+ underline_text: "text-amber-200",
37
+ underline_decoration: "decoration-amber-200/30"
38
+ },
39
+ cyan: {
40
+ strong_border: "border-cyan-300/20",
41
+ strong_bg: "bg-cyan-300/10",
42
+ strong_hover: "hover:bg-cyan-300/20",
43
+ soft_border: "border-cyan-300/20",
44
+ soft_bg: "bg-cyan-300/10",
45
+ soft_text: "text-cyan-100",
46
+ soft_hover: "hover:bg-cyan-300/20",
47
+ ghost_hover_border: "hover:border-cyan-200/30",
48
+ ghost_hover_bg: "hover:bg-cyan-300/10",
49
+ ghost_hover_text: "hover:text-cyan-100",
50
+ underline_text: "text-cyan-200",
51
+ underline_decoration: "decoration-cyan-200/30"
52
+ }
53
+ }.freeze
54
+
55
+ module_function
56
+
57
+ def action(variant: :primary, theme: :orange, size: :md, extra: nil)
58
+ classes = [base_action(size), variant_classes(variant.to_sym, palette_for(theme)), extra]
59
+ classes.compact.join(" ")
60
+ end
61
+
62
+ def underline_link(theme: :orange, extra: nil)
63
+ palette = palette_for(theme)
64
+ [palette.fetch(:underline_text), "underline", palette.fetch(:underline_decoration), "underline-offset-4", extra].compact.join(" ")
65
+ end
66
+
67
+ def badge(theme: :orange, extra: nil)
68
+ palette = palette_for(theme)
69
+ [
70
+ "inline-flex rounded-full border px-3 py-1 text-xs font-mono uppercase tracking-[0.18em]",
71
+ palette.fetch(:soft_border),
72
+ palette.fetch(:soft_bg),
73
+ palette.fetch(:soft_text),
74
+ extra
75
+ ].compact.join(" ")
76
+ end
77
+
78
+ private_class_method def palette_for(theme)
79
+ THEMES.fetch(theme.to_sym)
80
+ end
81
+
82
+ private_class_method def base_action(size)
83
+ case size.to_sym
84
+ when :sm
85
+ "inline-flex items-center justify-center rounded-full border px-4 py-2 text-sm transition"
86
+ else
87
+ "inline-flex items-center justify-center rounded-full border px-5 py-3 text-sm font-semibold uppercase tracking-[0.18em] transition"
88
+ end
89
+ end
90
+
91
+ private_class_method def variant_classes(variant, palette)
92
+ case variant
93
+ when :primary
94
+ [palette.fetch(:strong_border), palette.fetch(:strong_bg), "text-stone-950", palette.fetch(:strong_hover)].join(" ")
95
+ when :secondary
96
+ "border-white/10 bg-white/10 text-stone-100 hover:bg-white/15"
97
+ when :soft
98
+ [palette.fetch(:soft_border), palette.fetch(:soft_bg), palette.fetch(:soft_text), palette.fetch(:soft_hover)].join(" ")
99
+ when :ghost
100
+ ["border-white/10", "bg-white/5", "text-stone-200", palette.fetch(:ghost_hover_border), palette.fetch(:ghost_hover_bg), palette.fetch(:ghost_hover_text)].join(" ")
101
+ else
102
+ raise ArgumentError, "unsupported action variant: #{variant}"
103
+ end
104
+ end
105
+ end
106
+
107
+ class ActionBar < Frontend::Component
108
+ DEFAULT_CLASS = "flex flex-wrap gap-3".freeze
109
+
110
+ def initialize(tag: :div, class_name: DEFAULT_CLASS, &block)
111
+ @tag = tag
112
+ @class_name = class_name
113
+ @block = block
114
+ end
115
+
116
+ def call(view)
117
+ view.tag(@tag, class: @class_name) do |container|
118
+ @block&.call(container)
119
+ end
120
+ end
121
+ end
122
+
123
+ class InlineActions < ActionBar
124
+ DEFAULT_CLASS = "mt-4 flex flex-wrap gap-3".freeze
125
+
126
+ def initialize(tag: :div, class_name: DEFAULT_CLASS, &block)
127
+ super(tag: tag, class_name: class_name, &block)
128
+ end
129
+ end
130
+
131
+ class Banner < Frontend::Component
132
+ DEFAULT_BASE_CLASS = "rounded-2xl border px-4 py-3 text-sm".freeze
133
+
134
+ def initialize(message:, tone: :notice, tag: :div, base_class: DEFAULT_BASE_CLASS, tone_class: nil)
135
+ @message = message
136
+ @tone = tone.to_sym
137
+ @tag = tag
138
+ @base_class = base_class
139
+ @tone_class = tone_class
140
+ end
141
+
142
+ def call(view)
143
+ view.tag(@tag, @message, class: [@base_class, resolved_tone_class])
144
+ end
145
+
146
+ private
147
+
148
+ def resolved_tone_class
149
+ return @tone_class if @tone_class
150
+
151
+ case @tone
152
+ when :success
153
+ "border-emerald-300/20 bg-emerald-300/10 text-emerald-100"
154
+ when :warning
155
+ "border-amber-300/20 bg-amber-300/10 text-amber-100"
156
+ when :error
157
+ "border-rose-300/20 bg-rose-300/10 text-rose-100"
158
+ when :info
159
+ "border-cyan-300/20 bg-cyan-300/10 text-cyan-100"
160
+ else
161
+ "border-orange-300/20 bg-orange-300/10 text-orange-100"
162
+ end
163
+ end
164
+ end
165
+
166
+ class Field < Frontend::Component
167
+ DEFAULT_WRAPPER_CLASS = "field".freeze
168
+ DEFAULT_LABEL_CLASS = "mb-2 block text-sm font-semibold uppercase tracking-[0.18em] text-stone-300".freeze
169
+ DEFAULT_HINT_CLASS = "mt-2 text-sm text-stone-400".freeze
170
+ DEFAULT_ERROR_CLASS = "mt-2 text-sm text-rose-200".freeze
171
+
172
+ def initialize(id:, label: nil, error: nil, hint: nil, wrapper_class: DEFAULT_WRAPPER_CLASS,
173
+ label_class: DEFAULT_LABEL_CLASS, hint_class: DEFAULT_HINT_CLASS,
174
+ error_class: DEFAULT_ERROR_CLASS, &block)
175
+ @id = id
176
+ @label = label
177
+ @error = error
178
+ @hint = hint
179
+ @wrapper_class = wrapper_class
180
+ @label_class = label_class
181
+ @hint_class = hint_class
182
+ @error_class = error_class
183
+ @block = block
184
+ end
185
+
186
+ def call(view)
187
+ view.tag(:div, class: @wrapper_class) do |container|
188
+ container.tag(:label, @label, for: @id, class: @label_class) if @label
189
+ @block&.call(container)
190
+ container.tag(:p, @hint, class: @hint_class) if @hint
191
+ container.tag(:p, @error, class: @error_class) if @error
192
+ end
193
+ end
194
+ end
195
+
196
+ class SubmissionNotice < Frontend::Component
197
+ def initialize(message:, tone: :notice, tag: :div, **banner_options)
198
+ @message = message
199
+ @tone = tone
200
+ @tag = tag
201
+ @banner_options = banner_options
202
+ end
203
+
204
+ def call(view)
205
+ view.component(Banner.new(message: @message, tone: @tone, tag: @tag, **@banner_options))
206
+ end
207
+ end
208
+
209
+ class FieldGroup < Frontend::Component
210
+ def initialize(id:, label:, error: nil, hint: nil, **field_options, &block)
211
+ @id = id
212
+ @label = label
213
+ @error = error
214
+ @hint = hint
215
+ @field_options = field_options
216
+ @block = block
217
+ end
218
+
219
+ def call(view)
220
+ view.component(
221
+ Field.new(
222
+ id: @id,
223
+ label: @label,
224
+ error: @error,
225
+ hint: @hint,
226
+ **@field_options,
227
+ &@block
228
+ )
229
+ )
230
+ end
231
+ end
232
+
233
+ class ChoiceField < Frontend::Component
234
+ def initialize(kind:, name:, id:, label:, error: nil, hint: nil, options: nil, selected: nil,
235
+ checked: nil, value: "1", input_class: nil, checkbox_label_class: nil,
236
+ checkbox_class: nil)
237
+ @kind = kind.to_sym
238
+ @name = name
239
+ @id = id
240
+ @label = label
241
+ @error = error
242
+ @hint = hint
243
+ @options = options
244
+ @selected = selected
245
+ @checked = checked
246
+ @value = value
247
+ @input_class = input_class
248
+ @checkbox_label_class = checkbox_label_class
249
+ @checkbox_class = checkbox_class
250
+ end
251
+
252
+ def call(view)
253
+ case @kind
254
+ when :select
255
+ view.component(
256
+ FieldGroup.new(id: @id, label: @label, error: @error, hint: @hint) do |field|
257
+ FormBuilder.new(field).select(
258
+ @name,
259
+ id: @id,
260
+ selected: @selected,
261
+ options: @options,
262
+ class: @input_class
263
+ )
264
+ end
265
+ )
266
+ when :checkbox
267
+ view.component(
268
+ Field.new(id: @id, error: @error, hint: @hint) do |field|
269
+ field.tag(:label, class: @checkbox_label_class, for: @id) do |label|
270
+ label.raw(
271
+ Frontend.render do |nested|
272
+ FormBuilder.new(nested).checkbox(
273
+ @name,
274
+ value: @value,
275
+ checked: @checked,
276
+ id: @id,
277
+ class: @checkbox_class
278
+ )
279
+ end
280
+ )
281
+ label.text(" #{@label}")
282
+ end
283
+ end
284
+ )
285
+ else
286
+ raise ArgumentError, "unsupported choice field kind: #{@kind}"
287
+ end
288
+ end
289
+ end
290
+
291
+ class SchemaHero < Frontend::Component
292
+ def initialize(title:, description: nil, eyebrow: "Schema Page",
293
+ wrapper_class:, eyebrow_class:, title_class:, body_class:)
294
+ @title = title
295
+ @description = description
296
+ @eyebrow = eyebrow
297
+ @wrapper_class = wrapper_class
298
+ @eyebrow_class = eyebrow_class
299
+ @title_class = title_class
300
+ @body_class = body_class
301
+ end
302
+
303
+ def call(view)
304
+ view.tag(:section, class: @wrapper_class) do |hero|
305
+ hero.tag(:p, @eyebrow, class: @eyebrow_class)
306
+ hero.tag(:h1, @title, class: @title_class)
307
+ hero.tag(:p, @description, class: @body_class) if @description
308
+ end
309
+ end
310
+ end
311
+
312
+ class SchemaStack < Frontend::Component
313
+ DEFAULT_CLASS = "view-stack grid gap-5".freeze
314
+
315
+ def initialize(class_name: DEFAULT_CLASS, &block)
316
+ @class_name = class_name
317
+ @block = block
318
+ end
319
+
320
+ def call(view)
321
+ view.tag(:section, class: @class_name) do |container|
322
+ @block&.call(container)
323
+ end
324
+ end
325
+ end
326
+
327
+ class SchemaGrid < Frontend::Component
328
+ DEFAULT_CLASS = "view-grid grid gap-5 sm:grid-cols-2".freeze
329
+
330
+ def initialize(class_name: DEFAULT_CLASS, &block)
331
+ @class_name = class_name
332
+ @block = block
333
+ end
334
+
335
+ def call(view)
336
+ view.tag(:section, class: @class_name) do |container|
337
+ @block&.call(container)
338
+ end
339
+ end
340
+ end
341
+
342
+ class SchemaSection < Frontend::Component
343
+ DEFAULT_CLASS = "view-section rounded-[28px] border border-white/10 bg-[#2a1914]/90 p-6 shadow-2xl shadow-black/20 backdrop-blur".freeze
344
+
345
+ def initialize(tag: :section, class_name: DEFAULT_CLASS, &block)
346
+ @tag = tag
347
+ @class_name = class_name
348
+ @block = block
349
+ end
350
+
351
+ def call(view)
352
+ view.tag(@tag, class: @class_name) do |container|
353
+ @block&.call(container)
354
+ end
355
+ end
356
+ end
357
+
358
+ class SchemaCard < Frontend::Component
359
+ DEFAULT_CLASS = "view-card rounded-[28px] border border-orange-200/15 bg-[linear-gradient(145deg,rgba(60,33,21,0.92),rgba(30,18,14,0.96))] p-6 shadow-2xl shadow-black/25".freeze
360
+
361
+ def initialize(class_name: DEFAULT_CLASS, &block)
362
+ @class_name = class_name
363
+ @block = block
364
+ end
365
+
366
+ def call(view)
367
+ view.tag(:article, class: @class_name) do |container|
368
+ @block&.call(container)
369
+ end
370
+ end
371
+ end
372
+
373
+ class SchemaIntro < Frontend::Component
374
+ def initialize(text:, tone: nil, tag: :p, class_name:, muted_class:)
375
+ @text = text
376
+ @tone = tone
377
+ @tag = tag
378
+ @class_name = class_name
379
+ @muted_class = muted_class
380
+ end
381
+
382
+ def call(view)
383
+ view.tag(@tag, @text, class: @tone.to_s == "muted" ? @muted_class : @class_name)
384
+ end
385
+ end
386
+
387
+ class SchemaFieldset < Frontend::Component
388
+ def initialize(legend: nil, description: nil, class_name:, legend_class:, description_class:, &block)
389
+ @legend = legend
390
+ @description = description
391
+ @class_name = class_name
392
+ @legend_class = legend_class
393
+ @description_class = description_class
394
+ @block = block
395
+ end
396
+
397
+ def call(view)
398
+ view.tag(:fieldset, class: @class_name) do |fieldset|
399
+ fieldset.tag(:legend, @legend, class: @legend_class) if @legend
400
+ fieldset.tag(:p, @description, class: @description_class) if @description
401
+ @block&.call(fieldset)
402
+ end
403
+ end
404
+ end
405
+
406
+ class SchemaForm < Frontend::Component
407
+ def initialize(action:, method: "post", hidden_action: nil, class_name:, fieldset:, &block)
408
+ @action = action
409
+ @method = method
410
+ @hidden_action = hidden_action
411
+ @class_name = class_name
412
+ @fieldset = fieldset
413
+ @block = block
414
+ end
415
+
416
+ def call(view)
417
+ view.form(action: @action, method: @method, class: @class_name) do |form|
418
+ form.hidden("_action", @hidden_action) if @hidden_action
419
+ form.view.component(
420
+ SchemaFieldset.new(**@fieldset) do |fieldset|
421
+ @block&.call(form, fieldset)
422
+ end
423
+ )
424
+ end
425
+ end
426
+ end
427
+
428
+ class MessagePage < Frontend::Component
429
+ DEFAULT_WRAPPER_CLASS = "relative overflow-hidden rounded-[34px] border border-orange-200/15 bg-[radial-gradient(circle_at_top_left,_rgba(194,107,61,0.24),_transparent_18rem),linear-gradient(145deg,rgba(60,33,21,0.96),rgba(22,15,13,0.98))] px-6 py-8 shadow-2xl shadow-black/25 sm:px-8 lg:px-10".freeze
430
+ DEFAULT_GLOW_CLASS = "absolute inset-y-0 right-0 hidden w-72 bg-[radial-gradient(circle_at_center,_rgba(251,146,60,0.14),_transparent_65%)] lg:block".freeze
431
+ DEFAULT_CONTENT_CLASS = "relative z-10 max-w-3xl".freeze
432
+ DEFAULT_EYEBROW_CLASS = "text-[11px] font-semibold uppercase tracking-[0.34em] text-orange-200/75".freeze
433
+ DEFAULT_TITLE_CLASS = "mt-3 font-display text-4xl leading-tight text-white sm:text-5xl".freeze
434
+ DEFAULT_MESSAGE_CLASS = "mt-4 text-base leading-7 text-stone-300 sm:text-lg".freeze
435
+ DEFAULT_DETAIL_WRAPPER_CLASS = "mt-4".freeze
436
+ DEFAULT_DETAIL_CLASS = "rounded-xl bg-black/30 px-3 py-2 font-mono text-xs leading-6 text-orange-100".freeze
437
+ DEFAULT_ACTION_BAR_CLASS = "mt-6 flex flex-wrap gap-3".freeze
438
+ DEFAULT_ACTION_CLASS = Tokens.action(variant: :primary, theme: :orange).freeze
439
+
440
+ def initialize(title:, eyebrow:, message:, back_label:, back_path:, detail: nil,
441
+ wrapper_class: DEFAULT_WRAPPER_CLASS, glow_class: DEFAULT_GLOW_CLASS,
442
+ content_class: DEFAULT_CONTENT_CLASS, eyebrow_class: DEFAULT_EYEBROW_CLASS,
443
+ title_class: DEFAULT_TITLE_CLASS, message_class: DEFAULT_MESSAGE_CLASS,
444
+ detail_wrapper_class: DEFAULT_DETAIL_WRAPPER_CLASS, detail_class: DEFAULT_DETAIL_CLASS,
445
+ action_bar_class: DEFAULT_ACTION_BAR_CLASS, action_class: DEFAULT_ACTION_CLASS)
446
+ @title = title
447
+ @eyebrow = eyebrow
448
+ @message = message
449
+ @back_label = back_label
450
+ @back_path = back_path
451
+ @detail = detail
452
+ @wrapper_class = wrapper_class
453
+ @glow_class = glow_class
454
+ @content_class = content_class
455
+ @eyebrow_class = eyebrow_class
456
+ @title_class = title_class
457
+ @message_class = message_class
458
+ @detail_wrapper_class = detail_wrapper_class
459
+ @detail_class = detail_class
460
+ @action_bar_class = action_bar_class
461
+ @action_class = action_class
462
+ end
463
+
464
+ def call(view)
465
+ view.tag(:section, class: @wrapper_class) do |hero|
466
+ hero.tag(:div, class: @glow_class) unless @glow_class.to_s.empty?
467
+ hero.tag(:div, class: @content_class) do |content|
468
+ content.tag(:p, @eyebrow, class: @eyebrow_class)
469
+ content.tag(:h1, @title, class: @title_class)
470
+ content.tag(:p, @message, class: @message_class)
471
+ if @detail
472
+ content.tag(:p, class: @detail_wrapper_class) do |paragraph|
473
+ paragraph.tag(:code, @detail, class: @detail_class)
474
+ end
475
+ end
476
+ content.component(
477
+ ActionBar.new(class_name: @action_bar_class) do |actions|
478
+ actions.tag(:a, @back_label, href: @back_path, class: @action_class)
479
+ end
480
+ )
481
+ end
482
+ end
483
+ end
484
+ end
485
+
486
+ class MetricCard < Frontend::Component
487
+ DEFAULT_WRAPPER_CLASS = "rounded-[28px] border border-white/10 bg-white/5 p-5 shadow-2xl shadow-black/20 backdrop-blur".freeze
488
+ DEFAULT_LABEL_CLASS = "text-[11px] font-semibold uppercase tracking-[0.28em] text-amber-200/70".freeze
489
+ DEFAULT_VALUE_CLASS = "mt-3 block font-display text-4xl text-white".freeze
490
+ DEFAULT_HINT_CLASS = "mt-2 block text-sm text-stone-400".freeze
491
+
492
+ def initialize(label:, value:, hint: nil, wrapper_class: DEFAULT_WRAPPER_CLASS, label_class: DEFAULT_LABEL_CLASS,
493
+ value_class: DEFAULT_VALUE_CLASS, hint_class: DEFAULT_HINT_CLASS,
494
+ wrapper_attributes: {}, value_attributes: {})
495
+ @label = label
496
+ @value = value
497
+ @hint = hint
498
+ @wrapper_class = wrapper_class
499
+ @label_class = label_class
500
+ @value_class = value_class
501
+ @hint_class = hint_class
502
+ @wrapper_attributes = wrapper_attributes
503
+ @value_attributes = value_attributes
504
+ end
505
+
506
+ def call(view)
507
+ view.tag(:article, **@wrapper_attributes.merge(class: @wrapper_class)) do |card|
508
+ card.tag(:span, @label, class: @label_class)
509
+ card.tag(:strong, @value.to_s, **@value_attributes.merge(class: @value_class))
510
+ card.tag(:span, @hint, class: @hint_class) if @hint
511
+ end
512
+ end
513
+ end
514
+
515
+ class Panel < Frontend::Component
516
+ DEFAULT_WRAPPER_CLASS = "rounded-[30px] border border-white/10 bg-stone-900/80 p-6 shadow-2xl shadow-black/20 backdrop-blur".freeze
517
+ DEFAULT_HEAD_CLASS = "mb-5 flex flex-col gap-2".freeze
518
+ DEFAULT_TITLE_CLASS = "font-display text-2xl text-white".freeze
519
+ DEFAULT_SUBTITLE_CLASS = "text-sm leading-6 text-stone-400".freeze
520
+
521
+ def initialize(title:, subtitle: nil, tag: :section, wrapper_class: DEFAULT_WRAPPER_CLASS,
522
+ head_class: DEFAULT_HEAD_CLASS, title_class: DEFAULT_TITLE_CLASS,
523
+ subtitle_class: DEFAULT_SUBTITLE_CLASS, &block)
524
+ @title = title
525
+ @subtitle = subtitle
526
+ @tag = tag
527
+ @wrapper_class = wrapper_class
528
+ @head_class = head_class
529
+ @title_class = title_class
530
+ @subtitle_class = subtitle_class
531
+ @block = block
532
+ end
533
+
534
+ def call(view)
535
+ view.tag(@tag, class: @wrapper_class) do |panel|
536
+ panel.tag(:div, class: @head_class) do |head|
537
+ head.tag(:h2, @title, class: @title_class)
538
+ head.tag(:p, @subtitle, class: @subtitle_class) if @subtitle
539
+ end
540
+ @block.call(panel)
541
+ end
542
+ end
543
+ end
544
+
545
+ class FormSection < Frontend::Component
546
+ DEFAULT_FORM_CLASS = "grid gap-3".freeze
547
+
548
+ def initialize(title:, action:, subtitle: nil, method: "post", tag: :section, wrapper_class: Panel::DEFAULT_WRAPPER_CLASS,
549
+ head_class: Panel::DEFAULT_HEAD_CLASS, title_class: Panel::DEFAULT_TITLE_CLASS,
550
+ subtitle_class: Panel::DEFAULT_SUBTITLE_CLASS, form_class: DEFAULT_FORM_CLASS, &block)
551
+ @title = title
552
+ @action = action
553
+ @subtitle = subtitle
554
+ @method = method
555
+ @tag = tag
556
+ @wrapper_class = wrapper_class
557
+ @head_class = head_class
558
+ @title_class = title_class
559
+ @subtitle_class = subtitle_class
560
+ @form_class = form_class
561
+ @block = block
562
+ end
563
+
564
+ def call(view)
565
+ view.component(
566
+ Panel.new(
567
+ title: @title,
568
+ subtitle: @subtitle,
569
+ tag: @tag,
570
+ wrapper_class: @wrapper_class,
571
+ head_class: @head_class,
572
+ title_class: @title_class,
573
+ subtitle_class: @subtitle_class
574
+ ) do |panel|
575
+ panel.form(action: @action, method: @method, class: @form_class) do |form|
576
+ yield_form(form, panel)
577
+ end
578
+ end
579
+ )
580
+ end
581
+
582
+ private
583
+
584
+ def yield_form(form, panel)
585
+ return if @block.nil?
586
+
587
+ if @block.arity >= 2
588
+ @block.call(form, panel)
589
+ else
590
+ @block.call(form)
591
+ end
592
+ end
593
+ end
594
+
595
+ class KeyValueList < Frontend::Component
596
+ DEFAULT_WRAPPER_CLASS = "kv-list mt-1 grid grid-cols-1 gap-x-6 gap-y-4 sm:grid-cols-[minmax(140px,190px)_1fr]".freeze
597
+ DEFAULT_KEY_CLASS = "text-sm font-semibold uppercase tracking-[0.18em] text-stone-400".freeze
598
+ DEFAULT_VALUE_CLASS = "break-words text-sm leading-6 text-stone-200".freeze
599
+
600
+ def initialize(rows:, wrapper_class: DEFAULT_WRAPPER_CLASS, key_class: DEFAULT_KEY_CLASS, value_class: DEFAULT_VALUE_CLASS)
601
+ @rows = rows
602
+ @wrapper_class = wrapper_class
603
+ @key_class = key_class
604
+ @value_class = value_class
605
+ end
606
+
607
+ def call(view)
608
+ view.tag(:dl, class: @wrapper_class) do |list|
609
+ @rows.each do |key, value|
610
+ list.tag(:dt, key.to_s, class: @key_class)
611
+ list.tag(:dd, value.to_s, class: @value_class)
612
+ end
613
+ end
614
+ end
615
+ end
616
+
617
+ class PropertyCard < Frontend::Component
618
+ def initialize(title:, href: nil, body: nil, meta: nil, code: nil, action_label: nil, action_href: nil,
619
+ wrapper_class:, title_class:, body_class:, meta_class:, code_class:, link_class: nil,
620
+ action_class: nil)
621
+ @title = title
622
+ @href = href
623
+ @body = body
624
+ @meta = meta
625
+ @code = code
626
+ @action_label = action_label
627
+ @action_href = action_href
628
+ @wrapper_class = wrapper_class
629
+ @title_class = title_class
630
+ @body_class = body_class
631
+ @meta_class = meta_class
632
+ @code_class = code_class
633
+ @link_class = link_class
634
+ @action_class = action_class
635
+ end
636
+
637
+ def call(view)
638
+ view.tag(:li, class: @wrapper_class) do |item|
639
+ render_title(item)
640
+ item.tag(:div, @body, class: "#{@body_class} mt-2") if @body
641
+ item.tag(:div, @meta, class: "#{@meta_class} mt-2") if @meta
642
+ item.tag(:code, @code, class: "#{@code_class} mt-2 block") if @code
643
+ return unless @action_label && @action_href
644
+
645
+ item.tag(:a, @action_label, href: @action_href, class: @action_class)
646
+ end
647
+ end
648
+
649
+ private
650
+
651
+ def render_title(view)
652
+ if @href
653
+ view.tag(:strong, class: @title_class) do |heading|
654
+ heading.tag(:a, @title, href: @href, class: @link_class)
655
+ end
656
+ else
657
+ view.tag(:strong, @title, class: @title_class)
658
+ end
659
+ end
660
+ end
661
+
662
+ class ResourceList < Frontend::Component
663
+ def initialize(items:, theme:, empty_message: nil, compact: false)
664
+ @items = items
665
+ @theme = theme
666
+ @empty_message = empty_message
667
+ @compact = compact
668
+ end
669
+
670
+ def call(view)
671
+ if @items.empty?
672
+ view.tag(:p, @empty_message, class: @theme.empty_state_class) if @empty_message
673
+ return
674
+ end
675
+
676
+ view.tag(:ul, class: list_class) do |list|
677
+ @items.each do |item|
678
+ list.component(
679
+ PropertyCard.new(
680
+ title: item.fetch(:title),
681
+ body: item[:body],
682
+ meta: item[:meta],
683
+ code: item[:code],
684
+ wrapper_class: card_class,
685
+ title_class: @theme.item_title_class,
686
+ body_class: @theme.body_text_class,
687
+ meta_class: @theme.muted_text_class,
688
+ code_class: @theme.code_class
689
+ )
690
+ )
691
+ end
692
+ end
693
+ end
694
+
695
+ private
696
+
697
+ def list_class
698
+ @compact ? @theme.compact_list_class : @theme.list_class
699
+ end
700
+
701
+ def card_class
702
+ @compact ? @theme.compact_card_class : @theme.list_item_class
703
+ end
704
+ end
705
+
706
+ class EndpointList < Frontend::Component
707
+ def initialize(items:, theme:, empty_message: nil, compact: false, link_class:)
708
+ @items = items
709
+ @theme = theme
710
+ @empty_message = empty_message
711
+ @compact = compact
712
+ @link_class = link_class
713
+ end
714
+
715
+ def call(view)
716
+ if @items.empty?
717
+ view.tag(:p, @empty_message, class: @theme.empty_state_class) if @empty_message
718
+ return
719
+ end
720
+
721
+ view.tag(:ul, class: list_class) do |list|
722
+ @items.each do |item|
723
+ list.component(
724
+ PropertyCard.new(
725
+ title: item.fetch(:title),
726
+ body: item[:body],
727
+ meta: item[:meta],
728
+ href: item[:href],
729
+ wrapper_class: card_class,
730
+ title_class: @theme.item_title_class,
731
+ body_class: @theme.body_text_class,
732
+ meta_class: @theme.muted_text_class,
733
+ code_class: @theme.code_class,
734
+ link_class: @link_class
735
+ )
736
+ )
737
+ end
738
+ end
739
+ end
740
+
741
+ private
742
+
743
+ def list_class
744
+ @compact ? @theme.compact_list_class : @theme.list_class
745
+ end
746
+
747
+ def card_class
748
+ @compact ? @theme.compact_card_class : @theme.list_item_class
749
+ end
750
+ end
751
+
752
+ class TimelineList < Frontend::Component
753
+ def initialize(items:, theme:, empty_message: nil, title_link_class:, action_link_class:)
754
+ @items = items
755
+ @theme = theme
756
+ @empty_message = empty_message
757
+ @title_link_class = title_link_class
758
+ @action_link_class = action_link_class
759
+ end
760
+
761
+ def call(view)
762
+ if @items.empty?
763
+ view.tag(:p, @empty_message, class: @theme.empty_state_class) if @empty_message
764
+ return
765
+ end
766
+
767
+ view.tag(:ul, class: @theme.list_class) do |list|
768
+ @items.each do |item|
769
+ list.component(
770
+ PropertyCard.new(
771
+ title: item.fetch(:title),
772
+ href: item.fetch(:href),
773
+ body: item[:body],
774
+ meta: item[:meta],
775
+ action_label: item[:action_label],
776
+ action_href: item[:action_href],
777
+ wrapper_class: @theme.list_item_class,
778
+ title_class: @theme.item_title_class,
779
+ body_class: @theme.muted_text_class,
780
+ meta_class: @theme.muted_text_class,
781
+ code_class: @theme.code_class,
782
+ link_class: @title_link_class,
783
+ action_class: @action_link_class
784
+ )
785
+ )
786
+ end
787
+ end
788
+ end
789
+ end
790
+
791
+ class PayloadDiff < Frontend::Component
792
+ def initialize(raw_payload:, normalized_payload:, theme:, empty_message: nil)
793
+ @raw_payload = raw_payload
794
+ @normalized_payload = normalized_payload
795
+ @theme = theme
796
+ @empty_message = empty_message || "No payload differences detected."
797
+ end
798
+
799
+ def call(view)
800
+ entries = diff_entries
801
+
802
+ if entries.empty?
803
+ view.tag(:p, @empty_message, class: @theme.empty_state_class)
804
+ return
805
+ end
806
+
807
+ view.tag(:ul, class: @theme.list_class) do |list|
808
+ entries.each do |entry|
809
+ list.tag(:li, class: @theme.list_item_class) do |item|
810
+ item.tag(:strong, entry.fetch(:path), class: @theme.item_title_class)
811
+ item.tag(:div, diff_summary(entry), class: @theme.body_text_class(extra: "mt-2"))
812
+ item.tag(:div, class: "mt-3 grid gap-3 lg:grid-cols-2") do |grid|
813
+ grid.tag(:div) do |column|
814
+ column.tag(:div, "raw", class: @theme.muted_text_class)
815
+ column.tag(:code, entry.fetch(:raw_label), class: "#{@theme.code_class} mt-2 block")
816
+ end
817
+ grid.tag(:div) do |column|
818
+ column.tag(:div, "normalized", class: @theme.muted_text_class)
819
+ column.tag(:code, entry.fetch(:normalized_label), class: "#{@theme.code_class} mt-2 block")
820
+ end
821
+ end
822
+ end
823
+ end
824
+ end
825
+ end
826
+
827
+ private
828
+
829
+ def diff_entries
830
+ raw_index = flatten_payload(@raw_payload)
831
+ normalized_index = flatten_payload(@normalized_payload)
832
+ paths = (raw_index.keys + normalized_index.keys).uniq.sort
833
+
834
+ paths.filter_map do |path|
835
+ raw_value = raw_index[path]
836
+ normalized_value = normalized_index[path]
837
+ status = diff_status(raw_value, normalized_value)
838
+ next if status == :unchanged
839
+
840
+ {
841
+ path: path,
842
+ status: status,
843
+ raw_label: value_label(raw_value),
844
+ normalized_label: value_label(normalized_value)
845
+ }
846
+ end
847
+ end
848
+
849
+ def flatten_payload(payload, prefix = nil, result = {})
850
+ case payload
851
+ when Hash
852
+ payload.each do |key, value|
853
+ next if key.to_s == "_action"
854
+
855
+ path = [prefix, key.to_s].compact.join(".")
856
+ flatten_payload(value, path, result)
857
+ end
858
+ when Array
859
+ payload.each_with_index do |value, index|
860
+ path = [prefix, index].compact.join(".")
861
+ flatten_payload(value, path, result)
862
+ end
863
+ else
864
+ result[prefix.to_s] = payload
865
+ end
866
+
867
+ result
868
+ end
869
+
870
+ def diff_status(raw_value, normalized_value)
871
+ return :added if raw_value.nil? && !normalized_value.nil?
872
+ return :removed if !raw_value.nil? && normalized_value.nil?
873
+ return :unchanged if raw_value == normalized_value && raw_value.class == normalized_value.class
874
+ return :type_changed if raw_value.to_s == normalized_value.to_s && raw_value.class != normalized_value.class
875
+
876
+ :changed
877
+ end
878
+
879
+ def diff_summary(entry)
880
+ case entry.fetch(:status)
881
+ when :type_changed
882
+ "Type changed during normalization."
883
+ when :added
884
+ "Field was added by normalization."
885
+ when :removed
886
+ "Field was removed during normalization."
887
+ else
888
+ "Field value changed during normalization."
889
+ end
890
+ end
891
+
892
+ def value_label(value)
893
+ return "(missing)" if value.nil?
894
+
895
+ JSON.generate(value)
896
+ end
897
+ end
898
+
899
+ class BarChart < Frontend::Component
900
+ def initialize(items:, theme:, chart_id:, empty_message: nil, value_suffix: "", max_value: nil)
901
+ @items = items
902
+ @theme = theme
903
+ @chart_id = chart_id
904
+ @empty_message = empty_message || "No chart data yet."
905
+ @value_suffix = value_suffix
906
+ @max_value = max_value
907
+ end
908
+
909
+ def call(view)
910
+ if @items.empty?
911
+ view.tag(:p, @empty_message, class: @theme.empty_state_class)
912
+ return
913
+ end
914
+
915
+ view.tag(:ul, class: "grid gap-3", "data-chart-id": @chart_id) do |list|
916
+ @items.each do |item|
917
+ render_item(list, item)
918
+ end
919
+ end
920
+ end
921
+
922
+ private
923
+
924
+ def render_item(view, item)
925
+ key = item.fetch(:key, item.fetch(:label).to_s.downcase.gsub(/[^a-z0-9]+/, "_"))
926
+ value = item.fetch(:value).to_f
927
+ max = @max_value || @items.map { |entry| entry.fetch(:value).to_f }.max || 0
928
+ percent = max <= 0 ? 0 : ((value / max) * 100.0).round(1)
929
+
930
+ view.tag(:li, class: "rounded-2xl border border-white/10 bg-white/5 p-4", "data-chart-key": key) do |row|
931
+ row.tag(:div, class: "flex items-center justify-between gap-4") do |meta|
932
+ meta.tag(:strong, item.fetch(:label), class: @theme.item_title_class)
933
+ meta.tag(:span, value_label(item.fetch(:value)), class: @theme.muted_text_class, "data-chart-value": key)
934
+ end
935
+ row.tag(:div, class: "mt-3 h-2.5 overflow-hidden rounded-full bg-white/10") do |bar|
936
+ bar.tag(:div,
937
+ "",
938
+ class: "h-full rounded-full bg-gradient-to-r from-amber-300 via-orange-300 to-cyan-300 transition-all",
939
+ style: "width: #{percent}%",
940
+ "data-chart-fill": key)
941
+ end
942
+ row.tag(:p, item.fetch(:hint), class: @theme.muted_text_class(extra: "mt-2")) if item[:hint]
943
+ end
944
+ end
945
+
946
+ def value_label(value)
947
+ "#{value}#{@value_suffix}"
948
+ end
949
+ end
950
+
951
+ class MermaidDiagram < Frontend::Component
952
+ def initialize(diagram:, title: nil, description: nil, wrapper_class: "grid gap-3")
953
+ @diagram = diagram
954
+ @title = title
955
+ @description = description
956
+ @wrapper_class = wrapper_class
957
+ end
958
+
959
+ def call(view)
960
+ view.tag(:div, class: @wrapper_class) do |container|
961
+ container.tag(:div, @title, class: "text-sm font-semibold uppercase tracking-[0.18em] text-stone-300") if @title
962
+ container.tag(:p, @description, class: "text-sm leading-6 text-stone-400") if @description
963
+ container.tag(:pre,
964
+ @diagram,
965
+ class: "mermaid overflow-x-auto whitespace-pre rounded-3xl border border-white/10 bg-black/20 p-4 font-mono text-xs leading-6 text-stone-200")
966
+ end
967
+ end
968
+ end
969
+
970
+ class LiveBadge < Frontend::Component
971
+ def initialize(label:, value:, interval_seconds:)
972
+ @label = label
973
+ @value = value
974
+ @interval_seconds = interval_seconds
975
+ end
976
+
977
+ def call(view)
978
+ view.tag(:div,
979
+ class: "inline-flex flex-wrap items-center gap-3 rounded-full border border-cyan-300/20 bg-cyan-300/10 px-4 py-2 text-sm text-cyan-100",
980
+ "data-live-badge": "true") do |badge|
981
+ badge.tag(:span, @label, class: "text-[11px] font-semibold uppercase tracking-[0.24em] text-cyan-100/80")
982
+ badge.tag(:strong, @value, class: "font-mono text-sm text-white", "data-live-generated-at": "true")
983
+ badge.tag(:span, "poll #{@interval_seconds}s", class: "text-xs text-stone-400")
984
+ end
985
+ end
986
+ end
987
+
988
+ class StatusBadge < Frontend::Component
989
+ DEFAULT_BASE_CLASS = "status-badge inline-flex items-center rounded-full border px-2.5 py-1 text-[11px] font-semibold uppercase tracking-[0.18em]".freeze
990
+
991
+ def initialize(label:, tone: nil, base_class: DEFAULT_BASE_CLASS, html_attributes: {})
992
+ @label = label.to_s
993
+ @tone = tone
994
+ @base_class = base_class
995
+ @html_attributes = html_attributes
996
+ end
997
+
998
+ def call(view)
999
+ view.tag(:span, @label, **@html_attributes.merge(class: [@base_class, tone_classes]))
1000
+ end
1001
+
1002
+ private
1003
+
1004
+ def tone_classes
1005
+ return @tone if @tone
1006
+
1007
+ case @label.downcase
1008
+ when "ready", "configured", "online", "healthy"
1009
+ "border-emerald-400/30 bg-emerald-400/10 text-emerald-200"
1010
+ when "warning", "stale"
1011
+ "border-amber-300/30 bg-amber-300/10 text-amber-100"
1012
+ when "degraded", "offline"
1013
+ "border-rose-300/30 bg-rose-300/10 text-rose-100"
1014
+ else
1015
+ "border-cyan-300/30 bg-cyan-300/10 text-cyan-100"
1016
+ end
1017
+ end
1018
+ end
1019
+
1020
+ class Theme
1021
+ PRESETS = {
1022
+ default: {
1023
+ panel: {},
1024
+ form_section: {},
1025
+ message_page: {},
1026
+ heroes: {},
1027
+ surfaces: {
1028
+ field_label_class: "text-sm font-semibold uppercase tracking-[0.18em] text-stone-300",
1029
+ input_class: "w-full rounded-2xl border border-white/10 bg-stone-950 px-4 py-3 text-sm text-white placeholder:text-stone-500 focus:border-orange-300/50 focus:outline-none",
1030
+ checkbox_label_class: "flex items-center gap-3 rounded-2xl border border-white/10 bg-white/5 px-4 py-3 text-sm text-stone-200",
1031
+ checkbox_class: "h-4 w-4 rounded border-white/20 bg-stone-950 text-orange-300",
1032
+ code_class: "rounded-xl bg-black/30 px-3 py-2 font-mono text-xs leading-6 text-orange-100",
1033
+ empty_state_class: "text-sm leading-6 text-stone-400",
1034
+ muted_text_class: "text-sm leading-6 text-stone-400",
1035
+ body_text_class: "text-sm leading-6 text-stone-300",
1036
+ item_title_class: "font-semibold text-white",
1037
+ list_class: "space-y-4",
1038
+ compact_list_class: "mt-3 space-y-3",
1039
+ list_item_class: "rounded-3xl border border-white/10 bg-white/5 p-4",
1040
+ compact_card_class: "rounded-2xl border border-white/10 bg-white/5 p-4",
1041
+ compact_item_class: "text-sm leading-6 text-stone-300",
1042
+ section_heading_class: "mt-5 text-sm font-semibold uppercase tracking-[0.22em] text-stone-300",
1043
+ schema_intro_class: "text-base leading-7 text-stone-200",
1044
+ schema_intro_muted_class: "view-muted text-sm leading-6 text-stone-400",
1045
+ schema_form_class: "view-form grid gap-3",
1046
+ schema_fieldset_class: "grid gap-3",
1047
+ schema_fieldset_legend_class: "text-sm font-semibold uppercase tracking-[0.18em] text-stone-300",
1048
+ schema_fieldset_description_class: "text-sm leading-6 text-stone-400",
1049
+ schema_stack_class: SchemaStack::DEFAULT_CLASS,
1050
+ schema_grid_class: SchemaGrid::DEFAULT_CLASS,
1051
+ schema_section_class: SchemaSection::DEFAULT_CLASS,
1052
+ schema_card_class: SchemaCard::DEFAULT_CLASS
1053
+ }
1054
+ },
1055
+ companion: {
1056
+ panel: {
1057
+ wrapper_class: "rounded-[28px] border border-white/10 bg-[#2a1914]/90 p-6 shadow-2xl shadow-black/20 backdrop-blur",
1058
+ subtitle_class: "text-sm leading-6 text-stone-400"
1059
+ },
1060
+ form_section: {
1061
+ wrapper_class: "rounded-[28px] border border-white/10 bg-[#2a1914]/90 p-6 shadow-2xl shadow-black/20 backdrop-blur"
1062
+ },
1063
+ message_page: {
1064
+ wrapper_class: "relative overflow-hidden rounded-[34px] border border-orange-200/15 bg-[radial-gradient(circle_at_top_left,_rgba(194,107,61,0.24),_transparent_18rem),linear-gradient(145deg,rgba(60,33,21,0.96),rgba(22,15,13,0.98))] px-6 py-8 shadow-2xl shadow-black/25 sm:px-8 lg:px-10",
1065
+ glow_class: "absolute inset-y-0 right-0 hidden w-72 bg-[radial-gradient(circle_at_center,_rgba(251,146,60,0.14),_transparent_65%)] lg:block",
1066
+ content_class: "relative z-10 max-w-3xl",
1067
+ eyebrow_class: "text-[11px] font-semibold uppercase tracking-[0.34em] text-orange-200/75",
1068
+ title_class: "mt-3 font-display text-4xl leading-tight text-white sm:text-5xl",
1069
+ message_class: "mt-4 text-base leading-7 text-stone-300 sm:text-lg",
1070
+ detail_class: "rounded-xl bg-black/30 px-3 py-2 font-mono text-xs leading-6 text-orange-100",
1071
+ action_class: Tokens.action(variant: :primary, theme: :orange)
1072
+ },
1073
+ heroes: {
1074
+ dashboard: {
1075
+ wrapper_class: "relative overflow-hidden rounded-[34px] border border-orange-200/15 bg-[radial-gradient(circle_at_top_left,_rgba(194,107,61,0.24),_transparent_18rem),linear-gradient(145deg,rgba(60,33,21,0.96),rgba(22,15,13,0.98))] px-6 py-8 shadow-2xl shadow-black/25 sm:px-8 lg:px-10",
1076
+ glow_class: "absolute inset-y-0 right-0 hidden w-72 bg-[radial-gradient(circle_at_center,_rgba(251,146,60,0.14),_transparent_65%)] lg:block",
1077
+ content_class: "relative z-10 max-w-4xl",
1078
+ eyebrow_class: "text-[11px] font-semibold uppercase tracking-[0.34em] text-orange-200/75",
1079
+ title_class: "mt-3 font-display text-4xl leading-tight text-white sm:text-5xl",
1080
+ body_class: "mt-4 max-w-3xl text-base leading-7 text-stone-300 sm:text-lg",
1081
+ meta_class: "mt-5 flex flex-wrap gap-x-4 gap-y-2 font-mono text-xs text-stone-400"
1082
+ }
1083
+ },
1084
+ surfaces: {
1085
+ footer_bar_class: "rounded-3xl border border-white/10 bg-white/5 px-5 py-4 font-mono text-xs text-stone-300",
1086
+ field_label_class: "text-sm font-semibold uppercase tracking-[0.18em] text-stone-300",
1087
+ input_class: "w-full rounded-2xl border border-white/10 bg-[#160f0d] px-4 py-3 text-sm text-white placeholder:text-stone-500 focus:border-orange-300/50 focus:outline-none",
1088
+ checkbox_label_class: "flex items-center gap-3 rounded-2xl border border-white/10 bg-white/5 px-4 py-3 text-sm text-stone-200",
1089
+ checkbox_class: "h-4 w-4 rounded border-white/20 bg-stone-950 text-orange-300",
1090
+ code_class: "rounded-xl bg-black/30 px-3 py-2 font-mono text-xs leading-6 text-orange-100",
1091
+ empty_state_class: "text-sm leading-6 text-stone-400",
1092
+ muted_text_class: "text-sm leading-6 text-stone-400",
1093
+ body_text_class: "text-sm leading-6 text-stone-300",
1094
+ item_title_class: "font-semibold text-white",
1095
+ list_class: "space-y-4",
1096
+ compact_list_class: "mt-3 space-y-3",
1097
+ list_item_class: "rounded-3xl border border-white/10 bg-white/5 p-4",
1098
+ compact_card_class: "rounded-2xl border border-white/10 bg-white/5 p-4",
1099
+ compact_item_class: "text-sm leading-6 text-stone-300",
1100
+ section_heading_class: "mt-5 text-sm font-semibold uppercase tracking-[0.22em] text-stone-300",
1101
+ schema_intro_class: "text-base leading-7 text-stone-200",
1102
+ schema_intro_muted_class: "view-muted text-sm leading-6 text-stone-400",
1103
+ schema_form_class: "view-form grid gap-3",
1104
+ schema_fieldset_class: "grid gap-3",
1105
+ schema_fieldset_legend_class: "text-sm font-semibold uppercase tracking-[0.18em] text-stone-300",
1106
+ schema_fieldset_description_class: "text-sm leading-6 text-stone-400",
1107
+ schema_stack_class: SchemaStack::DEFAULT_CLASS,
1108
+ schema_grid_class: SchemaGrid::DEFAULT_CLASS,
1109
+ schema_section_class: SchemaSection::DEFAULT_CLASS,
1110
+ schema_card_class: SchemaCard::DEFAULT_CLASS
1111
+ }
1112
+ },
1113
+ ops: {
1114
+ panel: {},
1115
+ form_section: {},
1116
+ message_page: {},
1117
+ heroes: {
1118
+ dashboard: {
1119
+ wrapper_class: "hero relative overflow-hidden rounded-[36px] border border-amber-200/15 bg-[radial-gradient(circle_at_top_left,_rgba(245,158,11,0.22),_transparent_24rem),linear-gradient(145deg,rgba(41,37,36,0.96),rgba(12,10,9,0.98))] px-6 py-8 shadow-glow sm:px-8 lg:px-10",
1120
+ glow_class: "absolute inset-y-0 right-0 hidden w-72 bg-[radial-gradient(circle_at_center,_rgba(251,191,36,0.16),_transparent_65%)] lg:block",
1121
+ content_class: "relative z-10 max-w-4xl",
1122
+ eyebrow_class: "eyebrow text-[11px] font-semibold uppercase tracking-[0.34em] text-amber-200/75",
1123
+ title_class: "mt-3 font-display text-4xl leading-tight text-white sm:text-5xl",
1124
+ body_class: "mt-4 max-w-3xl text-base leading-7 text-stone-300 sm:text-lg",
1125
+ meta_class: "meta mt-5 flex flex-wrap gap-x-4 gap-y-2 text-sm text-stone-400",
1126
+ action_bar_class: "hero-links mt-6 flex flex-wrap gap-3"
1127
+ },
1128
+ detail: {
1129
+ wrapper_class: "hero rounded-[34px] border border-white/10 bg-[radial-gradient(circle_at_top_left,_rgba(45,212,191,0.16),_transparent_18rem),linear-gradient(145deg,rgba(28,25,23,0.96),rgba(12,10,9,0.98))] px-6 py-8 shadow-2xl shadow-black/20 sm:px-8",
1130
+ eyebrow_class: "eyebrow text-[11px] font-semibold uppercase tracking-[0.34em] text-cyan-200/75",
1131
+ title_class: "mt-3 font-display text-4xl leading-tight text-white sm:text-5xl",
1132
+ body_class: "mt-4 max-w-3xl text-base leading-7 text-stone-300",
1133
+ action_bar_class: "mt-6"
1134
+ }
1135
+ },
1136
+ surfaces: {
1137
+ field_label_class: "text-sm font-semibold uppercase tracking-[0.18em] text-stone-300",
1138
+ input_class: "w-full rounded-2xl border border-white/10 bg-stone-950/90 px-4 py-3 text-sm text-white placeholder:text-stone-500 focus:border-amber-300/50 focus:outline-none",
1139
+ checkbox_label_class: "flex items-center gap-3 rounded-2xl border border-white/10 bg-white/5 px-4 py-3 text-sm text-stone-200",
1140
+ checkbox_class: "h-4 w-4 rounded border-white/20 bg-stone-950 text-amber-300",
1141
+ code_class: "rounded-xl bg-black/30 px-3 py-2 font-mono text-xs leading-6 text-amber-100",
1142
+ empty_state_class: "text-sm leading-6 text-stone-400",
1143
+ muted_text_class: "text-sm leading-6 text-stone-400",
1144
+ body_text_class: "text-sm leading-6 text-stone-300",
1145
+ item_title_class: "font-semibold text-white",
1146
+ list_class: "item-list space-y-4",
1147
+ compact_list_class: "item-list compact mt-3 space-y-3",
1148
+ list_item_class: "rounded-3xl border border-white/10 bg-white/5 p-4",
1149
+ compact_card_class: "rounded-2xl border border-white/10 bg-white/5 p-4",
1150
+ compact_item_class: "text-sm leading-6 text-stone-300",
1151
+ section_heading_class: "mt-5 text-sm font-semibold uppercase tracking-[0.22em] text-stone-300",
1152
+ schema_intro_class: "text-base leading-7 text-stone-200",
1153
+ schema_intro_muted_class: "view-muted text-sm leading-6 text-stone-400",
1154
+ schema_form_class: "view-form grid gap-3",
1155
+ schema_fieldset_class: "grid gap-3",
1156
+ schema_fieldset_legend_class: "text-sm font-semibold uppercase tracking-[0.18em] text-stone-300",
1157
+ schema_fieldset_description_class: "text-sm leading-6 text-stone-400",
1158
+ schema_stack_class: SchemaStack::DEFAULT_CLASS,
1159
+ schema_grid_class: SchemaGrid::DEFAULT_CLASS,
1160
+ schema_section_class: SchemaSection::DEFAULT_CLASS,
1161
+ schema_card_class: SchemaCard::DEFAULT_CLASS
1162
+ }
1163
+ },
1164
+ schema: {
1165
+ panel: {
1166
+ wrapper_class: "rounded-[28px] border border-white/10 bg-[#2a1914]/90 p-6 shadow-2xl shadow-black/20 backdrop-blur",
1167
+ subtitle_class: "text-sm leading-6 text-stone-400"
1168
+ },
1169
+ form_section: {},
1170
+ message_page: {},
1171
+ heroes: {
1172
+ page: {
1173
+ wrapper_class: "overflow-hidden rounded-[34px] border border-orange-200/15 bg-[radial-gradient(circle_at_top_left,_rgba(194,107,61,0.22),_transparent_18rem),linear-gradient(145deg,rgba(60,33,21,0.96),rgba(22,15,13,0.98))] px-6 py-8 shadow-2xl shadow-black/25 sm:px-8",
1174
+ eyebrow_class: "text-[11px] font-semibold uppercase tracking-[0.34em] text-orange-200/75",
1175
+ title_class: "mt-3 font-display text-4xl leading-tight text-white sm:text-5xl",
1176
+ body_class: "mt-4 max-w-3xl text-base leading-7 text-stone-300"
1177
+ }
1178
+ },
1179
+ surfaces: {
1180
+ field_label_class: "text-sm font-semibold uppercase tracking-[0.18em] text-stone-300",
1181
+ input_class: "w-full rounded-2xl border border-white/10 bg-[#160f0d] px-4 py-3 text-sm text-white placeholder:text-stone-500 focus:border-orange-300/50 focus:outline-none",
1182
+ checkbox_label_class: "flex items-center gap-3 rounded-2xl border border-white/10 bg-white/5 px-4 py-3 text-sm text-stone-200",
1183
+ checkbox_class: "h-4 w-4 rounded border-white/20 bg-stone-950 text-orange-300",
1184
+ code_class: "rounded-xl bg-black/30 px-3 py-2 font-mono text-xs leading-6 text-orange-100",
1185
+ empty_state_class: "text-sm leading-6 text-stone-400",
1186
+ muted_text_class: "text-sm leading-6 text-stone-400",
1187
+ body_text_class: "text-sm leading-6 text-stone-300",
1188
+ item_title_class: "font-semibold text-white",
1189
+ list_class: "space-y-4",
1190
+ compact_list_class: "mt-3 space-y-3",
1191
+ list_item_class: "rounded-3xl border border-white/10 bg-white/5 p-4",
1192
+ compact_card_class: "rounded-2xl border border-white/10 bg-white/5 p-4",
1193
+ compact_item_class: "text-sm leading-6 text-stone-300",
1194
+ section_heading_class: "mt-5 text-sm font-semibold uppercase tracking-[0.22em] text-stone-300",
1195
+ schema_intro_class: "text-base leading-7 text-stone-200",
1196
+ schema_intro_muted_class: "view-muted text-sm leading-6 text-stone-400",
1197
+ schema_form_class: "view-form grid gap-3",
1198
+ schema_fieldset_class: "grid gap-3",
1199
+ schema_fieldset_legend_class: "text-sm font-semibold uppercase tracking-[0.18em] text-stone-300",
1200
+ schema_fieldset_description_class: "text-sm leading-6 text-stone-400",
1201
+ schema_stack_class: SchemaStack::DEFAULT_CLASS,
1202
+ schema_grid_class: SchemaGrid::DEFAULT_CLASS,
1203
+ schema_section_class: SchemaSection::DEFAULT_CLASS,
1204
+ schema_card_class: SchemaCard::DEFAULT_CLASS
1205
+ }
1206
+ }
1207
+ }.freeze
1208
+
1209
+ def self.fetch(name)
1210
+ new(PRESETS.fetch(name.to_sym))
1211
+ end
1212
+
1213
+ def self.deep_merge(base, override)
1214
+ return override unless base.is_a?(Hash) && override.is_a?(Hash)
1215
+
1216
+ base.merge(override) do |_key, left, right|
1217
+ left.is_a?(Hash) && right.is_a?(Hash) ? deep_merge(left, right) : right
1218
+ end
1219
+ end
1220
+
1221
+ def initialize(definition)
1222
+ @definition = definition
1223
+ end
1224
+
1225
+ def panel(title:, subtitle: nil, tag: :section, **overrides, &block)
1226
+ Panel.new(title: title, subtitle: subtitle, tag: tag, **component_options(:panel, overrides), &block)
1227
+ end
1228
+
1229
+ def resource_list(items:, empty_message: nil, compact: false)
1230
+ ResourceList.new(items: items, theme: self, empty_message: empty_message, compact: compact)
1231
+ end
1232
+
1233
+ def bar_chart(items:, chart_id:, empty_message: nil, value_suffix: "", max_value: nil)
1234
+ BarChart.new(
1235
+ items: items,
1236
+ chart_id: chart_id,
1237
+ theme: self,
1238
+ empty_message: empty_message,
1239
+ value_suffix: value_suffix,
1240
+ max_value: max_value
1241
+ )
1242
+ end
1243
+
1244
+ def mermaid_diagram(diagram:, title: nil, description: nil)
1245
+ MermaidDiagram.new(diagram: diagram, title: title, description: description)
1246
+ end
1247
+
1248
+ def live_badge(label:, value:, interval_seconds:)
1249
+ LiveBadge.new(label: label, value: value, interval_seconds: interval_seconds)
1250
+ end
1251
+
1252
+ def schema_hero(title:, description: nil, eyebrow: "Schema Page", **overrides)
1253
+ hero_options = self.class.deep_merge(hero(:page), overrides)
1254
+ SchemaHero.new(title: title, description: description, eyebrow: eyebrow, **hero_options)
1255
+ end
1256
+
1257
+ def schema_intro(text:, tone: nil, tag: :p)
1258
+ SchemaIntro.new(
1259
+ text: text,
1260
+ tone: tone,
1261
+ tag: tag,
1262
+ class_name: surface(:schema_intro_class),
1263
+ muted_class: surface(:schema_intro_muted_class)
1264
+ )
1265
+ end
1266
+
1267
+ def schema_form(action:, method: "post", hidden_action: nil, legend: nil, description: nil, &block)
1268
+ SchemaForm.new(
1269
+ action: action,
1270
+ method: method,
1271
+ hidden_action: hidden_action,
1272
+ class_name: surface(:schema_form_class),
1273
+ fieldset: {
1274
+ legend: legend,
1275
+ description: description,
1276
+ class_name: surface(:schema_fieldset_class),
1277
+ legend_class: surface(:schema_fieldset_legend_class),
1278
+ description_class: surface(:schema_fieldset_description_class)
1279
+ },
1280
+ &block
1281
+ )
1282
+ end
1283
+
1284
+ def schema_stack(class_name: nil, &block)
1285
+ SchemaStack.new(class_name: class_name || surface(:schema_stack_class), &block)
1286
+ end
1287
+
1288
+ def schema_grid(class_name: nil, &block)
1289
+ SchemaGrid.new(class_name: class_name || surface(:schema_grid_class), &block)
1290
+ end
1291
+
1292
+ def schema_section(tag: :section, class_name: nil, &block)
1293
+ SchemaSection.new(tag: tag, class_name: class_name || surface(:schema_section_class), &block)
1294
+ end
1295
+
1296
+ def schema_card(class_name: nil, &block)
1297
+ SchemaCard.new(class_name: class_name || surface(:schema_card_class), &block)
1298
+ end
1299
+
1300
+ def schema_fieldset(legend: nil, description: nil, class_name: nil, &block)
1301
+ SchemaFieldset.new(
1302
+ legend: legend,
1303
+ description: description,
1304
+ class_name: class_name || surface(:schema_fieldset_class),
1305
+ legend_class: surface(:schema_fieldset_legend_class),
1306
+ description_class: surface(:schema_fieldset_description_class),
1307
+ &block
1308
+ )
1309
+ end
1310
+
1311
+ def endpoint_list(items:, empty_message: nil, compact: false, link_class:)
1312
+ EndpointList.new(
1313
+ items: items,
1314
+ theme: self,
1315
+ empty_message: empty_message,
1316
+ compact: compact,
1317
+ link_class: link_class
1318
+ )
1319
+ end
1320
+
1321
+ def timeline_list(items:, empty_message: nil, title_link_class:, action_link_class:)
1322
+ TimelineList.new(
1323
+ items: items,
1324
+ theme: self,
1325
+ empty_message: empty_message,
1326
+ title_link_class: title_link_class,
1327
+ action_link_class: action_link_class
1328
+ )
1329
+ end
1330
+
1331
+ def payload_diff(raw_payload:, normalized_payload:, empty_message: nil)
1332
+ PayloadDiff.new(
1333
+ raw_payload: raw_payload,
1334
+ normalized_payload: normalized_payload,
1335
+ theme: self,
1336
+ empty_message: empty_message
1337
+ )
1338
+ end
1339
+
1340
+ def form_section(title:, action:, subtitle: nil, method: "post", tag: :section, **overrides, &block)
1341
+ FormSection.new(
1342
+ title: title,
1343
+ action: action,
1344
+ subtitle: subtitle,
1345
+ method: method,
1346
+ tag: tag,
1347
+ **component_options(:form_section, overrides),
1348
+ &block
1349
+ )
1350
+ end
1351
+
1352
+ def message_page_options(**overrides)
1353
+ component_options(:message_page, overrides)
1354
+ end
1355
+
1356
+ def hero(name, **overrides)
1357
+ preset = @definition.fetch(:heroes, {}).fetch(name.to_sym)
1358
+ self.class.deep_merge(preset, overrides)
1359
+ end
1360
+
1361
+ def surface(name)
1362
+ @definition.fetch(:surfaces, {}).fetch(name.to_sym)
1363
+ end
1364
+
1365
+ def field_label_class(extra: nil)
1366
+ merge_classes(surface(:field_label_class), extra)
1367
+ end
1368
+
1369
+ def input_class(extra: nil)
1370
+ merge_classes(surface(:input_class), extra)
1371
+ end
1372
+
1373
+ def checkbox_label_class(extra: nil)
1374
+ merge_classes(surface(:checkbox_label_class), extra)
1375
+ end
1376
+
1377
+ def checkbox_class(extra: nil)
1378
+ merge_classes(surface(:checkbox_class), extra)
1379
+ end
1380
+
1381
+ def code_class(extra: nil)
1382
+ merge_classes(surface(:code_class), extra)
1383
+ end
1384
+
1385
+ def empty_state_class(extra: nil)
1386
+ merge_classes(surface(:empty_state_class), extra)
1387
+ end
1388
+
1389
+ def muted_text_class(extra: nil)
1390
+ merge_classes(surface(:muted_text_class), extra)
1391
+ end
1392
+
1393
+ def body_text_class(extra: nil)
1394
+ merge_classes(surface(:body_text_class), extra)
1395
+ end
1396
+
1397
+ def item_title_class(extra: nil)
1398
+ merge_classes(surface(:item_title_class), extra)
1399
+ end
1400
+
1401
+ def list_class(extra: nil)
1402
+ merge_classes(surface(:list_class), extra)
1403
+ end
1404
+
1405
+ def compact_list_class(extra: nil)
1406
+ merge_classes(surface(:compact_list_class), extra)
1407
+ end
1408
+
1409
+ def list_item_class(extra: nil)
1410
+ merge_classes(surface(:list_item_class), extra)
1411
+ end
1412
+
1413
+ def compact_card_class(extra: nil)
1414
+ merge_classes(surface(:compact_card_class), extra)
1415
+ end
1416
+
1417
+ def compact_item_class(extra: nil)
1418
+ merge_classes(surface(:compact_item_class), extra)
1419
+ end
1420
+
1421
+ def section_heading_class(extra: nil)
1422
+ merge_classes(surface(:section_heading_class), extra)
1423
+ end
1424
+
1425
+ private
1426
+
1427
+ def component_options(name, overrides)
1428
+ self.class.deep_merge(@definition.fetch(name, {}), overrides)
1429
+ end
1430
+
1431
+ def merge_classes(base, extra)
1432
+ [base, extra].compact.join(" ")
1433
+ end
1434
+ end
1435
+ end
1436
+ end
1437
+ end
1438
+ end