htmlgraph 0.9.3__py3-none-any.whl → 0.27.5__py3-none-any.whl

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 (331) hide show
  1. htmlgraph/.htmlgraph/.session-warning-state.json +6 -0
  2. htmlgraph/.htmlgraph/agents.json +72 -0
  3. htmlgraph/.htmlgraph/htmlgraph.db +0 -0
  4. htmlgraph/__init__.py +173 -17
  5. htmlgraph/__init__.pyi +123 -0
  6. htmlgraph/agent_detection.py +127 -0
  7. htmlgraph/agent_registry.py +45 -30
  8. htmlgraph/agents.py +160 -107
  9. htmlgraph/analytics/__init__.py +9 -2
  10. htmlgraph/analytics/cli.py +190 -51
  11. htmlgraph/analytics/cost_analyzer.py +391 -0
  12. htmlgraph/analytics/cost_monitor.py +664 -0
  13. htmlgraph/analytics/cost_reporter.py +675 -0
  14. htmlgraph/analytics/cross_session.py +617 -0
  15. htmlgraph/analytics/dependency.py +192 -100
  16. htmlgraph/analytics/pattern_learning.py +771 -0
  17. htmlgraph/analytics/session_graph.py +707 -0
  18. htmlgraph/analytics/strategic/__init__.py +80 -0
  19. htmlgraph/analytics/strategic/cost_optimizer.py +611 -0
  20. htmlgraph/analytics/strategic/pattern_detector.py +876 -0
  21. htmlgraph/analytics/strategic/preference_manager.py +709 -0
  22. htmlgraph/analytics/strategic/suggestion_engine.py +747 -0
  23. htmlgraph/analytics/work_type.py +190 -14
  24. htmlgraph/analytics_index.py +135 -51
  25. htmlgraph/api/__init__.py +3 -0
  26. htmlgraph/api/cost_alerts_websocket.py +416 -0
  27. htmlgraph/api/main.py +2498 -0
  28. htmlgraph/api/static/htmx.min.js +1 -0
  29. htmlgraph/api/static/style-redesign.css +1344 -0
  30. htmlgraph/api/static/style.css +1079 -0
  31. htmlgraph/api/templates/dashboard-redesign.html +1366 -0
  32. htmlgraph/api/templates/dashboard.html +794 -0
  33. htmlgraph/api/templates/partials/activity-feed-hierarchical.html +326 -0
  34. htmlgraph/api/templates/partials/activity-feed.html +1100 -0
  35. htmlgraph/api/templates/partials/agents-redesign.html +317 -0
  36. htmlgraph/api/templates/partials/agents.html +317 -0
  37. htmlgraph/api/templates/partials/event-traces.html +373 -0
  38. htmlgraph/api/templates/partials/features-kanban-redesign.html +509 -0
  39. htmlgraph/api/templates/partials/features.html +578 -0
  40. htmlgraph/api/templates/partials/metrics-redesign.html +346 -0
  41. htmlgraph/api/templates/partials/metrics.html +346 -0
  42. htmlgraph/api/templates/partials/orchestration-redesign.html +443 -0
  43. htmlgraph/api/templates/partials/orchestration.html +198 -0
  44. htmlgraph/api/templates/partials/spawners.html +375 -0
  45. htmlgraph/api/templates/partials/work-items.html +613 -0
  46. htmlgraph/api/websocket.py +538 -0
  47. htmlgraph/archive/__init__.py +24 -0
  48. htmlgraph/archive/bloom.py +234 -0
  49. htmlgraph/archive/fts.py +297 -0
  50. htmlgraph/archive/manager.py +583 -0
  51. htmlgraph/archive/search.py +244 -0
  52. htmlgraph/atomic_ops.py +560 -0
  53. htmlgraph/attribute_index.py +208 -0
  54. htmlgraph/bounded_paths.py +539 -0
  55. htmlgraph/builders/__init__.py +14 -0
  56. htmlgraph/builders/base.py +118 -29
  57. htmlgraph/builders/bug.py +150 -0
  58. htmlgraph/builders/chore.py +119 -0
  59. htmlgraph/builders/epic.py +150 -0
  60. htmlgraph/builders/feature.py +31 -6
  61. htmlgraph/builders/insight.py +195 -0
  62. htmlgraph/builders/metric.py +217 -0
  63. htmlgraph/builders/pattern.py +202 -0
  64. htmlgraph/builders/phase.py +162 -0
  65. htmlgraph/builders/spike.py +52 -19
  66. htmlgraph/builders/track.py +148 -72
  67. htmlgraph/cigs/__init__.py +81 -0
  68. htmlgraph/cigs/autonomy.py +385 -0
  69. htmlgraph/cigs/cost.py +475 -0
  70. htmlgraph/cigs/messages_basic.py +472 -0
  71. htmlgraph/cigs/messaging.py +365 -0
  72. htmlgraph/cigs/models.py +771 -0
  73. htmlgraph/cigs/pattern_storage.py +427 -0
  74. htmlgraph/cigs/patterns.py +503 -0
  75. htmlgraph/cigs/posttool_analyzer.py +234 -0
  76. htmlgraph/cigs/reporter.py +818 -0
  77. htmlgraph/cigs/tracker.py +317 -0
  78. htmlgraph/cli/.htmlgraph/.session-warning-state.json +6 -0
  79. htmlgraph/cli/.htmlgraph/agents.json +72 -0
  80. htmlgraph/cli/.htmlgraph/htmlgraph.db +0 -0
  81. htmlgraph/cli/__init__.py +42 -0
  82. htmlgraph/cli/__main__.py +6 -0
  83. htmlgraph/cli/analytics.py +1424 -0
  84. htmlgraph/cli/base.py +685 -0
  85. htmlgraph/cli/constants.py +206 -0
  86. htmlgraph/cli/core.py +954 -0
  87. htmlgraph/cli/main.py +147 -0
  88. htmlgraph/cli/models.py +475 -0
  89. htmlgraph/cli/templates/__init__.py +1 -0
  90. htmlgraph/cli/templates/cost_dashboard.py +399 -0
  91. htmlgraph/cli/work/__init__.py +239 -0
  92. htmlgraph/cli/work/browse.py +115 -0
  93. htmlgraph/cli/work/features.py +568 -0
  94. htmlgraph/cli/work/orchestration.py +676 -0
  95. htmlgraph/cli/work/report.py +728 -0
  96. htmlgraph/cli/work/sessions.py +466 -0
  97. htmlgraph/cli/work/snapshot.py +559 -0
  98. htmlgraph/cli/work/tracks.py +486 -0
  99. htmlgraph/cli_commands/__init__.py +1 -0
  100. htmlgraph/cli_commands/feature.py +195 -0
  101. htmlgraph/cli_framework.py +115 -0
  102. htmlgraph/collections/__init__.py +18 -0
  103. htmlgraph/collections/base.py +415 -98
  104. htmlgraph/collections/bug.py +53 -0
  105. htmlgraph/collections/chore.py +53 -0
  106. htmlgraph/collections/epic.py +53 -0
  107. htmlgraph/collections/feature.py +12 -26
  108. htmlgraph/collections/insight.py +100 -0
  109. htmlgraph/collections/metric.py +92 -0
  110. htmlgraph/collections/pattern.py +97 -0
  111. htmlgraph/collections/phase.py +53 -0
  112. htmlgraph/collections/session.py +194 -0
  113. htmlgraph/collections/spike.py +56 -16
  114. htmlgraph/collections/task_delegation.py +241 -0
  115. htmlgraph/collections/todo.py +511 -0
  116. htmlgraph/collections/traces.py +487 -0
  117. htmlgraph/config/cost_models.json +56 -0
  118. htmlgraph/config.py +190 -0
  119. htmlgraph/context_analytics.py +344 -0
  120. htmlgraph/converter.py +216 -28
  121. htmlgraph/cost_analysis/__init__.py +5 -0
  122. htmlgraph/cost_analysis/analyzer.py +438 -0
  123. htmlgraph/dashboard.html +2406 -307
  124. htmlgraph/dashboard.html.backup +6592 -0
  125. htmlgraph/dashboard.html.bak +7181 -0
  126. htmlgraph/dashboard.html.bak2 +7231 -0
  127. htmlgraph/dashboard.html.bak3 +7232 -0
  128. htmlgraph/db/__init__.py +38 -0
  129. htmlgraph/db/queries.py +790 -0
  130. htmlgraph/db/schema.py +1788 -0
  131. htmlgraph/decorators.py +317 -0
  132. htmlgraph/dependency_models.py +19 -2
  133. htmlgraph/deploy.py +142 -125
  134. htmlgraph/deployment_models.py +474 -0
  135. htmlgraph/docs/API_REFERENCE.md +841 -0
  136. htmlgraph/docs/HTTP_API.md +750 -0
  137. htmlgraph/docs/INTEGRATION_GUIDE.md +752 -0
  138. htmlgraph/docs/ORCHESTRATION_PATTERNS.md +717 -0
  139. htmlgraph/docs/README.md +532 -0
  140. htmlgraph/docs/__init__.py +77 -0
  141. htmlgraph/docs/docs_version.py +55 -0
  142. htmlgraph/docs/metadata.py +93 -0
  143. htmlgraph/docs/migrations.py +232 -0
  144. htmlgraph/docs/template_engine.py +143 -0
  145. htmlgraph/docs/templates/_sections/cli_reference.md.j2 +52 -0
  146. htmlgraph/docs/templates/_sections/core_concepts.md.j2 +29 -0
  147. htmlgraph/docs/templates/_sections/sdk_basics.md.j2 +69 -0
  148. htmlgraph/docs/templates/base_agents.md.j2 +78 -0
  149. htmlgraph/docs/templates/example_user_override.md.j2 +47 -0
  150. htmlgraph/docs/version_check.py +163 -0
  151. htmlgraph/edge_index.py +182 -27
  152. htmlgraph/error_handler.py +544 -0
  153. htmlgraph/event_log.py +100 -52
  154. htmlgraph/event_migration.py +13 -4
  155. htmlgraph/exceptions.py +49 -0
  156. htmlgraph/file_watcher.py +101 -28
  157. htmlgraph/find_api.py +75 -63
  158. htmlgraph/git_events.py +145 -63
  159. htmlgraph/graph.py +1122 -106
  160. htmlgraph/hooks/.htmlgraph/.session-warning-state.json +6 -0
  161. htmlgraph/hooks/.htmlgraph/agents.json +72 -0
  162. htmlgraph/hooks/.htmlgraph/index.sqlite +0 -0
  163. htmlgraph/hooks/__init__.py +45 -0
  164. htmlgraph/hooks/bootstrap.py +169 -0
  165. htmlgraph/hooks/cigs_pretool_enforcer.py +354 -0
  166. htmlgraph/hooks/concurrent_sessions.py +208 -0
  167. htmlgraph/hooks/context.py +350 -0
  168. htmlgraph/hooks/drift_handler.py +525 -0
  169. htmlgraph/hooks/event_tracker.py +1314 -0
  170. htmlgraph/hooks/git_commands.py +175 -0
  171. htmlgraph/hooks/hooks-config.example.json +12 -0
  172. htmlgraph/hooks/installer.py +343 -0
  173. htmlgraph/hooks/orchestrator.py +674 -0
  174. htmlgraph/hooks/orchestrator_reflector.py +223 -0
  175. htmlgraph/hooks/post-checkout.sh +28 -0
  176. htmlgraph/hooks/post-commit.sh +24 -0
  177. htmlgraph/hooks/post-merge.sh +26 -0
  178. htmlgraph/hooks/post_tool_use_failure.py +273 -0
  179. htmlgraph/hooks/post_tool_use_handler.py +257 -0
  180. htmlgraph/hooks/posttooluse.py +408 -0
  181. htmlgraph/hooks/pre-commit.sh +94 -0
  182. htmlgraph/hooks/pre-push.sh +28 -0
  183. htmlgraph/hooks/pretooluse.py +819 -0
  184. htmlgraph/hooks/prompt_analyzer.py +637 -0
  185. htmlgraph/hooks/session_handler.py +668 -0
  186. htmlgraph/hooks/session_summary.py +395 -0
  187. htmlgraph/hooks/state_manager.py +504 -0
  188. htmlgraph/hooks/subagent_detection.py +202 -0
  189. htmlgraph/hooks/subagent_stop.py +369 -0
  190. htmlgraph/hooks/task_enforcer.py +255 -0
  191. htmlgraph/hooks/task_validator.py +177 -0
  192. htmlgraph/hooks/validator.py +628 -0
  193. htmlgraph/ids.py +41 -27
  194. htmlgraph/index.d.ts +286 -0
  195. htmlgraph/learning.py +767 -0
  196. htmlgraph/mcp_server.py +69 -23
  197. htmlgraph/models.py +1586 -87
  198. htmlgraph/operations/README.md +62 -0
  199. htmlgraph/operations/__init__.py +79 -0
  200. htmlgraph/operations/analytics.py +339 -0
  201. htmlgraph/operations/bootstrap.py +289 -0
  202. htmlgraph/operations/events.py +244 -0
  203. htmlgraph/operations/fastapi_server.py +231 -0
  204. htmlgraph/operations/hooks.py +350 -0
  205. htmlgraph/operations/initialization.py +597 -0
  206. htmlgraph/operations/initialization.py.backup +228 -0
  207. htmlgraph/operations/server.py +303 -0
  208. htmlgraph/orchestration/__init__.py +58 -0
  209. htmlgraph/orchestration/claude_launcher.py +179 -0
  210. htmlgraph/orchestration/command_builder.py +72 -0
  211. htmlgraph/orchestration/headless_spawner.py +281 -0
  212. htmlgraph/orchestration/live_events.py +377 -0
  213. htmlgraph/orchestration/model_selection.py +327 -0
  214. htmlgraph/orchestration/plugin_manager.py +140 -0
  215. htmlgraph/orchestration/prompts.py +137 -0
  216. htmlgraph/orchestration/spawner_event_tracker.py +383 -0
  217. htmlgraph/orchestration/spawners/__init__.py +16 -0
  218. htmlgraph/orchestration/spawners/base.py +194 -0
  219. htmlgraph/orchestration/spawners/claude.py +173 -0
  220. htmlgraph/orchestration/spawners/codex.py +435 -0
  221. htmlgraph/orchestration/spawners/copilot.py +294 -0
  222. htmlgraph/orchestration/spawners/gemini.py +471 -0
  223. htmlgraph/orchestration/subprocess_runner.py +36 -0
  224. htmlgraph/orchestration/task_coordination.py +343 -0
  225. htmlgraph/orchestration.md +563 -0
  226. htmlgraph/orchestrator-system-prompt-optimized.txt +863 -0
  227. htmlgraph/orchestrator.py +669 -0
  228. htmlgraph/orchestrator_config.py +357 -0
  229. htmlgraph/orchestrator_mode.py +328 -0
  230. htmlgraph/orchestrator_validator.py +133 -0
  231. htmlgraph/parallel.py +646 -0
  232. htmlgraph/parser.py +160 -35
  233. htmlgraph/path_query.py +608 -0
  234. htmlgraph/pattern_matcher.py +636 -0
  235. htmlgraph/planning.py +147 -52
  236. htmlgraph/pydantic_models.py +476 -0
  237. htmlgraph/quality_gates.py +350 -0
  238. htmlgraph/query_builder.py +109 -72
  239. htmlgraph/query_composer.py +509 -0
  240. htmlgraph/reflection.py +443 -0
  241. htmlgraph/refs.py +344 -0
  242. htmlgraph/repo_hash.py +512 -0
  243. htmlgraph/repositories/__init__.py +292 -0
  244. htmlgraph/repositories/analytics_repository.py +455 -0
  245. htmlgraph/repositories/analytics_repository_standard.py +628 -0
  246. htmlgraph/repositories/feature_repository.py +581 -0
  247. htmlgraph/repositories/feature_repository_htmlfile.py +668 -0
  248. htmlgraph/repositories/feature_repository_memory.py +607 -0
  249. htmlgraph/repositories/feature_repository_sqlite.py +858 -0
  250. htmlgraph/repositories/filter_service.py +620 -0
  251. htmlgraph/repositories/filter_service_standard.py +445 -0
  252. htmlgraph/repositories/shared_cache.py +621 -0
  253. htmlgraph/repositories/shared_cache_memory.py +395 -0
  254. htmlgraph/repositories/track_repository.py +552 -0
  255. htmlgraph/repositories/track_repository_htmlfile.py +619 -0
  256. htmlgraph/repositories/track_repository_memory.py +508 -0
  257. htmlgraph/repositories/track_repository_sqlite.py +711 -0
  258. htmlgraph/routing.py +8 -19
  259. htmlgraph/scripts/deploy.py +1 -2
  260. htmlgraph/sdk/__init__.py +398 -0
  261. htmlgraph/sdk/__init__.pyi +14 -0
  262. htmlgraph/sdk/analytics/__init__.py +19 -0
  263. htmlgraph/sdk/analytics/engine.py +155 -0
  264. htmlgraph/sdk/analytics/helpers.py +178 -0
  265. htmlgraph/sdk/analytics/registry.py +109 -0
  266. htmlgraph/sdk/base.py +484 -0
  267. htmlgraph/sdk/constants.py +216 -0
  268. htmlgraph/sdk/core.pyi +308 -0
  269. htmlgraph/sdk/discovery.py +120 -0
  270. htmlgraph/sdk/help/__init__.py +12 -0
  271. htmlgraph/sdk/help/mixin.py +699 -0
  272. htmlgraph/sdk/mixins/__init__.py +15 -0
  273. htmlgraph/sdk/mixins/attribution.py +113 -0
  274. htmlgraph/sdk/mixins/mixin.py +410 -0
  275. htmlgraph/sdk/operations/__init__.py +12 -0
  276. htmlgraph/sdk/operations/mixin.py +427 -0
  277. htmlgraph/sdk/orchestration/__init__.py +17 -0
  278. htmlgraph/sdk/orchestration/coordinator.py +203 -0
  279. htmlgraph/sdk/orchestration/spawner.py +204 -0
  280. htmlgraph/sdk/planning/__init__.py +19 -0
  281. htmlgraph/sdk/planning/bottlenecks.py +93 -0
  282. htmlgraph/sdk/planning/mixin.py +211 -0
  283. htmlgraph/sdk/planning/parallel.py +186 -0
  284. htmlgraph/sdk/planning/queue.py +210 -0
  285. htmlgraph/sdk/planning/recommendations.py +87 -0
  286. htmlgraph/sdk/planning/smart_planning.py +319 -0
  287. htmlgraph/sdk/session/__init__.py +19 -0
  288. htmlgraph/sdk/session/continuity.py +57 -0
  289. htmlgraph/sdk/session/handoff.py +110 -0
  290. htmlgraph/sdk/session/info.py +309 -0
  291. htmlgraph/sdk/session/manager.py +103 -0
  292. htmlgraph/sdk/strategic/__init__.py +26 -0
  293. htmlgraph/sdk/strategic/mixin.py +563 -0
  294. htmlgraph/server.py +685 -180
  295. htmlgraph/services/__init__.py +10 -0
  296. htmlgraph/services/claiming.py +199 -0
  297. htmlgraph/session_hooks.py +300 -0
  298. htmlgraph/session_manager.py +1392 -175
  299. htmlgraph/session_registry.py +587 -0
  300. htmlgraph/session_state.py +436 -0
  301. htmlgraph/session_warning.py +201 -0
  302. htmlgraph/sessions/__init__.py +23 -0
  303. htmlgraph/sessions/handoff.py +756 -0
  304. htmlgraph/setup.py +34 -17
  305. htmlgraph/spike_index.py +143 -0
  306. htmlgraph/sync_docs.py +12 -15
  307. htmlgraph/system_prompts.py +450 -0
  308. htmlgraph/templates/AGENTS.md.template +366 -0
  309. htmlgraph/templates/CLAUDE.md.template +97 -0
  310. htmlgraph/templates/GEMINI.md.template +87 -0
  311. htmlgraph/templates/orchestration-view.html +350 -0
  312. htmlgraph/track_builder.py +146 -15
  313. htmlgraph/track_manager.py +69 -21
  314. htmlgraph/transcript.py +890 -0
  315. htmlgraph/transcript_analytics.py +699 -0
  316. htmlgraph/types.py +323 -0
  317. htmlgraph/validation.py +115 -0
  318. htmlgraph/watch.py +8 -5
  319. htmlgraph/work_type_utils.py +3 -2
  320. {htmlgraph-0.9.3.data → htmlgraph-0.27.5.data}/data/htmlgraph/dashboard.html +2406 -307
  321. htmlgraph-0.27.5.data/data/htmlgraph/templates/AGENTS.md.template +366 -0
  322. htmlgraph-0.27.5.data/data/htmlgraph/templates/CLAUDE.md.template +97 -0
  323. htmlgraph-0.27.5.data/data/htmlgraph/templates/GEMINI.md.template +87 -0
  324. {htmlgraph-0.9.3.dist-info → htmlgraph-0.27.5.dist-info}/METADATA +97 -64
  325. htmlgraph-0.27.5.dist-info/RECORD +337 -0
  326. {htmlgraph-0.9.3.dist-info → htmlgraph-0.27.5.dist-info}/entry_points.txt +1 -1
  327. htmlgraph/cli.py +0 -2688
  328. htmlgraph/sdk.py +0 -709
  329. htmlgraph-0.9.3.dist-info/RECORD +0 -61
  330. {htmlgraph-0.9.3.data → htmlgraph-0.27.5.data}/data/htmlgraph/styles.css +0 -0
  331. {htmlgraph-0.9.3.dist-info → htmlgraph-0.27.5.dist-info}/WHEEL +0 -0
@@ -0,0 +1,443 @@
1
+ <div class="view-container orchestration-view">
2
+ <div class="view-header">
3
+ <h2>Agent Orchestration Graph</h2>
4
+ <div class="view-description">
5
+ Real-time visualization of agent delegation chains and multi-hop workflows
6
+ </div>
7
+ <div class="view-filters">
8
+ <div class="filter-group">
9
+ <label>Filter by Agent:</label>
10
+ <select class="filter-select" id="agent-filter">
11
+ <option value="all">All Agents</option>
12
+ {% set agents = [] %}
13
+ {% for d in delegations %}
14
+ {% if d.from_agent not in agents %}
15
+ {% set _ = agents.append(d.from_agent) %}
16
+ {% endif %}
17
+ {% if d.to_agent not in agents %}
18
+ {% set _ = agents.append(d.to_agent) %}
19
+ {% endif %}
20
+ {% endfor %}
21
+ {% for agent in agents|sort %}
22
+ <option value="{{ agent }}">{{ agent }}</option>
23
+ {% endfor %}
24
+ </select>
25
+ </div>
26
+ </div>
27
+ </div>
28
+
29
+ <!-- DAG Visualization Container -->
30
+ <div class="orchestration-graph">
31
+ <div class="graph-container">
32
+ <svg class="graph-edges" id="graph-edges"></svg>
33
+ <div class="graph-nodes" id="graph-nodes"></div>
34
+ </div>
35
+ </div>
36
+
37
+ <!-- Stats -->
38
+ <div style="margin-top: var(--spacing-2xl); padding-top: var(--spacing-xl); border-top: 1px solid var(--border-subtle);">
39
+ <h3 style="color: var(--accent-lime); text-transform: uppercase; letter-spacing: 0.1em; margin-bottom: var(--spacing-lg);">
40
+ Orchestration Statistics
41
+ </h3>
42
+ <div class="metrics-grid">
43
+ <div class="metric-card">
44
+ <div class="metric-label">Total Delegations</div>
45
+ <div class="metric-value">{{ delegations|length }}</div>
46
+ <div class="metric-trend">Agent-to-agent handoffs</div>
47
+ </div>
48
+ <div class="metric-card">
49
+ <div class="metric-label">Unique Agents</div>
50
+ <div class="metric-value" id="unique-agents">
51
+ {% set unique_agents = {} %}
52
+ {% for d in delegations %}
53
+ {% if d.from_agent not in unique_agents %}
54
+ {% set _ = unique_agents.update({d.from_agent: True}) %}
55
+ {% endif %}
56
+ {% if d.to_agent not in unique_agents %}
57
+ {% set _ = unique_agents.update({d.to_agent: True}) %}
58
+ {% endif %}
59
+ {% endfor %}
60
+ {{ unique_agents|length }}
61
+ </div>
62
+ <div class="metric-trend">In network</div>
63
+ </div>
64
+ <div class="metric-card">
65
+ <div class="metric-label">Deepest Chain</div>
66
+ <div class="metric-value" id="deepest-chain">—</div>
67
+ <div class="metric-trend">Hops in longest path</div>
68
+ </div>
69
+ <div class="metric-card">
70
+ <div class="metric-label">Avg Chain Length</div>
71
+ <div class="metric-value" id="avg-chain-length">—</div>
72
+ <div class="metric-trend">Mean delegation depth</div>
73
+ </div>
74
+ </div>
75
+ </div>
76
+
77
+ <!-- Delegation List -->
78
+ {% if delegations %}
79
+ <div style="margin-top: var(--spacing-2xl); padding-top: var(--spacing-xl); border-top: 1px solid var(--border-subtle);">
80
+ <h3 style="color: var(--accent-lime); text-transform: uppercase; letter-spacing: 0.1em; margin-bottom: var(--spacing-lg);">
81
+ Recent Delegations
82
+ </h3>
83
+ <div style="background: var(--bg-card); border: 1px solid var(--border-subtle); border-radius: 2px; overflow: hidden;">
84
+ <table style="width: 100%; border-collapse: collapse;">
85
+ <thead style="background: var(--bg-darker); border-bottom: 1px solid var(--border-subtle);">
86
+ <tr>
87
+ <th style="padding: var(--spacing-lg); text-align: left; color: var(--accent-lime); font-size: 0.85rem; text-transform: uppercase; letter-spacing: 0.05em;">From</th>
88
+ <th style="padding: var(--spacing-lg); text-align: left; color: var(--accent-lime); font-size: 0.85rem; text-transform: uppercase; letter-spacing: 0.05em;">To</th>
89
+ <th style="padding: var(--spacing-lg); text-align: left; color: var(--accent-lime); font-size: 0.85rem; text-transform: uppercase; letter-spacing: 0.05em;">Task</th>
90
+ <th style="padding: var(--spacing-lg); text-align: left; color: var(--accent-lime); font-size: 0.85rem; text-transform: uppercase; letter-spacing: 0.05em;">Timestamp</th>
91
+ </tr>
92
+ </thead>
93
+ <tbody>
94
+ {% for delegation in delegations[:20] %}
95
+ <tr style="border-bottom: 1px solid var(--border-subtle);">
96
+ <td style="padding: var(--spacing-lg);">
97
+ <span style="background: rgba(139, 92, 246, 0.1); border: 1px solid rgba(139, 92, 246, 0.3); padding: var(--spacing-xs) var(--spacing-sm); border-radius: 2px; color: var(--agent-claude); font-size: 0.85rem; font-weight: 600;">
98
+ {{ delegation.from_agent }}
99
+ </span>
100
+ </td>
101
+ <td style="padding: var(--spacing-lg);">
102
+ <span style="background: rgba(59, 130, 246, 0.1); border: 1px solid rgba(59, 130, 246, 0.3); padding: var(--spacing-xs) var(--spacing-sm); border-radius: 2px; color: var(--agent-gemini); font-size: 0.85rem; font-weight: 600;">
103
+ {{ delegation.to_agent }}
104
+ </span>
105
+ </td>
106
+ <td style="padding: var(--spacing-lg); color: var(--text-secondary); max-width: 300px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">
107
+ {% if delegation.task %}
108
+ {{ delegation.task[:60] }}{% if delegation.task|length > 60 %}...{% endif %}
109
+ {% else %}
110
+ <span style="color: var(--text-muted);">—</span>
111
+ {% endif %}
112
+ </td>
113
+ <td style="padding: var(--spacing-lg); color: var(--text-secondary); font-family: 'Courier New', monospace; font-size: 0.9rem;">
114
+ {{ delegation.timestamp }}
115
+ </td>
116
+ </tr>
117
+ {% endfor %}
118
+ </tbody>
119
+ </table>
120
+ </div>
121
+ </div>
122
+ {% else %}
123
+ <div style="margin-top: var(--spacing-2xl); padding: var(--spacing-2xl); background: var(--bg-card); border: 1px solid var(--border-subtle); border-radius: 2px; text-align: center; color: var(--text-muted);">
124
+ <p style="font-size: 1.1rem; margin-bottom: var(--spacing-md);">No delegation chains found</p>
125
+ <small>Agent-to-agent delegations will appear here as tasks are assigned</small>
126
+ </div>
127
+ {% endif %}
128
+ </div>
129
+
130
+ <script>
131
+ // Graph rendering for orchestration DAG
132
+ class OrchestrationGraph {
133
+ constructor(delegations) {
134
+ this.delegations = delegations;
135
+ this.nodes = [];
136
+ this.edges = [];
137
+ this.nodeMap = new Map();
138
+ this.init();
139
+ }
140
+
141
+ init() {
142
+ // Build node map from delegations
143
+ this.delegations.forEach(d => {
144
+ if (!this.nodeMap.has(d.from_agent)) {
145
+ this.nodeMap.set(d.from_agent, {
146
+ id: d.from_agent,
147
+ label: d.from_agent,
148
+ x: 0,
149
+ y: 0,
150
+ inDegree: 0,
151
+ outDegree: 0,
152
+ totalCost: 0
153
+ });
154
+ }
155
+ if (!this.nodeMap.has(d.to_agent)) {
156
+ this.nodeMap.set(d.to_agent, {
157
+ id: d.to_agent,
158
+ label: d.to_agent,
159
+ x: 0,
160
+ y: 0,
161
+ inDegree: 0,
162
+ outDegree: 0,
163
+ totalCost: 0
164
+ });
165
+ }
166
+
167
+ const fromNode = this.nodeMap.get(d.from_agent);
168
+ const toNode = this.nodeMap.get(d.to_agent);
169
+
170
+ fromNode.outDegree++;
171
+ toNode.inDegree++;
172
+ fromNode.totalCost += d.cost || 0;
173
+
174
+ this.edges.push({
175
+ from: d.from_agent,
176
+ to: d.to_agent,
177
+ task: d.task,
178
+ cost: d.cost || 0
179
+ });
180
+ });
181
+
182
+ this.nodes = Array.from(this.nodeMap.values());
183
+
184
+ // Layout nodes using simple force-directed approach
185
+ this.layoutNodes();
186
+ this.render();
187
+ }
188
+
189
+ layoutNodes() {
190
+ const container = document.getElementById('graph-nodes');
191
+ const width = container.parentElement.offsetWidth;
192
+ const height = container.parentElement.offsetHeight;
193
+
194
+ // Arrange nodes horizontally by level (topological sort)
195
+ const levels = this.getNodeLevels();
196
+ const levelCounts = new Map();
197
+
198
+ this.nodes.forEach(node => {
199
+ const level = levels.get(node.id) || 0;
200
+ const count = levelCounts.get(level) || 0;
201
+ levelCounts.set(level, count + 1);
202
+
203
+ const maxLevel = Math.max(...levels.values());
204
+ const levelWidth = width / (maxLevel + 2);
205
+ const levelHeight = height / (Math.max(...levelCounts.values()) + 1);
206
+
207
+ node.x = (level + 1) * levelWidth;
208
+ node.y = (count + 1) * levelHeight;
209
+ });
210
+ }
211
+
212
+ getNodeLevels() {
213
+ const levels = new Map();
214
+ const visited = new Set();
215
+
216
+ const visit = (nodeId, level) => {
217
+ if (visited.has(nodeId)) return;
218
+ visited.add(nodeId);
219
+
220
+ levels.set(nodeId, Math.max(levels.get(nodeId) || 0, level));
221
+
222
+ // Visit outgoing nodes
223
+ this.edges
224
+ .filter(e => e.from === nodeId)
225
+ .forEach(e => visit(e.to, level + 1));
226
+ };
227
+
228
+ this.nodes.forEach(node => visit(node.id, 0));
229
+ return levels;
230
+ }
231
+
232
+ render() {
233
+ const container = document.getElementById('graph-nodes');
234
+ container.innerHTML = '';
235
+
236
+ // Render edges first (so they appear behind nodes)
237
+ this.renderEdges();
238
+
239
+ // Render nodes
240
+ this.nodes.forEach(node => {
241
+ const nodeEl = document.createElement('div');
242
+ nodeEl.className = 'graph-node';
243
+ nodeEl.style.left = (node.x - 60) + 'px';
244
+ nodeEl.style.top = (node.y - 40) + 'px';
245
+
246
+ // Determine color based on model
247
+ const modelClass = node.id.toLowerCase().includes('gemini') ? 'gemini'
248
+ : node.id.toLowerCase().includes('copilot') ? 'copilot'
249
+ : 'claude';
250
+ nodeEl.classList.add(modelClass);
251
+
252
+ nodeEl.innerHTML = `
253
+ <div class="node-label">${node.label}</div>
254
+ <div class="node-cost">OUT: ${node.outDegree} | IN: ${node.inDegree}</div>
255
+ `;
256
+
257
+ container.appendChild(nodeEl);
258
+
259
+ // Add hover effect
260
+ nodeEl.addEventListener('mouseenter', () => {
261
+ this.highlightConnections(node.id);
262
+ });
263
+ nodeEl.addEventListener('mouseleave', () => {
264
+ this.clearHighlights();
265
+ });
266
+ });
267
+ }
268
+
269
+ renderEdges() {
270
+ const svg = document.getElementById('graph-edges');
271
+ svg.innerHTML = '';
272
+
273
+ this.edges.forEach(edge => {
274
+ const fromNode = this.nodeMap.get(edge.from);
275
+ const toNode = this.nodeMap.get(edge.to);
276
+
277
+ const x1 = fromNode.x + 60;
278
+ const y1 = fromNode.y;
279
+ const x2 = toNode.x - 60;
280
+ const y2 = toNode.y;
281
+
282
+ // Create curved path
283
+ const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
284
+ const curve = `M ${x1} ${y1} Q ${(x1 + x2) / 2} ${(y1 + y2) / 2 - 50} ${x2} ${y2}`;
285
+
286
+ path.setAttribute('d', curve);
287
+ path.setAttribute('stroke', 'rgba(205, 255, 0, 0.3)');
288
+ path.setAttribute('stroke-width', '2');
289
+ path.setAttribute('fill', 'none');
290
+ path.setAttribute('marker-end', 'url(#arrowhead)');
291
+
292
+ svg.appendChild(path);
293
+ });
294
+
295
+ // Add arrow marker
296
+ const defs = document.createElementNS('http://www.w3.org/2000/svg', 'defs');
297
+ const marker = document.createElementNS('http://www.w3.org/2000/svg', 'marker');
298
+ marker.setAttribute('id', 'arrowhead');
299
+ marker.setAttribute('markerWidth', '10');
300
+ marker.setAttribute('markerHeight', '10');
301
+ marker.setAttribute('refX', '9');
302
+ marker.setAttribute('refY', '3');
303
+ marker.setAttribute('orient', 'auto');
304
+
305
+ const polygon = document.createElementNS('http://www.w3.org/2000/svg', 'polygon');
306
+ polygon.setAttribute('points', '0 0, 10 3, 0 6');
307
+ polygon.setAttribute('fill', 'rgba(205, 255, 0, 0.5)');
308
+
309
+ marker.appendChild(polygon);
310
+ defs.appendChild(marker);
311
+ svg.appendChild(defs);
312
+ }
313
+
314
+ highlightConnections(nodeId) {
315
+ const svg = document.getElementById('graph-edges');
316
+ const paths = svg.querySelectorAll('path');
317
+
318
+ // Dim all paths first
319
+ paths.forEach(p => {
320
+ p.style.opacity = '0.2';
321
+ p.style.stroke = 'rgba(205, 255, 0, 0.2)';
322
+ });
323
+
324
+ // Highlight connected paths
325
+ this.edges.forEach((edge, idx) => {
326
+ if (edge.from === nodeId || edge.to === nodeId) {
327
+ paths[idx].style.opacity = '1';
328
+ paths[idx].style.stroke = 'rgba(205, 255, 0, 0.8)';
329
+ paths[idx].style.strokeWidth = '3';
330
+ }
331
+ });
332
+ }
333
+
334
+ clearHighlights() {
335
+ const svg = document.getElementById('graph-edges');
336
+ const paths = svg.querySelectorAll('path');
337
+ paths.forEach(p => {
338
+ p.style.opacity = '1';
339
+ p.style.stroke = 'rgba(205, 255, 0, 0.3)';
340
+ p.style.strokeWidth = '2';
341
+ });
342
+ }
343
+ }
344
+
345
+ // Initialize graph if delegations exist
346
+ const delegationsData = {{ delegations|tojson }};
347
+ if (delegationsData && delegationsData.length > 0) {
348
+ document.addEventListener('DOMContentLoaded', () => {
349
+ window.graph = new OrchestrationGraph(delegationsData);
350
+ });
351
+ }
352
+ </script>
353
+
354
+ <style>
355
+ .orchestration-graph {
356
+ background: var(--bg-card);
357
+ border: 1px solid var(--border-subtle);
358
+ border-radius: 2px;
359
+ padding: var(--spacing-xl);
360
+ height: 600px;
361
+ position: relative;
362
+ overflow: hidden;
363
+ margin-bottom: var(--spacing-2xl);
364
+ }
365
+
366
+ .graph-container {
367
+ width: 100%;
368
+ height: 100%;
369
+ position: relative;
370
+ }
371
+
372
+ .graph-edges {
373
+ position: absolute;
374
+ top: 0;
375
+ left: 0;
376
+ width: 100%;
377
+ height: 100%;
378
+ pointer-events: none;
379
+ z-index: 0;
380
+ }
381
+
382
+ .graph-nodes {
383
+ position: relative;
384
+ z-index: 1;
385
+ width: 100%;
386
+ height: 100%;
387
+ }
388
+
389
+ .graph-node {
390
+ position: absolute;
391
+ background: var(--bg-darker);
392
+ border: 2px solid var(--border-medium);
393
+ border-radius: 4px;
394
+ padding: var(--spacing-md);
395
+ min-width: 140px;
396
+ text-align: center;
397
+ cursor: pointer;
398
+ transition: all var(--transition-base);
399
+ user-select: none;
400
+ transform: translate(-50%, -50%);
401
+ }
402
+
403
+ .graph-node:hover {
404
+ border-color: var(--accent-lime);
405
+ box-shadow: var(--shadow-lg);
406
+ transform: translate(-50%, -50%) scale(1.1);
407
+ }
408
+
409
+ .graph-node.claude {
410
+ background: rgba(139, 92, 246, 0.1);
411
+ border-color: var(--agent-claude);
412
+ }
413
+
414
+ .graph-node.gemini {
415
+ background: rgba(59, 130, 246, 0.1);
416
+ border-color: var(--agent-gemini);
417
+ }
418
+
419
+ .graph-node.copilot {
420
+ background: rgba(107, 114, 128, 0.1);
421
+ border-color: var(--agent-copilot);
422
+ }
423
+
424
+ .node-label {
425
+ font-weight: 700;
426
+ color: var(--accent-lime);
427
+ font-size: 0.95rem;
428
+ margin-bottom: var(--spacing-sm);
429
+ text-transform: uppercase;
430
+ letter-spacing: 0.05em;
431
+ }
432
+
433
+ .node-cost {
434
+ font-size: 0.75rem;
435
+ color: var(--text-secondary);
436
+ }
437
+
438
+ @media (max-width: 768px) {
439
+ .orchestration-graph {
440
+ height: 400px;
441
+ }
442
+ }
443
+ </style>
@@ -0,0 +1,198 @@
1
+ <div class="view-container orchestration-view">
2
+ <div class="view-header">
3
+ <h2>Agent Orchestration Graph</h2>
4
+ <div class="view-description">
5
+ Real-time visualization of agent delegation chains and multi-hop workflows
6
+ </div>
7
+ <div class="view-filters">
8
+ <div class="filter-group">
9
+ <label>Filter by Agent:</label>
10
+ <select class="filter-select" id="agent-filter">
11
+ <option value="all">All Agents</option>
12
+ {% set agents = [] %}
13
+ {% for d in delegations %}
14
+ {% if d.from_agent not in agents %}
15
+ {% set _ = agents.append(d.from_agent) %}
16
+ {% endif %}
17
+ {% if d.to_agent not in agents %}
18
+ {% set _ = agents.append(d.to_agent) %}
19
+ {% endif %}
20
+ {% endfor %}
21
+ {% for agent in agents|sort %}
22
+ <option value="{{ agent }}">{{ agent }}</option>
23
+ {% endfor %}
24
+ </select>
25
+ </div>
26
+ </div>
27
+ </div>
28
+
29
+ <!-- Stats -->
30
+ <div style="margin-top: 1rem; padding-top: 0.75rem; border-top: 1px solid var(--border-subtle);">
31
+ <h3 style="color: var(--accent-lime); text-transform: uppercase; letter-spacing: 0.1em; margin-bottom: 0.5rem; font-size: 0.85rem;">
32
+ Orchestration Statistics
33
+ </h3>
34
+ <div class="metrics-grid">
35
+ <div class="metric-card">
36
+ <div class="metric-label">Total Delegations</div>
37
+ <div class="metric-value">{{ delegations|length }}</div>
38
+ <div class="metric-trend">Agent-to-agent handoffs</div>
39
+ </div>
40
+ <div class="metric-card">
41
+ <div class="metric-label">Unique Agents</div>
42
+ <div class="metric-value" id="unique-agents">
43
+ {% set unique_agents = {} %}
44
+ {% for d in delegations %}
45
+ {% if d.from_agent not in unique_agents %}
46
+ {% set _ = unique_agents.update({d.from_agent: True}) %}
47
+ {% endif %}
48
+ {% if d.to_agent not in unique_agents %}
49
+ {% set _ = unique_agents.update({d.to_agent: True}) %}
50
+ {% endif %}
51
+ {% endfor %}
52
+ {{ unique_agents|length }}
53
+ </div>
54
+ <div class="metric-trend">In network</div>
55
+ </div>
56
+ <div class="metric-card">
57
+ <div class="metric-label">Deepest Chain</div>
58
+ <div class="metric-value" id="deepest-chain">—</div>
59
+ <div class="metric-trend">Hops in longest path</div>
60
+ </div>
61
+ <div class="metric-card">
62
+ <div class="metric-label">Avg Chain Length</div>
63
+ <div class="metric-value" id="avg-chain-length">—</div>
64
+ <div class="metric-trend">Mean delegation depth</div>
65
+ </div>
66
+ </div>
67
+ </div>
68
+
69
+ <!-- Delegation List -->
70
+ {% if delegations %}
71
+ <div style="margin-top: 1rem; padding-top: 0.75rem; border-top: 1px solid var(--border-subtle);">
72
+ <h3 style="color: var(--accent-lime); text-transform: uppercase; letter-spacing: 0.1em; margin-bottom: 0.5rem; font-size: 0.85rem;">
73
+ Recent Delegations
74
+ </h3>
75
+ <div style="background: var(--bg-card); border: 1px solid var(--border-subtle); border-radius: 2px; overflow: hidden;">
76
+ <table style="width: 100%; border-collapse: collapse;">
77
+ <thead style="background: var(--bg-darker); border-bottom: 1px solid var(--border-subtle);">
78
+ <tr>
79
+ <th style="padding: 0.5rem 0.75rem; text-align: left; color: var(--accent-lime); font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.05em;">From</th>
80
+ <th style="padding: 0.5rem 0.75rem; text-align: left; color: var(--accent-lime); font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.05em;">To</th>
81
+ <th style="padding: 0.5rem 0.75rem; text-align: left; color: var(--accent-lime); font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.05em;">Task</th>
82
+ <th style="padding: 0.5rem 0.75rem; text-align: left; color: var(--accent-lime); font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.05em;">Status</th>
83
+ <th style="padding: 0.5rem 0.75rem; text-align: left; color: var(--accent-lime); font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.05em;">Session</th>
84
+ <th style="padding: 0.5rem 0.75rem; text-align: left; color: var(--accent-lime); font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.05em;">Timestamp</th>
85
+ </tr>
86
+ </thead>
87
+ <tbody>
88
+ {% for delegation in delegations[:20] %}
89
+ <tr style="border-bottom: 1px solid var(--border-subtle);">
90
+ <td style="padding: 0.4rem 0.75rem;">
91
+ <span style="background: rgba(139, 92, 246, 0.1); border: 1px solid rgba(139, 92, 246, 0.3); padding: 0.15rem 0.4rem; border-radius: 2px; color: var(--agent-claude); font-size: 0.75rem; font-weight: 600;">
92
+ {{ delegation.from_agent }}
93
+ </span>
94
+ </td>
95
+ <td style="padding: 0.4rem 0.75rem;">
96
+ <span style="background: rgba(59, 130, 246, 0.1); border: 1px solid rgba(59, 130, 246, 0.3); padding: 0.15rem 0.4rem; border-radius: 2px; color: var(--agent-gemini); font-size: 0.75rem; font-weight: 600;">
97
+ {{ delegation.to_agent }}
98
+ </span>
99
+ </td>
100
+ <td style="padding: 0.4rem 0.75rem; color: var(--text-secondary); max-width: 300px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 0.8rem;">
101
+ {% if delegation.task %}
102
+ {{ delegation.task[:60] }}{% if delegation.task|length > 60 %}...{% endif %}
103
+ {% else %}
104
+ <span style="color: var(--text-muted);">—</span>
105
+ {% endif %}
106
+ </td>
107
+ <td style="padding: 0.4rem 0.75rem;">
108
+ <span class="status-badge status-{{ delegation.status|lower }}">
109
+ {{ delegation.status }}
110
+ </span>
111
+ </td>
112
+ <td style="padding: 0.4rem 0.75rem; color: var(--text-muted); font-size: 0.7rem; font-family: monospace;">
113
+ {{ delegation.session_id[:8] }}
114
+ </td>
115
+ <td style="padding: 0.4rem 0.75rem; color: var(--text-secondary); font-family: 'Courier New', monospace; font-size: 0.75rem;">
116
+ {{ delegation.timestamp }}
117
+ </td>
118
+ </tr>
119
+ {% endfor %}
120
+ </tbody>
121
+ </table>
122
+ </div>
123
+ </div>
124
+ {% else %}
125
+ <div style="margin-top: 1rem; padding: 1rem; background: var(--bg-card); border: 1px solid var(--border-subtle); border-radius: 2px; text-align: center; color: var(--text-muted);">
126
+ <p style="font-size: 0.9rem; margin-bottom: 0.5rem;">No delegation chains found</p>
127
+ <small style="font-size: 0.75rem;">Agent-to-agent delegations will appear here as tasks are assigned</small>
128
+ </div>
129
+ {% endif %}
130
+ </div>
131
+
132
+ <style>
133
+ /* Compact view header */
134
+ .orchestration-view .view-header {
135
+ margin-bottom: 0.75rem;
136
+ }
137
+
138
+ .orchestration-view .view-description {
139
+ margin-bottom: 0.5rem;
140
+ }
141
+
142
+ /* Compact stats section */
143
+ .orchestration-view .metrics-grid {
144
+ display: grid;
145
+ grid-template-columns: repeat(4, 1fr);
146
+ gap: 0.75rem;
147
+ margin-bottom: 1rem;
148
+ }
149
+
150
+ .orchestration-view .metric-card {
151
+ padding: 0.75rem 1rem;
152
+ min-height: auto;
153
+ }
154
+
155
+ .orchestration-view .metric-value {
156
+ font-size: 1.5rem;
157
+ margin: 0.25rem 0;
158
+ }
159
+
160
+ .orchestration-view .metric-label {
161
+ font-size: 0.7rem;
162
+ }
163
+
164
+ .orchestration-view .metric-trend {
165
+ font-size: 0.65rem;
166
+ }
167
+
168
+ /* Status Badges */
169
+ .status-badge {
170
+ padding: 0.15rem 0.4rem;
171
+ border-radius: 2px;
172
+ font-size: 0.65rem;
173
+ font-weight: 600;
174
+ text-transform: uppercase;
175
+ letter-spacing: 0.05em;
176
+ }
177
+ .status-completed, .status-success, .status-recorded {
178
+ background: rgba(34, 197, 94, 0.15);
179
+ color: #4ade80;
180
+ border: 1px solid rgba(34, 197, 94, 0.3);
181
+ }
182
+ .status-pending, .status-active {
183
+ background: rgba(251, 191, 36, 0.15);
184
+ color: #fbbf24;
185
+ border: 1px solid rgba(251, 191, 36, 0.3);
186
+ }
187
+ .status-failed, .status-error {
188
+ background: rgba(239, 68, 68, 0.15);
189
+ color: #f87171;
190
+ border: 1px solid rgba(239, 68, 68, 0.3);
191
+ }
192
+
193
+ @media (max-width: 768px) {
194
+ .orchestration-view .metrics-grid {
195
+ grid-template-columns: repeat(2, 1fr);
196
+ }
197
+ }
198
+ </style>