@shortlink-org/portolan 0.1.0 → 0.2.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 (381) hide show
  1. package/README.md +69 -205
  2. package/cli/init.mjs +285 -0
  3. package/cli/init.test.mjs +158 -0
  4. package/cli/portolan.mjs +37 -73
  5. package/index.html +19 -0
  6. package/package.json +5 -10
  7. package/plugins/README.md +66 -27
  8. package/plugins/portolan-go.wasm +0 -0
  9. package/portolan.json +52 -73
  10. package/public/landing/cats/bug-hunter.webp +0 -0
  11. package/public/landing/cats/compass-nap.webp +0 -0
  12. package/public/landing/cats/diagrammer.webp +0 -0
  13. package/public/landing/cats/docs-reader.webp +0 -0
  14. package/public/landing/cats/laptop.webp +0 -0
  15. package/public/landing/cats/map-inspector.webp +0 -0
  16. package/public/landing/cats/server-break.webp +0 -0
  17. package/public/landing/cats/star-mapper.webp +0 -0
  18. package/public/landing/cats/system-builder.webp +0 -0
  19. package/public/landing/cats/thread-tangle.webp +0 -0
  20. package/public/og.png +0 -0
  21. package/public/readme-header.webp +0 -0
  22. package/schema/portolan.schema.json +180 -0
  23. package/scripts/builtin-plugins.mjs +14 -0
  24. package/scripts/delivery-presets.mjs +353 -0
  25. package/scripts/gen.mjs +37 -4
  26. package/scripts/history.mjs +126 -0
  27. package/scripts/history.test.mjs +110 -0
  28. package/scripts/host-plugins/fetch-bsr.mjs +351 -0
  29. package/{plugins/fetch-bsr/options.schema.json → scripts/host-plugins/fetch-bsr.options.json} +4 -0
  30. package/scripts/host-plugins/fetch-bsr.test.mjs +212 -0
  31. package/scripts/host-plugins/fetch-csr.mjs +386 -0
  32. package/scripts/host-plugins/fetch-csr.test.mjs +178 -0
  33. package/scripts/host-plugins/fetch-git.mjs +379 -0
  34. package/scripts/host-plugins/fetch-git.test.mjs +210 -0
  35. package/scripts/local-api.mjs +27 -10
  36. package/scripts/local-api.test.mjs +85 -2
  37. package/scripts/manifest.mjs +3 -2
  38. package/scripts/package-smoke.mjs +24 -3
  39. package/scripts/plugin-host.mjs +77 -18
  40. package/scripts/plugin-host.test.mjs +50 -1
  41. package/scripts/plugin-wasm-worker.mjs +6 -3
  42. package/scripts/run-builtin.mjs +6 -26
  43. package/scripts/schema.mjs +6 -1
  44. package/src/app/App.tsx +24 -429
  45. package/src/app/Breadcrumbs.test.ts +7 -0
  46. package/src/app/Breadcrumbs.tsx +15 -2
  47. package/src/app/CatalogApp.tsx +436 -0
  48. package/src/app/Sidebar.tsx +0 -1
  49. package/src/chat/ChatPanel.tsx +98 -41
  50. package/src/chat/Composer.tsx +4 -2
  51. package/src/chat/Conversation.tsx +79 -15
  52. package/src/chat/Header.tsx +43 -9
  53. package/src/chat/Starter.tsx +64 -3
  54. package/src/chat/Steps.tsx +1 -1
  55. package/src/chat/flags.test.ts +51 -14
  56. package/src/chat/flags.ts +8 -2
  57. package/src/chat/page-context.test.ts +51 -0
  58. package/src/chat/page-context.ts +128 -0
  59. package/src/chat/prompt.test.ts +49 -1
  60. package/src/chat/prompt.ts +42 -1
  61. package/src/chat/tools.ts +7 -2
  62. package/src/chat/transport.ts +16 -4
  63. package/src/components/ProblemRow.tsx +182 -0
  64. package/src/data.ts +20 -5
  65. package/src/er/ErCanvas.tsx +1 -1
  66. package/src/graph/DependencyGraph.tsx +10 -1
  67. package/src/graph/FocusedEventGraph.tsx +1 -1
  68. package/src/graph/GraphToolbar.tsx +2 -4
  69. package/src/index.css +220 -2
  70. package/src/landing/DraggableReveal.tsx +168 -0
  71. package/src/landing/EstateGraph.tsx +28 -0
  72. package/src/landing/FlowPlayback.tsx +282 -0
  73. package/src/landing/HeroMap.tsx +128 -0
  74. package/src/landing/LandingPage.tsx +757 -0
  75. package/src/landing/ProductFrame.tsx +68 -0
  76. package/src/landing/ProductTour.tsx +371 -0
  77. package/src/landing/catalog.ts +25 -0
  78. package/src/landing/motion.tsx +52 -0
  79. package/src/lib/all-problems.ts +28 -0
  80. package/src/lib/local-api.ts +35 -1
  81. package/src/lib/setup-info.test.ts +8 -0
  82. package/src/lib/setup-info.ts +3 -2
  83. package/src/lib/source-code.ts +2 -1
  84. package/src/likec4/C4View.tsx +5 -0
  85. package/src/likec4/InteractiveView.tsx +5 -1
  86. package/src/map/ContextMapGraph.tsx +47 -20
  87. package/src/pages/ContextMap.tsx +5 -1
  88. package/src/pages/FlowDetail.tsx +24 -7
  89. package/src/pages/Overview.tsx +68 -12
  90. package/src/pages/Problems.tsx +6 -193
  91. package/src/pages/Settings.tsx +133 -108
  92. package/src/pages/settings/DeliverySettings.tsx +229 -0
  93. package/src/pages/settings/PreferencesSettings.tsx +101 -0
  94. package/src/routes.test.ts +20 -0
  95. package/src/routes.ts +13 -1
  96. package/vite.config.ts +17 -9
  97. package/catalog/enum_test.go +0 -46
  98. package/catalog/model.go +0 -932
  99. package/catalog/roundtrip_test.go +0 -185
  100. package/catalog/via_test.go +0 -38
  101. package/go.mod +0 -14
  102. package/go.sum +0 -14
  103. package/internal/commands/cargo.go +0 -154
  104. package/internal/commands/commands.go +0 -143
  105. package/internal/commands/commands_test.go +0 -323
  106. package/internal/commands/gradle.go +0 -77
  107. package/internal/commands/justfile.go +0 -82
  108. package/internal/commands/makefile.go +0 -106
  109. package/internal/commands/maven.go +0 -93
  110. package/internal/commands/packagejson.go +0 -159
  111. package/internal/commands/pyproject.go +0 -169
  112. package/internal/commands/taskfile.go +0 -101
  113. package/internal/commands/testdata/estate/.cargo/config.toml +0 -7
  114. package/internal/commands/testdata/estate/Makefile +0 -33
  115. package/internal/commands/testdata/estate/Taskfile.yml +0 -21
  116. package/internal/commands/testdata/estate/build.gradle.kts +0 -21
  117. package/internal/commands/testdata/estate/justfile +0 -20
  118. package/internal/commands/testdata/estate/package.json +0 -13
  119. package/internal/commands/testdata/estate/pom.xml +0 -29
  120. package/internal/commands/testdata/estate/pyproject.toml +0 -27
  121. package/internal/commands/testdata/estate/xtask/src/main.rs +0 -27
  122. package/internal/commands/testdata/golden/commands.json +0 -305
  123. package/internal/gohttp/analyze.go +0 -2500
  124. package/internal/gohttp/endpoints.go +0 -1067
  125. package/internal/gohttp/roots.go +0 -320
  126. package/internal/gohttp/typed.go +0 -143
  127. package/internal/goscan/constants.go +0 -85
  128. package/internal/goscan/goscan_test.go +0 -227
  129. package/internal/goscan/names.go +0 -52
  130. package/internal/goscan/parse_test.go +0 -11
  131. package/internal/goscan/source.go +0 -36
  132. package/internal/goscan/tree.go +0 -155
  133. package/internal/goscan/types.go +0 -99
  134. package/internal/wsdl/ids.go +0 -127
  135. package/internal/wsdl/ids_test.go +0 -21
  136. package/internal/wsdl/model.go +0 -62
  137. package/internal/wsdl/parse.go +0 -920
  138. package/internal/wsdl/parse_test.go +0 -133
  139. package/plugin/describe.go +0 -107
  140. package/plugin/describe_test.go +0 -114
  141. package/plugin/protocol.go +0 -117
  142. package/plugin/schematest/schematest.go +0 -126
  143. package/plugins/extract-adr/describe.go +0 -19
  144. package/plugins/extract-adr/describe_test.go +0 -11
  145. package/plugins/extract-adr/extract.go +0 -153
  146. package/plugins/extract-adr/extract_test.go +0 -350
  147. package/plugins/extract-adr/history.go +0 -99
  148. package/plugins/extract-adr/main.go +0 -65
  149. package/plugins/extract-adr/parse.go +0 -642
  150. package/plugins/extract-adr/parse_test.go +0 -405
  151. package/plugins/extract-asyncapi/describe.go +0 -19
  152. package/plugins/extract-asyncapi/describe_test.go +0 -11
  153. package/plugins/extract-asyncapi/extract.go +0 -315
  154. package/plugins/extract-asyncapi/extract_test.go +0 -197
  155. package/plugins/extract-asyncapi/main.go +0 -48
  156. package/plugins/extract-asyncapi/spec.go +0 -151
  157. package/plugins/extract-commands/describe.go +0 -19
  158. package/plugins/extract-commands/describe_test.go +0 -11
  159. package/plugins/extract-commands/extract.go +0 -79
  160. package/plugins/extract-commands/extract_test.go +0 -92
  161. package/plugins/extract-commands/main.go +0 -55
  162. package/plugins/extract-csr/avro.go +0 -258
  163. package/plugins/extract-csr/describe.go +0 -19
  164. package/plugins/extract-csr/describe_test.go +0 -11
  165. package/plugins/extract-csr/extract.go +0 -338
  166. package/plugins/extract-csr/extract_test.go +0 -374
  167. package/plugins/extract-csr/jsonschema.go +0 -343
  168. package/plugins/extract-csr/lock.go +0 -25
  169. package/plugins/extract-csr/main.go +0 -84
  170. package/plugins/extract-csr/subject.go +0 -102
  171. package/plugins/extract-flows/describe.go +0 -19
  172. package/plugins/extract-flows/describe_test.go +0 -11
  173. package/plugins/extract-flows/extract.go +0 -91
  174. package/plugins/extract-flows/main.go +0 -45
  175. package/plugins/extract-flows/parse.go +0 -593
  176. package/plugins/extract-flows/parse_test.go +0 -204
  177. package/plugins/extract-glossary/describe.go +0 -19
  178. package/plugins/extract-glossary/describe_test.go +0 -11
  179. package/plugins/extract-glossary/extract.go +0 -115
  180. package/plugins/extract-glossary/extract_test.go +0 -220
  181. package/plugins/extract-glossary/main.go +0 -59
  182. package/plugins/extract-glossary/parse.go +0 -214
  183. package/plugins/extract-glossary/parse_test.go +0 -203
  184. package/plugins/extract-go/aggregate.go +0 -214
  185. package/plugins/extract-go/client.go +0 -409
  186. package/plugins/extract-go/client_test.go +0 -266
  187. package/plugins/extract-go/describe.go +0 -19
  188. package/plugins/extract-go/describe_test.go +0 -11
  189. package/plugins/extract-go/enum.go +0 -195
  190. package/plugins/extract-go/enum_test.go +0 -82
  191. package/plugins/extract-go/event.go +0 -99
  192. package/plugins/extract-go/extract.go +0 -191
  193. package/plugins/extract-go/extract_test.go +0 -261
  194. package/plugins/extract-go/flow.go +0 -1441
  195. package/plugins/extract-go/flow_test.go +0 -609
  196. package/plugins/extract-go/httpclient.go +0 -174
  197. package/plugins/extract-go/httpclient_test.go +0 -296
  198. package/plugins/extract-go/ids.go +0 -92
  199. package/plugins/extract-go/layout.go +0 -225
  200. package/plugins/extract-go/layout_test.go +0 -125
  201. package/plugins/extract-go/lifecycle.go +0 -324
  202. package/plugins/extract-go/lifecycle_test.go +0 -108
  203. package/plugins/extract-go/main.go +0 -79
  204. package/plugins/extract-go/operation.go +0 -157
  205. package/plugins/extract-go/source.go +0 -316
  206. package/plugins/extract-go/transport.go +0 -321
  207. package/plugins/extract-go/transport_test.go +0 -145
  208. package/plugins/extract-go/wiring.go +0 -434
  209. package/plugins/extract-go-nats/describe.go +0 -19
  210. package/plugins/extract-go-nats/describe_test.go +0 -11
  211. package/plugins/extract-go-nats/extract.go +0 -177
  212. package/plugins/extract-go-nats/extract_test.go +0 -389
  213. package/plugins/extract-go-nats/index.go +0 -379
  214. package/plugins/extract-go-nats/main.go +0 -42
  215. package/plugins/extract-go-nats/resolve.go +0 -161
  216. package/plugins/extract-go-nats/sites.go +0 -224
  217. package/plugins/extract-graphql/describe.go +0 -19
  218. package/plugins/extract-graphql/describe_test.go +0 -11
  219. package/plugins/extract-graphql/extract.go +0 -433
  220. package/plugins/extract-graphql/extract_test.go +0 -256
  221. package/plugins/extract-graphql/ids.go +0 -49
  222. package/plugins/extract-graphql/lex.go +0 -237
  223. package/plugins/extract-graphql/main.go +0 -51
  224. package/plugins/extract-graphql/parse.go +0 -621
  225. package/plugins/extract-graphql/parse_test.go +0 -122
  226. package/plugins/extract-http-clients/describe.go +0 -19
  227. package/plugins/extract-http-clients/describe_test.go +0 -11
  228. package/plugins/extract-http-clients/extract.go +0 -705
  229. package/plugins/extract-http-clients/extract_test.go +0 -1263
  230. package/plugins/extract-http-clients/main.go +0 -42
  231. package/plugins/extract-openapi/describe.go +0 -19
  232. package/plugins/extract-openapi/describe_test.go +0 -11
  233. package/plugins/extract-openapi/discover.go +0 -243
  234. package/plugins/extract-openapi/extract.go +0 -526
  235. package/plugins/extract-openapi/extract_test.go +0 -545
  236. package/plugins/extract-openapi/main.go +0 -75
  237. package/plugins/extract-openapi/spec.go +0 -350
  238. package/plugins/extract-project/describe.go +0 -19
  239. package/plugins/extract-project/describe_test.go +0 -11
  240. package/plugins/extract-project/extract.go +0 -221
  241. package/plugins/extract-project/extract_test.go +0 -109
  242. package/plugins/extract-project/main.go +0 -41
  243. package/plugins/extract-proto/ast.go +0 -125
  244. package/plugins/extract-proto/consumes.go +0 -77
  245. package/plugins/extract-proto/describe.go +0 -19
  246. package/plugins/extract-proto/extract.go +0 -293
  247. package/plugins/extract-proto/extract_test.go +0 -459
  248. package/plugins/extract-proto/ids.go +0 -89
  249. package/plugins/extract-proto/ids_test.go +0 -57
  250. package/plugins/extract-proto/lex.go +0 -285
  251. package/plugins/extract-proto/main.go +0 -100
  252. package/plugins/extract-proto/module.go +0 -120
  253. package/plugins/extract-proto/parse.go +0 -720
  254. package/plugins/extract-proto/parse_test.go +0 -307
  255. package/plugins/extract-proto/provides.go +0 -236
  256. package/plugins/extract-proto/resolve.go +0 -222
  257. package/plugins/extract-proto/resolve_test.go +0 -119
  258. package/plugins/extract-redis/describe.go +0 -19
  259. package/plugins/extract-redis/describe_test.go +0 -11
  260. package/plugins/extract-redis/extract.go +0 -183
  261. package/plugins/extract-redis/extract_test.go +0 -168
  262. package/plugins/extract-redis/keyspaces.go +0 -469
  263. package/plugins/extract-redis/main.go +0 -44
  264. package/plugins/extract-river/describe.go +0 -19
  265. package/plugins/extract-river/describe_test.go +0 -11
  266. package/plugins/extract-river/extract.go +0 -507
  267. package/plugins/extract-river/extract_test.go +0 -132
  268. package/plugins/extract-river/main.go +0 -42
  269. package/plugins/extract-sql/ddl.go +0 -893
  270. package/plugins/extract-sql/ddl_test.go +0 -401
  271. package/plugins/extract-sql/describe.go +0 -19
  272. package/plugins/extract-sql/describe_test.go +0 -11
  273. package/plugins/extract-sql/layout.go +0 -221
  274. package/plugins/extract-sql/layout_test.go +0 -55
  275. package/plugins/extract-sql/lineage.go +0 -107
  276. package/plugins/extract-sql/main.go +0 -141
  277. package/plugins/extract-sql/maps.go +0 -564
  278. package/plugins/extract-sql/maps_java.go +0 -117
  279. package/plugins/extract-sql/maps_rust.go +0 -333
  280. package/plugins/extract-sql/maps_rust_test.go +0 -70
  281. package/plugins/extract-sql/maps_test.go +0 -204
  282. package/plugins/extract-sql/maps_ts.go +0 -398
  283. package/plugins/extract-sql/maps_ts_test.go +0 -136
  284. package/plugins/extract-sql/projection.go +0 -70
  285. package/plugins/extract-sql/projection_test.go +0 -50
  286. package/plugins/extract-sql/store.go +0 -420
  287. package/plugins/extract-sql/store_test.go +0 -233
  288. package/plugins/extract-sql/view.go +0 -295
  289. package/plugins/extract-watermill/describe.go +0 -19
  290. package/plugins/extract-watermill/describe_test.go +0 -11
  291. package/plugins/extract-watermill/extract.go +0 -1228
  292. package/plugins/extract-watermill/extract_test.go +0 -234
  293. package/plugins/extract-watermill/main.go +0 -41
  294. package/plugins/extract-wsdl/describe.go +0 -19
  295. package/plugins/extract-wsdl/describe_test.go +0 -11
  296. package/plugins/extract-wsdl/extract.go +0 -148
  297. package/plugins/extract-wsdl/extract_test.go +0 -45
  298. package/plugins/extract-wsdl/main.go +0 -53
  299. package/plugins/fetch-bsr/auth.go +0 -114
  300. package/plugins/fetch-bsr/bsr.go +0 -240
  301. package/plugins/fetch-bsr/cache.go +0 -66
  302. package/plugins/fetch-bsr/describe.go +0 -19
  303. package/plugins/fetch-bsr/fetch.go +0 -192
  304. package/plugins/fetch-bsr/fetch_test.go +0 -465
  305. package/plugins/fetch-bsr/lock.go +0 -71
  306. package/plugins/fetch-bsr/main.go +0 -117
  307. package/plugins/fetch-csr/auth.go +0 -95
  308. package/plugins/fetch-csr/cache.go +0 -69
  309. package/plugins/fetch-csr/describe.go +0 -19
  310. package/plugins/fetch-csr/describe_test.go +0 -11
  311. package/plugins/fetch-csr/fetch.go +0 -254
  312. package/plugins/fetch-csr/fetch_test.go +0 -494
  313. package/plugins/fetch-csr/lock.go +0 -95
  314. package/plugins/fetch-csr/main.go +0 -122
  315. package/plugins/fetch-csr/registry.go +0 -209
  316. package/plugins/fetch-git/cache.go +0 -65
  317. package/plugins/fetch-git/describe.go +0 -19
  318. package/plugins/fetch-git/describe_test.go +0 -11
  319. package/plugins/fetch-git/fetch.go +0 -181
  320. package/plugins/fetch-git/fetch_test.go +0 -332
  321. package/plugins/fetch-git/git.go +0 -169
  322. package/plugins/fetch-git/lock.go +0 -72
  323. package/plugins/fetch-git/main.go +0 -127
  324. package/plugins/fetch-git/offline.go +0 -39
  325. package/plugins/fetch-git/pin.go +0 -90
  326. package/plugins/fetch-git/pin_test.go +0 -74
  327. package/plugins/gen-backstage/describe.go +0 -17
  328. package/plugins/gen-backstage/main.go +0 -22
  329. package/plugins/gen-backstage/plugin.go +0 -473
  330. package/plugins/gen-backstage/plugin_test.go +0 -145
  331. package/plugins/gen-backstage.wasm +0 -0
  332. package/plugins/gen-markdown/adr.go +0 -162
  333. package/plugins/gen-markdown/aggregate.go +0 -362
  334. package/plugins/gen-markdown/canonical.go +0 -93
  335. package/plugins/gen-markdown/context.go +0 -90
  336. package/plugins/gen-markdown/coverage_test.go +0 -89
  337. package/plugins/gen-markdown/describe.go +0 -19
  338. package/plugins/gen-markdown/describe_test.go +0 -11
  339. package/plugins/gen-markdown/external.go +0 -71
  340. package/plugins/gen-markdown/flow.go +0 -277
  341. package/plugins/gen-markdown/glossary.go +0 -76
  342. package/plugins/gen-markdown/glossary_test.go +0 -148
  343. package/plugins/gen-markdown/llms.go +0 -302
  344. package/plugins/gen-markdown/main.go +0 -22
  345. package/plugins/gen-markdown/markdown.go +0 -254
  346. package/plugins/gen-markdown/markdown_test.go +0 -100
  347. package/plugins/gen-markdown/module.go +0 -107
  348. package/plugins/gen-markdown/plugin.go +0 -39
  349. package/plugins/gen-markdown/quality_test.go +0 -176
  350. package/plugins/gen-markdown/redis_test.go +0 -38
  351. package/plugins/gen-markdown/render.go +0 -344
  352. package/plugins/gen-markdown/render_test.go +0 -186
  353. package/plugins/gen-markdown/service.go +0 -456
  354. package/plugins/gen-markdown/source.go +0 -177
  355. package/plugins/gen-markdown/store.go +0 -201
  356. package/plugins/gen-markdown.wasm +0 -0
  357. package/plugins/gen-mermaid/describe.go +0 -17
  358. package/plugins/gen-mermaid/main.go +0 -22
  359. package/plugins/gen-mermaid/plugin.go +0 -104
  360. package/plugins/gen-mermaid/plugin_test.go +0 -33
  361. package/plugins/gen-mermaid.wasm +0 -0
  362. package/plugins/openapi/ids.go +0 -261
  363. package/plugins/openapi/ids_test.go +0 -98
  364. package/plugins/verify-codeowners/describe.go +0 -19
  365. package/plugins/verify-codeowners/describe_test.go +0 -11
  366. package/plugins/verify-codeowners/main.go +0 -75
  367. package/plugins/verify-codeowners/match.go +0 -85
  368. package/plugins/verify-codeowners/match_test.go +0 -47
  369. package/plugins/verify-codeowners/owners.go +0 -164
  370. package/plugins/verify-codeowners/owners_test.go +0 -225
  371. package/plugins/verify-codeowners/parse.go +0 -90
  372. package/plugins/verify-codeowners/parse_test.go +0 -62
  373. package/plugins/verify-otel/describe.go +0 -19
  374. package/plugins/verify-otel/describe_test.go +0 -11
  375. package/plugins/verify-otel/main.go +0 -53
  376. package/plugins/verify-otel/match.go +0 -336
  377. package/plugins/verify-otel/otlp.go +0 -200
  378. package/plugins/verify-otel/verify.go +0 -734
  379. package/plugins/verify-otel/verify_test.go +0 -460
  380. /package/{plugins/fetch-csr/options.schema.json → scripts/host-plugins/fetch-csr.options.json} +0 -0
  381. /package/{plugins/fetch-git/options.schema.json → scripts/host-plugins/fetch-git.options.json} +0 -0
@@ -0,0 +1,282 @@
1
+ // The tour's second stop: one real flow, played.
2
+ //
3
+ // The steps on the left are the flow's own, in order, with the status each
4
+ // one has in the catalog. The canvas on the right is the flow's participants,
5
+ // placed by elk, with one line per direction a message travels. The playback
6
+ // walks the steps: the current one is lit on the list, and its line and its
7
+ // two ends are lit on the canvas while everything else dims - the reading a
8
+ // finger would do down a sequence diagram, done for the reader.
9
+ //
10
+ // It plays while it is on screen and stops when it is not, when the reader
11
+ // pauses it, when they pick a step, and when they asked for less motion.
12
+
13
+ import { useEffect, useMemo, useRef, useState } from "react";
14
+ import { Link } from "react-router";
15
+ import { Background, ReactFlow, ReactFlowProvider } from "@xyflow/react";
16
+ import type { Edge } from "@xyflow/react";
17
+ import "@xyflow/react/dist/style.css";
18
+ import { useInView } from "motion/react";
19
+ import { ArrowRight, Pause, Play } from "lucide-react";
20
+ import type { Flow, FlowNode, Step } from "../catalog";
21
+ import { DiagramSkeleton } from "../components/DiagramSkeleton";
22
+ import { catalog } from "../data";
23
+ import { nodeTypes } from "../graph/nodes";
24
+ import type { ServiceNodeData } from "../graph/nodes";
25
+ import { EDGE_W, EDGE_W_LIT } from "../graph/theme";
26
+ import { useElkFlow } from "../graph/useElkFlow";
27
+ import type { FlowSpec } from "../graph/useElkFlow";
28
+ import { worstStatus } from "../lib/event-graph";
29
+ import { LayoutGroup, m, transitions, useReducedMotion } from "../lib/motion";
30
+ import { paths } from "../routes";
31
+ import { catalogTo } from "./catalog";
32
+
33
+ const FLOW_ID = "flow.cart-checkout";
34
+ const STEP_MS = 1600;
35
+ const LANE_NODE_W = 144;
36
+
37
+ /** The steps in reading order, whatever frames they sit in. */
38
+ function flatSteps(nodes: readonly FlowNode[]): Step[] {
39
+ const out: Step[] = [];
40
+ for (const node of nodes) {
41
+ if (node.type === "step") out.push(node);
42
+ else if (node.type === "parallel")
43
+ for (const branch of node.branches) out.push(...flatSteps(branch));
44
+ else if (node.type === "alt")
45
+ for (const branch of node.branches) out.push(...flatSteps(branch.steps));
46
+ else out.push(...flatSteps(node.steps));
47
+ }
48
+ return out;
49
+ }
50
+
51
+ const laneId = (step: Step): string => `${step.from}->${step.to}`;
52
+
53
+ /** What the box says under the eyebrow: a service by its id, anything else by what it is. */
54
+ function participantData(
55
+ participant: Flow["participants"][number],
56
+ ): ServiceNodeData {
57
+ return {
58
+ label: participant.label ?? participant.id,
59
+ context: participant.context,
60
+ ghost: participant.kind === "unknown",
61
+ kind: "service",
62
+ role: participant.kind,
63
+ };
64
+ }
65
+
66
+ function Canvas({ flow, steps, at }: { flow: Flow; steps: Step[]; at: number }) {
67
+ const spec = useMemo<FlowSpec>(() => {
68
+ const lanes = new Map<string, Step[]>();
69
+ for (const step of steps) {
70
+ const lane = lanes.get(laneId(step));
71
+ if (lane) lane.push(step);
72
+ else lanes.set(laneId(step), [step]);
73
+ }
74
+ // Narrow boxes and no labels for elk to leave room for: the label a lane
75
+ // shows is the current step's, put on at render time, and the whole
76
+ // picture has to fit a box half the width of the graph page's.
77
+ return {
78
+ nodes: flow.participants.map((p) => ({
79
+ id: p.id,
80
+ width: LANE_NODE_W,
81
+ data: participantData(p),
82
+ })),
83
+ edges: [...lanes.entries()].map(([id, lane]) => ({
84
+ id,
85
+ source: lane[0]!.from,
86
+ target: lane[0]!.to,
87
+ label: "",
88
+ status: worstStatus(lane.map((s) => s.status)),
89
+ })),
90
+ direction: "RIGHT",
91
+ layerSpacing: 48,
92
+ };
93
+ }, [flow, steps]);
94
+ const state = useElkFlow(spec);
95
+
96
+ const active = steps[at] ?? null;
97
+ const lit = active ? laneId(active) : null;
98
+
99
+ const nodes = useMemo(
100
+ () =>
101
+ state.nodes.map((node) => ({
102
+ ...node,
103
+ style: {
104
+ ...node.style,
105
+ opacity:
106
+ !active || node.id === active.from || node.id === active.to
107
+ ? 1
108
+ : 0.4,
109
+ },
110
+ })),
111
+ [state.nodes, active],
112
+ );
113
+
114
+ const edges: Edge[] = useMemo(
115
+ () =>
116
+ state.edges.map((edge) => {
117
+ const on = edge.id === lit;
118
+ return {
119
+ ...edge,
120
+ label: on ? (active?.label ?? "") : "",
121
+ style: {
122
+ ...edge.style,
123
+ opacity: lit && !on ? 0.3 : 1,
124
+ strokeWidth: on ? EDGE_W_LIT : EDGE_W,
125
+ },
126
+ zIndex: on ? 10 : 0,
127
+ };
128
+ }),
129
+ [state.edges, lit, active],
130
+ );
131
+
132
+ return (
133
+ <div className="canvas-motion relative h-[340px] overflow-hidden rounded-card border border-line">
134
+ {state.ready ? null : <DiagramSkeleton />}
135
+ <ReactFlow
136
+ nodes={nodes}
137
+ edges={edges}
138
+ nodeTypes={nodeTypes}
139
+ nodesDraggable={false}
140
+ nodesConnectable={false}
141
+ elementsSelectable={false}
142
+ zoomOnScroll={false}
143
+ preventScrolling={false}
144
+ proOptions={{ hideAttribution: true }}
145
+ fitView
146
+ fitViewOptions={{ padding: 0.18, maxZoom: 1.1 }}
147
+ minZoom={0.3}
148
+ maxZoom={1.6}
149
+ key={state.ready ? `fit-${state.nodes.length}` : "pending"}
150
+ >
151
+ <Background gap={20} size={2} />
152
+ </ReactFlow>
153
+ </div>
154
+ );
155
+ }
156
+
157
+ export function FlowPlayback() {
158
+ const flow = useMemo(
159
+ () => catalog.flows.find((f) => f.id === FLOW_ID) ?? catalog.flows[0] ?? null,
160
+ [],
161
+ );
162
+ const steps = useMemo(() => (flow ? flatSteps(flow.steps) : []), [flow]);
163
+ const [at, setAt] = useState(0);
164
+ const [playing, setPlaying] = useState(true);
165
+ const box = useRef<HTMLDivElement>(null);
166
+ const inView = useInView(box, { amount: 0.4 });
167
+ const reduced = useReducedMotion();
168
+
169
+ useEffect(() => {
170
+ if (!playing || !inView || reduced || steps.length < 2) return;
171
+ const every = window.setInterval(
172
+ () => setAt((i) => (i + 1) % steps.length),
173
+ STEP_MS,
174
+ );
175
+ return () => window.clearInterval(every);
176
+ }, [playing, inView, reduced, steps.length]);
177
+
178
+ if (!flow) {
179
+ return (
180
+ <div className="p-6 text-sm text-muted">
181
+ The example catalog has no flows to play.
182
+ </div>
183
+ );
184
+ }
185
+
186
+ const verified = steps.filter((s) => s.status === "verified").length;
187
+ const health =
188
+ verified === steps.length
189
+ ? "status-verified"
190
+ : steps.some((s) => s.status === "unresolved")
191
+ ? "status-unresolved"
192
+ : "status-declared";
193
+
194
+ return (
195
+ <div ref={box} className="p-4 sm:p-5">
196
+ <div className="mb-4 flex flex-wrap items-start justify-between gap-3">
197
+ <div className="min-w-0">
198
+ <div className="mono truncate text-faint">flow / {flow.slug}</div>
199
+ <div className="mt-1 text-md font-semibold text-ink">{flow.name}</div>
200
+ </div>
201
+ <div className="flex items-center gap-2">
202
+ <span className={`chip ${health}`}>
203
+ {verified} / {steps.length} verified
204
+ </span>
205
+ <button
206
+ type="button"
207
+ className="tbtn size-8 justify-center p-0"
208
+ onClick={() => setPlaying((p) => !p)}
209
+ aria-label={playing ? "Pause playback" : "Play the flow"}
210
+ aria-pressed={playing}
211
+ >
212
+ {playing ? <Pause size={14} /> : <Play size={14} />}
213
+ </button>
214
+ </div>
215
+ </div>
216
+ <div className="grid gap-4 lg:grid-cols-[minmax(0,0.8fr)_minmax(0,1.2fr)]">
217
+ <LayoutGroup id="flow-playback">
218
+ <ol
219
+ className="overflow-hidden rounded-card border border-line bg-canvas"
220
+ aria-label={`Steps of ${flow.name}`}
221
+ >
222
+ {steps.map((step, index) => {
223
+ const on = index === at;
224
+ return (
225
+ <li key={step.id} className="relative border-b border-line last:border-b-0">
226
+ {on ? (
227
+ <m.span
228
+ layoutId="flow-cursor"
229
+ transition={transitions.settle}
230
+ className="absolute inset-0 bg-accent/8"
231
+ aria-hidden
232
+ />
233
+ ) : null}
234
+ <button
235
+ type="button"
236
+ onClick={() => {
237
+ setAt(index);
238
+ setPlaying(false);
239
+ }}
240
+ aria-current={on ? "step" : undefined}
241
+ className={`relative grid w-full grid-cols-[26px_minmax(0,1fr)_auto] items-center gap-3 px-3 py-2 text-left t-narrative ${
242
+ on ? "opacity-100" : "opacity-70 hover:opacity-100"
243
+ }`}
244
+ >
245
+ <span className="mono tnum text-faint">
246
+ {String(index + 1).padStart(2, "0")}
247
+ </span>
248
+ <span className="min-w-0">
249
+ <span className="block truncate font-medium text-ink">
250
+ {step.label ?? step.ref ?? step.kind}
251
+ </span>
252
+ <span className="mono block truncate text-muted">
253
+ {step.from} → {step.to}
254
+ </span>
255
+ </span>
256
+ <span className={`chip status-${step.status}`}>
257
+ {step.kind}
258
+ </span>
259
+ </button>
260
+ </li>
261
+ );
262
+ })}
263
+ </ol>
264
+ </LayoutGroup>
265
+ <ReactFlowProvider>
266
+ <Canvas flow={flow} steps={steps} at={at} />
267
+ </ReactFlowProvider>
268
+ </div>
269
+ <div className="mt-3 flex items-center justify-between gap-3">
270
+ <span className="mono truncate text-faint">
271
+ {steps[at]?.line ?? flow.source ?? ""}
272
+ </span>
273
+ <Link
274
+ to={catalogTo(paths.flow(flow.slug))}
275
+ className="inline-flex shrink-0 items-center gap-1 text-sm text-accent hover:underline"
276
+ >
277
+ Open the flow <ArrowRight size={13} />
278
+ </Link>
279
+ </div>
280
+ </div>
281
+ );
282
+ }
@@ -0,0 +1,128 @@
1
+ // The hero's picture: the example estate's context map, drawn by the same
2
+ // component that draws it on the map page, with elk placing the domains and
3
+ // one line per relationship.
4
+ //
5
+ // It tours itself until the reader takes over. Every few seconds the next
6
+ // domain is selected - which lights its lines and dims the rest, exactly as a
7
+ // click would - and a card names it. A pointer over the map pauses the tour;
8
+ // a click ends it, because from then on the selection is the reader's.
9
+
10
+ import { useEffect, useMemo, useRef, useState } from "react";
11
+ import { Link } from "react-router";
12
+ import { ArrowRight, Boxes } from "lucide-react";
13
+ import { catalog } from "../data";
14
+ import { contextVar } from "../lib/context-color";
15
+ import { contextMap } from "../lib/context-map";
16
+ import { contextStats } from "../lib/derive";
17
+ import { AnimatePresence, m, rise, useReducedMotion } from "../lib/motion";
18
+ import { ContextMapPane } from "../map/ContextMapGraph";
19
+ import { paths } from "../routes";
20
+ import { useSelectionStore } from "../selection/store";
21
+ import { catalogTo, useSelectionCleared } from "./catalog";
22
+
23
+ const FIRST_MS = 1400;
24
+ const EVERY_MS = 2800;
25
+
26
+ export function HeroMap() {
27
+ const relations = useMemo(() => contextMap(catalog), []);
28
+ const selectionId = useSelectionStore((s) => s.selection?.id ?? null);
29
+ const select = useSelectionStore((s) => s.select);
30
+ const reduced = useReducedMotion();
31
+ const [touched, setTouched] = useState(false);
32
+ const [hovering, setHovering] = useState(false);
33
+ const at = useRef(0);
34
+ useSelectionCleared();
35
+
36
+ useEffect(() => {
37
+ if (touched || hovering || reduced) return;
38
+ const ids = catalog.contexts.map((c) => c.id);
39
+ if (ids.length === 0) return;
40
+ const tick = () => {
41
+ select(ids[at.current % ids.length]!, "diagram");
42
+ at.current += 1;
43
+ };
44
+ let every = 0;
45
+ const first = window.setTimeout(() => {
46
+ tick();
47
+ every = window.setInterval(tick, EVERY_MS);
48
+ }, FIRST_MS);
49
+ return () => {
50
+ window.clearTimeout(first);
51
+ window.clearInterval(every);
52
+ };
53
+ }, [touched, hovering, reduced, select]);
54
+
55
+ const context = catalog.contexts.find((c) => c.id === selectionId) ?? null;
56
+ const stats = context ? contextStats(context) : null;
57
+
58
+ return (
59
+ <div
60
+ className="relative h-full"
61
+ onPointerDown={() => setTouched(true)}
62
+ onPointerEnter={() => setHovering(true)}
63
+ onPointerLeave={() => setHovering(false)}
64
+ >
65
+ {/* The map stops above the strip, so the strip never covers a domain;
66
+ the frame's dotted ground runs on beneath both. */}
67
+ <div className="absolute inset-x-0 top-0 bottom-[68px]">
68
+ <ContextMapPane
69
+ catalog={catalog}
70
+ relations={relations}
71
+ zoomOnScroll={false}
72
+ compactLayout
73
+ showExport={false}
74
+ inactiveEdgeOpacity={0.36}
75
+ inactiveNodeOpacity={0.78}
76
+ />
77
+ </div>
78
+ <AnimatePresence mode="wait">
79
+ {context && stats ? (
80
+ <m.div
81
+ key={context.id}
82
+ {...rise}
83
+ className="pointer-events-none absolute right-3 bottom-3 left-3 z-10 flex items-center gap-3 rounded-card border border-line bg-canvas/95 px-3 py-2 shadow-md backdrop-blur"
84
+ >
85
+ <span
86
+ className="flow-tile shrink-0"
87
+ style={{ color: contextVar(context.id), width: 28, height: 28 }}
88
+ >
89
+ <Boxes size={15} aria-hidden />
90
+ </span>
91
+ <div className="min-w-0 flex-1">
92
+ <div className="flex items-baseline gap-2">
93
+ <span className="truncate font-semibold text-ink">
94
+ {context.name}
95
+ </span>
96
+ <span className="mono shrink-0 text-faint">
97
+ {context.id}
98
+ {context.classification ? ` · ${context.classification}` : ""}
99
+ </span>
100
+ </div>
101
+ <div className="truncate text-sm text-muted">
102
+ {context.summary || "\u00a0"}
103
+ </div>
104
+ </div>
105
+ <div className="mono hidden shrink-0 gap-x-3 text-faint md:flex">
106
+ <span>
107
+ <span className="tnum text-ink">{stats.services}</span> services
108
+ </span>
109
+ <span>
110
+ <span className="tnum text-ink">{stats.aggregates}</span>{" "}
111
+ aggregates
112
+ </span>
113
+ <span>
114
+ <span className="tnum text-ink">{stats.events}</span> events
115
+ </span>
116
+ </div>
117
+ <Link
118
+ to={catalogTo(paths.context(context.id))}
119
+ className="pointer-events-auto inline-flex shrink-0 items-center gap-1 text-sm text-accent hover:underline"
120
+ >
121
+ Open <ArrowRight size={13} />
122
+ </Link>
123
+ </m.div>
124
+ ) : null}
125
+ </AnimatePresence>
126
+ </div>
127
+ );
128
+ }