@shortlink-org/portolan 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (826) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +387 -0
  3. package/catalog/enum_test.go +46 -0
  4. package/catalog/model.go +932 -0
  5. package/catalog/roundtrip_test.go +185 -0
  6. package/catalog/via_test.go +38 -0
  7. package/cli/portolan.mjs +368 -0
  8. package/go.mod +14 -0
  9. package/go.sum +14 -0
  10. package/index.html +13 -0
  11. package/internal/commands/cargo.go +154 -0
  12. package/internal/commands/commands.go +143 -0
  13. package/internal/commands/commands_test.go +323 -0
  14. package/internal/commands/gradle.go +77 -0
  15. package/internal/commands/justfile.go +82 -0
  16. package/internal/commands/makefile.go +106 -0
  17. package/internal/commands/maven.go +93 -0
  18. package/internal/commands/packagejson.go +159 -0
  19. package/internal/commands/pyproject.go +169 -0
  20. package/internal/commands/taskfile.go +101 -0
  21. package/internal/commands/testdata/estate/.cargo/config.toml +7 -0
  22. package/internal/commands/testdata/estate/Makefile +33 -0
  23. package/internal/commands/testdata/estate/Taskfile.yml +21 -0
  24. package/internal/commands/testdata/estate/build.gradle.kts +21 -0
  25. package/internal/commands/testdata/estate/justfile +20 -0
  26. package/internal/commands/testdata/estate/package.json +13 -0
  27. package/internal/commands/testdata/estate/pom.xml +29 -0
  28. package/internal/commands/testdata/estate/pyproject.toml +27 -0
  29. package/internal/commands/testdata/estate/xtask/src/main.rs +27 -0
  30. package/internal/commands/testdata/golden/commands.json +305 -0
  31. package/internal/gohttp/analyze.go +2500 -0
  32. package/internal/gohttp/endpoints.go +1067 -0
  33. package/internal/gohttp/roots.go +320 -0
  34. package/internal/gohttp/typed.go +143 -0
  35. package/internal/goscan/constants.go +85 -0
  36. package/internal/goscan/goscan_test.go +227 -0
  37. package/internal/goscan/names.go +52 -0
  38. package/internal/goscan/parse_test.go +11 -0
  39. package/internal/goscan/source.go +36 -0
  40. package/internal/goscan/tree.go +155 -0
  41. package/internal/goscan/types.go +99 -0
  42. package/internal/wsdl/ids.go +127 -0
  43. package/internal/wsdl/ids_test.go +21 -0
  44. package/internal/wsdl/model.go +62 -0
  45. package/internal/wsdl/parse.go +920 -0
  46. package/internal/wsdl/parse_test.go +133 -0
  47. package/package.json +117 -0
  48. package/plugin/describe.go +107 -0
  49. package/plugin/describe_test.go +114 -0
  50. package/plugin/protocol.go +117 -0
  51. package/plugin/schematest/schematest.go +126 -0
  52. package/plugins/README.md +1160 -0
  53. package/plugins/extract-adr/describe.go +19 -0
  54. package/plugins/extract-adr/describe_test.go +11 -0
  55. package/plugins/extract-adr/extract.go +153 -0
  56. package/plugins/extract-adr/extract_test.go +350 -0
  57. package/plugins/extract-adr/history.go +99 -0
  58. package/plugins/extract-adr/main.go +65 -0
  59. package/plugins/extract-adr/options.schema.json +26 -0
  60. package/plugins/extract-adr/parse.go +642 -0
  61. package/plugins/extract-adr/parse_test.go +405 -0
  62. package/plugins/extract-asyncapi/describe.go +19 -0
  63. package/plugins/extract-asyncapi/describe_test.go +11 -0
  64. package/plugins/extract-asyncapi/extract.go +315 -0
  65. package/plugins/extract-asyncapi/extract_test.go +197 -0
  66. package/plugins/extract-asyncapi/main.go +48 -0
  67. package/plugins/extract-asyncapi/options.schema.json +26 -0
  68. package/plugins/extract-asyncapi/spec.go +151 -0
  69. package/plugins/extract-celery/README.md +169 -0
  70. package/plugins/extract-celery/extract.py +346 -0
  71. package/plugins/extract-celery/extract_test.py +192 -0
  72. package/plugins/extract-celery/main.py +66 -0
  73. package/plugins/extract-celery/options.py +31 -0
  74. package/plugins/extract-celery/options.schema.json +31 -0
  75. package/plugins/extract-celery/producers.py +156 -0
  76. package/plugins/extract-celery/routes_test.py +106 -0
  77. package/plugins/extract-commands/describe.go +19 -0
  78. package/plugins/extract-commands/describe_test.go +11 -0
  79. package/plugins/extract-commands/extract.go +79 -0
  80. package/plugins/extract-commands/extract_test.go +92 -0
  81. package/plugins/extract-commands/main.go +55 -0
  82. package/plugins/extract-commands/options.schema.json +20 -0
  83. package/plugins/extract-csr/avro.go +258 -0
  84. package/plugins/extract-csr/describe.go +19 -0
  85. package/plugins/extract-csr/describe_test.go +11 -0
  86. package/plugins/extract-csr/extract.go +338 -0
  87. package/plugins/extract-csr/extract_test.go +374 -0
  88. package/plugins/extract-csr/jsonschema.go +343 -0
  89. package/plugins/extract-csr/lock.go +25 -0
  90. package/plugins/extract-csr/main.go +84 -0
  91. package/plugins/extract-csr/options.schema.json +43 -0
  92. package/plugins/extract-csr/subject.go +102 -0
  93. package/plugins/extract-django/README.md +386 -0
  94. package/plugins/extract-django/apps.py +100 -0
  95. package/plugins/extract-django/auth.py +302 -0
  96. package/plugins/extract-django/auth_test.py +188 -0
  97. package/plugins/extract-django/clients.py +121 -0
  98. package/plugins/extract-django/contracts.py +458 -0
  99. package/plugins/extract-django/database.py +119 -0
  100. package/plugins/extract-django/domain.py +318 -0
  101. package/plugins/extract-django/events.py +86 -0
  102. package/plugins/extract-django/extract.py +601 -0
  103. package/plugins/extract-django/extract_test.py +397 -0
  104. package/plugins/extract-django/flows.py +1278 -0
  105. package/plugins/extract-django/flows_test.py +338 -0
  106. package/plugins/extract-django/ids.py +42 -0
  107. package/plugins/extract-django/ids_test.py +30 -0
  108. package/plugins/extract-django/lifecycle.py +222 -0
  109. package/plugins/extract-django/lifecycle_test.py +76 -0
  110. package/plugins/extract-django/main.py +66 -0
  111. package/plugins/extract-django/openapi.py +229 -0
  112. package/plugins/extract-django/openapi_test.py +60 -0
  113. package/plugins/extract-django/operations.py +96 -0
  114. package/plugins/extract-django/options.py +61 -0
  115. package/plugins/extract-django/options.schema.json +126 -0
  116. package/plugins/extract-django/routing.py +214 -0
  117. package/plugins/extract-django/routing_test.py +198 -0
  118. package/plugins/extract-django/serializers.py +558 -0
  119. package/plugins/extract-django/store.py +434 -0
  120. package/plugins/extract-django/transport.py +242 -0
  121. package/plugins/extract-flows/describe.go +19 -0
  122. package/plugins/extract-flows/describe_test.go +11 -0
  123. package/plugins/extract-flows/extract.go +91 -0
  124. package/plugins/extract-flows/main.go +45 -0
  125. package/plugins/extract-flows/options.schema.json +16 -0
  126. package/plugins/extract-flows/parse.go +593 -0
  127. package/plugins/extract-flows/parse_test.go +204 -0
  128. package/plugins/extract-glossary/describe.go +19 -0
  129. package/plugins/extract-glossary/describe_test.go +11 -0
  130. package/plugins/extract-glossary/extract.go +115 -0
  131. package/plugins/extract-glossary/extract_test.go +220 -0
  132. package/plugins/extract-glossary/main.go +59 -0
  133. package/plugins/extract-glossary/options.schema.json +21 -0
  134. package/plugins/extract-glossary/parse.go +214 -0
  135. package/plugins/extract-glossary/parse_test.go +203 -0
  136. package/plugins/extract-go/aggregate.go +214 -0
  137. package/plugins/extract-go/client.go +409 -0
  138. package/plugins/extract-go/client_test.go +266 -0
  139. package/plugins/extract-go/describe.go +19 -0
  140. package/plugins/extract-go/describe_test.go +11 -0
  141. package/plugins/extract-go/enum.go +195 -0
  142. package/plugins/extract-go/enum_test.go +82 -0
  143. package/plugins/extract-go/event.go +99 -0
  144. package/plugins/extract-go/extract.go +191 -0
  145. package/plugins/extract-go/extract_test.go +261 -0
  146. package/plugins/extract-go/flow.go +1441 -0
  147. package/plugins/extract-go/flow_test.go +609 -0
  148. package/plugins/extract-go/httpclient.go +174 -0
  149. package/plugins/extract-go/httpclient_test.go +296 -0
  150. package/plugins/extract-go/ids.go +92 -0
  151. package/plugins/extract-go/layout.go +225 -0
  152. package/plugins/extract-go/layout_test.go +125 -0
  153. package/plugins/extract-go/lifecycle.go +324 -0
  154. package/plugins/extract-go/lifecycle_test.go +108 -0
  155. package/plugins/extract-go/main.go +79 -0
  156. package/plugins/extract-go/operation.go +157 -0
  157. package/plugins/extract-go/options.schema.json +66 -0
  158. package/plugins/extract-go/source.go +316 -0
  159. package/plugins/extract-go/transport.go +321 -0
  160. package/plugins/extract-go/transport_test.go +145 -0
  161. package/plugins/extract-go/wiring.go +434 -0
  162. package/plugins/extract-go-nats/describe.go +19 -0
  163. package/plugins/extract-go-nats/describe_test.go +11 -0
  164. package/plugins/extract-go-nats/extract.go +177 -0
  165. package/plugins/extract-go-nats/extract_test.go +389 -0
  166. package/plugins/extract-go-nats/index.go +379 -0
  167. package/plugins/extract-go-nats/main.go +42 -0
  168. package/plugins/extract-go-nats/options.schema.json +21 -0
  169. package/plugins/extract-go-nats/resolve.go +161 -0
  170. package/plugins/extract-go-nats/sites.go +224 -0
  171. package/plugins/extract-graphql/describe.go +19 -0
  172. package/plugins/extract-graphql/describe_test.go +11 -0
  173. package/plugins/extract-graphql/extract.go +433 -0
  174. package/plugins/extract-graphql/extract_test.go +256 -0
  175. package/plugins/extract-graphql/ids.go +49 -0
  176. package/plugins/extract-graphql/lex.go +237 -0
  177. package/plugins/extract-graphql/main.go +51 -0
  178. package/plugins/extract-graphql/options.schema.json +31 -0
  179. package/plugins/extract-graphql/parse.go +621 -0
  180. package/plugins/extract-graphql/parse_test.go +122 -0
  181. package/plugins/extract-http-clients/describe.go +19 -0
  182. package/plugins/extract-http-clients/describe_test.go +11 -0
  183. package/plugins/extract-http-clients/extract.go +705 -0
  184. package/plugins/extract-http-clients/extract_test.go +1263 -0
  185. package/plugins/extract-http-clients/main.go +42 -0
  186. package/plugins/extract-http-clients/options.schema.json +33 -0
  187. package/plugins/extract-java/README.md +190 -0
  188. package/plugins/extract-java/build/org/portolan/extract/Catalog.class +0 -0
  189. package/plugins/extract-java/build/org/portolan/extract/Clients$1.class +0 -0
  190. package/plugins/extract-java/build/org/portolan/extract/Clients$2.class +0 -0
  191. package/plugins/extract-java/build/org/portolan/extract/Clients$Call.class +0 -0
  192. package/plugins/extract-java/build/org/portolan/extract/Clients$Client.class +0 -0
  193. package/plugins/extract-java/build/org/portolan/extract/Clients$Route.class +0 -0
  194. package/plugins/extract-java/build/org/portolan/extract/Clients.class +0 -0
  195. package/plugins/extract-java/build/org/portolan/extract/Domain$Aggregate.class +0 -0
  196. package/plugins/extract-java/build/org/portolan/extract/Domain.class +0 -0
  197. package/plugins/extract-java/build/org/portolan/extract/Events$Found.class +0 -0
  198. package/plugins/extract-java/build/org/portolan/extract/Events.class +0 -0
  199. package/plugins/extract-java/build/org/portolan/extract/Extract.class +0 -0
  200. package/plugins/extract-java/build/org/portolan/extract/Flows$1.class +0 -0
  201. package/plugins/extract-java/build/org/portolan/extract/Flows$Binding.class +0 -0
  202. package/plugins/extract-java/build/org/portolan/extract/Flows$Draft.class +0 -0
  203. package/plugins/extract-java/build/org/portolan/extract/Flows$Frame.class +0 -0
  204. package/plugins/extract-java/build/org/portolan/extract/Flows$Options.class +0 -0
  205. package/plugins/extract-java/build/org/portolan/extract/Flows.class +0 -0
  206. package/plugins/extract-java/build/org/portolan/extract/Ids.class +0 -0
  207. package/plugins/extract-java/build/org/portolan/extract/Json$Reader.class +0 -0
  208. package/plugins/extract-java/build/org/portolan/extract/Json.class +0 -0
  209. package/plugins/extract-java/build/org/portolan/extract/Lifecycle$1.class +0 -0
  210. package/plugins/extract-java/build/org/portolan/extract/Lifecycle$Move.class +0 -0
  211. package/plugins/extract-java/build/org/portolan/extract/Lifecycle.class +0 -0
  212. package/plugins/extract-java/build/org/portolan/extract/Main.class +0 -0
  213. package/plugins/extract-java/build/org/portolan/extract/OpenApi$Operation.class +0 -0
  214. package/plugins/extract-java/build/org/portolan/extract/OpenApi$Spec.class +0 -0
  215. package/plugins/extract-java/build/org/portolan/extract/OpenApi.class +0 -0
  216. package/plugins/extract-java/build/org/portolan/extract/Operations$1.class +0 -0
  217. package/plugins/extract-java/build/org/portolan/extract/Operations$UseCase.class +0 -0
  218. package/plugins/extract-java/build/org/portolan/extract/Operations.class +0 -0
  219. package/plugins/extract-java/build/org/portolan/extract/Proto$Rpc.class +0 -0
  220. package/plugins/extract-java/build/org/portolan/extract/Proto$Service.class +0 -0
  221. package/plugins/extract-java/build/org/portolan/extract/Proto.class +0 -0
  222. package/plugins/extract-java/build/org/portolan/extract/Protocol$Builder.class +0 -0
  223. package/plugins/extract-java/build/org/portolan/extract/Protocol$Input.class +0 -0
  224. package/plugins/extract-java/build/org/portolan/extract/Protocol$Options.class +0 -0
  225. package/plugins/extract-java/build/org/portolan/extract/Protocol.class +0 -0
  226. package/plugins/extract-java/build/org/portolan/extract/Source$Project.class +0 -0
  227. package/plugins/extract-java/build/org/portolan/extract/Source$Unit.class +0 -0
  228. package/plugins/extract-java/build/org/portolan/extract/Source.class +0 -0
  229. package/plugins/extract-java/build/org/portolan/extract/Transport$Endpoint.class +0 -0
  230. package/plugins/extract-java/build/org/portolan/extract/Transport.class +0 -0
  231. package/plugins/extract-java/build/org/portolan/extract/Yaml$Line.class +0 -0
  232. package/plugins/extract-java/build/org/portolan/extract/Yaml$Reader.class +0 -0
  233. package/plugins/extract-java/build/org/portolan/extract/Yaml.class +0 -0
  234. package/plugins/extract-java/options.schema.json +70 -0
  235. package/plugins/extract-openapi/describe.go +19 -0
  236. package/plugins/extract-openapi/describe_test.go +11 -0
  237. package/plugins/extract-openapi/discover.go +243 -0
  238. package/plugins/extract-openapi/extract.go +526 -0
  239. package/plugins/extract-openapi/extract_test.go +545 -0
  240. package/plugins/extract-openapi/main.go +75 -0
  241. package/plugins/extract-openapi/options.schema.json +62 -0
  242. package/plugins/extract-openapi/spec.go +350 -0
  243. package/plugins/extract-project/README.md +22 -0
  244. package/plugins/extract-project/describe.go +19 -0
  245. package/plugins/extract-project/describe_test.go +11 -0
  246. package/plugins/extract-project/extract.go +221 -0
  247. package/plugins/extract-project/extract_test.go +109 -0
  248. package/plugins/extract-project/main.go +41 -0
  249. package/plugins/extract-project/options.schema.json +48 -0
  250. package/plugins/extract-proto/ast.go +125 -0
  251. package/plugins/extract-proto/consumes.go +77 -0
  252. package/plugins/extract-proto/describe.go +19 -0
  253. package/plugins/extract-proto/extract.go +293 -0
  254. package/plugins/extract-proto/extract_test.go +459 -0
  255. package/plugins/extract-proto/ids.go +89 -0
  256. package/plugins/extract-proto/ids_test.go +57 -0
  257. package/plugins/extract-proto/lex.go +285 -0
  258. package/plugins/extract-proto/main.go +100 -0
  259. package/plugins/extract-proto/module.go +120 -0
  260. package/plugins/extract-proto/options.schema.json +51 -0
  261. package/plugins/extract-proto/parse.go +720 -0
  262. package/plugins/extract-proto/parse_test.go +307 -0
  263. package/plugins/extract-proto/provides.go +236 -0
  264. package/plugins/extract-proto/resolve.go +222 -0
  265. package/plugins/extract-proto/resolve_test.go +119 -0
  266. package/plugins/extract-python-kafka/README.md +36 -0
  267. package/plugins/extract-python-kafka/extract.py +211 -0
  268. package/plugins/extract-python-kafka/extract_test.py +68 -0
  269. package/plugins/extract-python-kafka/main.py +59 -0
  270. package/plugins/extract-python-kafka/options.py +29 -0
  271. package/plugins/extract-python-kafka/options.schema.json +31 -0
  272. package/plugins/extract-redis/README.md +11 -0
  273. package/plugins/extract-redis/describe.go +19 -0
  274. package/plugins/extract-redis/describe_test.go +11 -0
  275. package/plugins/extract-redis/extract.go +183 -0
  276. package/plugins/extract-redis/extract_test.go +168 -0
  277. package/plugins/extract-redis/keyspaces.go +469 -0
  278. package/plugins/extract-redis/main.go +44 -0
  279. package/plugins/extract-redis/options.schema.json +30 -0
  280. package/plugins/extract-river/describe.go +19 -0
  281. package/plugins/extract-river/describe_test.go +11 -0
  282. package/plugins/extract-river/extract.go +507 -0
  283. package/plugins/extract-river/extract_test.go +132 -0
  284. package/plugins/extract-river/main.go +42 -0
  285. package/plugins/extract-river/options.schema.json +21 -0
  286. package/plugins/extract-rust/Cargo.lock +133 -0
  287. package/plugins/extract-rust/Cargo.toml +23 -0
  288. package/plugins/extract-rust/README.md +204 -0
  289. package/plugins/extract-rust/options.schema.json +65 -0
  290. package/plugins/extract-rust/rustfmt.toml +1 -0
  291. package/plugins/extract-sql/ddl.go +893 -0
  292. package/plugins/extract-sql/ddl_test.go +401 -0
  293. package/plugins/extract-sql/describe.go +19 -0
  294. package/plugins/extract-sql/describe_test.go +11 -0
  295. package/plugins/extract-sql/layout.go +221 -0
  296. package/plugins/extract-sql/layout_test.go +55 -0
  297. package/plugins/extract-sql/lineage.go +107 -0
  298. package/plugins/extract-sql/main.go +141 -0
  299. package/plugins/extract-sql/maps.go +564 -0
  300. package/plugins/extract-sql/maps_java.go +117 -0
  301. package/plugins/extract-sql/maps_rust.go +333 -0
  302. package/plugins/extract-sql/maps_rust_test.go +70 -0
  303. package/plugins/extract-sql/maps_test.go +204 -0
  304. package/plugins/extract-sql/maps_ts.go +398 -0
  305. package/plugins/extract-sql/maps_ts_test.go +136 -0
  306. package/plugins/extract-sql/options.schema.json +53 -0
  307. package/plugins/extract-sql/projection.go +70 -0
  308. package/plugins/extract-sql/projection_test.go +50 -0
  309. package/plugins/extract-sql/store.go +420 -0
  310. package/plugins/extract-sql/store_test.go +233 -0
  311. package/plugins/extract-sql/view.go +295 -0
  312. package/plugins/extract-ts/README.md +306 -0
  313. package/plugins/extract-ts/ast.test.ts +178 -0
  314. package/plugins/extract-ts/ast.ts +648 -0
  315. package/plugins/extract-ts/clients.ts +185 -0
  316. package/plugins/extract-ts/domain.ts +164 -0
  317. package/plugins/extract-ts/extract.test.ts +208 -0
  318. package/plugins/extract-ts/extract.ts +200 -0
  319. package/plugins/extract-ts/flows.ts +783 -0
  320. package/plugins/extract-ts/graphql.test.ts +73 -0
  321. package/plugins/extract-ts/graphql.ts +181 -0
  322. package/plugins/extract-ts/ids.test.ts +21 -0
  323. package/plugins/extract-ts/ids.ts +52 -0
  324. package/plugins/extract-ts/lifecycle.ts +138 -0
  325. package/plugins/extract-ts/main.ts +48 -0
  326. package/plugins/extract-ts/openapi.test.ts +64 -0
  327. package/plugins/extract-ts/openapi.ts +99 -0
  328. package/plugins/extract-ts/operations.ts +93 -0
  329. package/plugins/extract-ts/options.schema.json +96 -0
  330. package/plugins/extract-ts/source.test.ts +174 -0
  331. package/plugins/extract-ts/source.ts +465 -0
  332. package/plugins/extract-ts/transport.ts +147 -0
  333. package/plugins/extract-ts/tsconfig.json +13 -0
  334. package/plugins/extract-ts/wiring.ts +190 -0
  335. package/plugins/extract-watermill/describe.go +19 -0
  336. package/plugins/extract-watermill/describe_test.go +11 -0
  337. package/plugins/extract-watermill/extract.go +1228 -0
  338. package/plugins/extract-watermill/extract_test.go +234 -0
  339. package/plugins/extract-watermill/main.go +41 -0
  340. package/plugins/extract-watermill/options.schema.json +21 -0
  341. package/plugins/extract-wsdl/describe.go +19 -0
  342. package/plugins/extract-wsdl/describe_test.go +11 -0
  343. package/plugins/extract-wsdl/extract.go +148 -0
  344. package/plugins/extract-wsdl/extract_test.go +45 -0
  345. package/plugins/extract-wsdl/main.go +53 -0
  346. package/plugins/extract-wsdl/options.schema.json +59 -0
  347. package/plugins/fetch-bsr/auth.go +114 -0
  348. package/plugins/fetch-bsr/bsr.go +240 -0
  349. package/plugins/fetch-bsr/cache.go +66 -0
  350. package/plugins/fetch-bsr/describe.go +19 -0
  351. package/plugins/fetch-bsr/fetch.go +192 -0
  352. package/plugins/fetch-bsr/fetch_test.go +465 -0
  353. package/plugins/fetch-bsr/lock.go +71 -0
  354. package/plugins/fetch-bsr/main.go +117 -0
  355. package/plugins/fetch-bsr/options.schema.json +42 -0
  356. package/plugins/fetch-csr/auth.go +95 -0
  357. package/plugins/fetch-csr/cache.go +69 -0
  358. package/plugins/fetch-csr/describe.go +19 -0
  359. package/plugins/fetch-csr/describe_test.go +11 -0
  360. package/plugins/fetch-csr/fetch.go +254 -0
  361. package/plugins/fetch-csr/fetch_test.go +494 -0
  362. package/plugins/fetch-csr/lock.go +95 -0
  363. package/plugins/fetch-csr/main.go +122 -0
  364. package/plugins/fetch-csr/options.schema.json +42 -0
  365. package/plugins/fetch-csr/registry.go +209 -0
  366. package/plugins/fetch-git/cache.go +65 -0
  367. package/plugins/fetch-git/describe.go +19 -0
  368. package/plugins/fetch-git/describe_test.go +11 -0
  369. package/plugins/fetch-git/fetch.go +181 -0
  370. package/plugins/fetch-git/fetch_test.go +332 -0
  371. package/plugins/fetch-git/git.go +169 -0
  372. package/plugins/fetch-git/lock.go +72 -0
  373. package/plugins/fetch-git/main.go +127 -0
  374. package/plugins/fetch-git/offline.go +39 -0
  375. package/plugins/fetch-git/options.schema.json +42 -0
  376. package/plugins/fetch-git/pin.go +90 -0
  377. package/plugins/fetch-git/pin_test.go +74 -0
  378. package/plugins/gen-backstage/describe.go +17 -0
  379. package/plugins/gen-backstage/main.go +22 -0
  380. package/plugins/gen-backstage/options.schema.json +30 -0
  381. package/plugins/gen-backstage/plugin.go +473 -0
  382. package/plugins/gen-backstage/plugin_test.go +145 -0
  383. package/plugins/gen-backstage.wasm +0 -0
  384. package/plugins/gen-markdown/adr.go +162 -0
  385. package/plugins/gen-markdown/aggregate.go +362 -0
  386. package/plugins/gen-markdown/canonical.go +93 -0
  387. package/plugins/gen-markdown/context.go +90 -0
  388. package/plugins/gen-markdown/coverage_test.go +89 -0
  389. package/plugins/gen-markdown/describe.go +19 -0
  390. package/plugins/gen-markdown/describe_test.go +11 -0
  391. package/plugins/gen-markdown/external.go +71 -0
  392. package/plugins/gen-markdown/flow.go +277 -0
  393. package/plugins/gen-markdown/glossary.go +76 -0
  394. package/plugins/gen-markdown/glossary_test.go +148 -0
  395. package/plugins/gen-markdown/llms.go +302 -0
  396. package/plugins/gen-markdown/main.go +22 -0
  397. package/plugins/gen-markdown/markdown.go +254 -0
  398. package/plugins/gen-markdown/markdown_test.go +100 -0
  399. package/plugins/gen-markdown/module.go +107 -0
  400. package/plugins/gen-markdown/options.schema.json +16 -0
  401. package/plugins/gen-markdown/plugin.go +39 -0
  402. package/plugins/gen-markdown/quality_test.go +176 -0
  403. package/plugins/gen-markdown/redis_test.go +38 -0
  404. package/plugins/gen-markdown/render.go +344 -0
  405. package/plugins/gen-markdown/render_test.go +186 -0
  406. package/plugins/gen-markdown/service.go +456 -0
  407. package/plugins/gen-markdown/source.go +177 -0
  408. package/plugins/gen-markdown/store.go +201 -0
  409. package/plugins/gen-markdown.wasm +0 -0
  410. package/plugins/gen-mermaid/describe.go +17 -0
  411. package/plugins/gen-mermaid/main.go +22 -0
  412. package/plugins/gen-mermaid/options.schema.json +10 -0
  413. package/plugins/gen-mermaid/plugin.go +104 -0
  414. package/plugins/gen-mermaid/plugin_test.go +33 -0
  415. package/plugins/gen-mermaid.wasm +0 -0
  416. package/plugins/openapi/ids.go +261 -0
  417. package/plugins/openapi/ids_test.go +98 -0
  418. package/plugins/pyplugin/catalog.py +209 -0
  419. package/plugins/pyplugin/celery_conf.py +436 -0
  420. package/plugins/pyplugin/celery_tasks.py +76 -0
  421. package/plugins/pyplugin/kafka.py +570 -0
  422. package/plugins/pyplugin/names.py +38 -0
  423. package/plugins/pyplugin/protocol.py +79 -0
  424. package/plugins/pyplugin/source.py +285 -0
  425. package/plugins/verify-codeowners/describe.go +19 -0
  426. package/plugins/verify-codeowners/describe_test.go +11 -0
  427. package/plugins/verify-codeowners/main.go +75 -0
  428. package/plugins/verify-codeowners/match.go +85 -0
  429. package/plugins/verify-codeowners/match_test.go +47 -0
  430. package/plugins/verify-codeowners/options.schema.json +15 -0
  431. package/plugins/verify-codeowners/owners.go +164 -0
  432. package/plugins/verify-codeowners/owners_test.go +225 -0
  433. package/plugins/verify-codeowners/parse.go +90 -0
  434. package/plugins/verify-codeowners/parse_test.go +62 -0
  435. package/plugins/verify-otel/describe.go +19 -0
  436. package/plugins/verify-otel/describe_test.go +11 -0
  437. package/plugins/verify-otel/main.go +53 -0
  438. package/plugins/verify-otel/match.go +336 -0
  439. package/plugins/verify-otel/options.schema.json +30 -0
  440. package/plugins/verify-otel/otlp.go +200 -0
  441. package/plugins/verify-otel/verify.go +734 -0
  442. package/plugins/verify-otel/verify_test.go +460 -0
  443. package/portolan.json +1073 -0
  444. package/public/favicon.svg +12 -0
  445. package/schema/portolan.schema.json +2558 -0
  446. package/scripts/README.md +53 -0
  447. package/scripts/build-report.mjs +60 -0
  448. package/scripts/build-report.test.mjs +44 -0
  449. package/scripts/builtin-plugins.mjs +49 -0
  450. package/scripts/catalog-sources.mjs +67 -0
  451. package/scripts/diff.mjs +315 -0
  452. package/scripts/diff.test.mjs +151 -0
  453. package/scripts/forge-comment.mjs +144 -0
  454. package/scripts/forge-comment.test.mjs +151 -0
  455. package/scripts/forge-release.mjs +138 -0
  456. package/scripts/forge-release.test.mjs +106 -0
  457. package/scripts/forge.mjs +61 -0
  458. package/scripts/gen-likec4.mjs +854 -0
  459. package/scripts/gen-likec4.test.mjs +258 -0
  460. package/scripts/gen.mjs +603 -0
  461. package/scripts/generated-docs.test.mjs +66 -0
  462. package/scripts/local-api.mjs +1192 -0
  463. package/scripts/local-api.test.mjs +387 -0
  464. package/scripts/manifest.mjs +214 -0
  465. package/scripts/manifest.test.mjs +148 -0
  466. package/scripts/output-path.mjs +79 -0
  467. package/scripts/output-path.test.mjs +45 -0
  468. package/scripts/package-smoke.mjs +37 -0
  469. package/scripts/plugin-host.mjs +419 -0
  470. package/scripts/plugin-host.test.mjs +109 -0
  471. package/scripts/plugin-wasm-worker.mjs +34 -0
  472. package/scripts/run-builtin.mjs +81 -0
  473. package/scripts/schema.mjs +357 -0
  474. package/scripts/site-docs.mjs +295 -0
  475. package/scripts/site-docs.test.mjs +181 -0
  476. package/scripts/vendor-lock.mjs +58 -0
  477. package/scripts/vendor-lock.test.mjs +69 -0
  478. package/src/README.md +38 -0
  479. package/src/app/App.tsx +448 -0
  480. package/src/app/BranchPicker.test.ts +46 -0
  481. package/src/app/BranchPicker.tsx +153 -0
  482. package/src/app/Breadcrumbs.test.ts +38 -0
  483. package/src/app/Breadcrumbs.tsx +223 -0
  484. package/src/app/BuildStamp.tsx +146 -0
  485. package/src/app/CommandPalette.tsx +292 -0
  486. package/src/app/HashScroll.tsx +45 -0
  487. package/src/app/Sidebar.tsx +1742 -0
  488. package/src/app/TopBar.tsx +393 -0
  489. package/src/app/density.tsx +64 -0
  490. package/src/app/forge-access.test.ts +27 -0
  491. package/src/app/forge-access.tsx +80 -0
  492. package/src/app/panels.tsx +141 -0
  493. package/src/app/pins.tsx +242 -0
  494. package/src/app/query-client.ts +24 -0
  495. package/src/app/responsive.ts +55 -0
  496. package/src/app/shortcuts.test.ts +39 -0
  497. package/src/app/shortcuts.tsx +282 -0
  498. package/src/app/theme.tsx +51 -0
  499. package/src/app/title.test.ts +14 -0
  500. package/src/app/title.ts +30 -0
  501. package/src/app/toast.tsx +59 -0
  502. package/src/app/tree-keys.test.ts +80 -0
  503. package/src/app/tree-keys.ts +87 -0
  504. package/src/app/ui-store.ts +52 -0
  505. package/src/catalog-error.test.ts +105 -0
  506. package/src/catalog-profile.test.ts +59 -0
  507. package/src/catalog-profile.ts +101 -0
  508. package/src/catalog-stores.test.ts +431 -0
  509. package/src/catalog.test.ts +1215 -0
  510. package/src/catalog.ts +3125 -0
  511. package/src/chat/Cards.tsx +408 -0
  512. package/src/chat/ChatPanel.tsx +79 -0
  513. package/src/chat/ChatSettings.tsx +219 -0
  514. package/src/chat/Composer.tsx +99 -0
  515. package/src/chat/Conversation.tsx +281 -0
  516. package/src/chat/Header.tsx +46 -0
  517. package/src/chat/Message.tsx +49 -0
  518. package/src/chat/Notice.tsx +195 -0
  519. package/src/chat/Starter.tsx +67 -0
  520. package/src/chat/Steps.tsx +45 -0
  521. package/src/chat/Waiting.tsx +87 -0
  522. package/src/chat/flags.test.ts +59 -0
  523. package/src/chat/flags.ts +155 -0
  524. package/src/chat/linkify.test.ts +30 -0
  525. package/src/chat/linkify.ts +45 -0
  526. package/src/chat/prefs.ts +56 -0
  527. package/src/chat/prompt.test.ts +43 -0
  528. package/src/chat/prompt.ts +157 -0
  529. package/src/chat/store.ts +35 -0
  530. package/src/chat/tools.ts +58 -0
  531. package/src/chat/transport.ts +70 -0
  532. package/src/components/AdrRow.tsx +29 -0
  533. package/src/components/AnchorLink.tsx +67 -0
  534. package/src/components/ApiReference.tsx +134 -0
  535. package/src/components/AsyncApiReference.tsx +120 -0
  536. package/src/components/CatalogStamp.tsx +155 -0
  537. package/src/components/ChannelRows.tsx +132 -0
  538. package/src/components/CommandRows.tsx +139 -0
  539. package/src/components/DiagramSkeleton.tsx +71 -0
  540. package/src/components/EditorLink.tsx +85 -0
  541. package/src/components/FieldTree.tsx +305 -0
  542. package/src/components/FlowTrigger.tsx +34 -0
  543. package/src/components/Ident.tsx +89 -0
  544. package/src/components/Integrations.tsx +243 -0
  545. package/src/components/LifecycleDiagram.tsx +110 -0
  546. package/src/components/MachineDocs.tsx +42 -0
  547. package/src/components/Markdown.tsx +173 -0
  548. package/src/components/MarkdownTable.tsx +206 -0
  549. package/src/components/Mermaid.tsx +211 -0
  550. package/src/components/MethodRows.tsx +309 -0
  551. package/src/components/Overlay.tsx +125 -0
  552. package/src/components/PageHeader.tsx +189 -0
  553. package/src/components/RowActions.tsx +97 -0
  554. package/src/components/SchemaDocument.tsx +89 -0
  555. package/src/components/Select.tsx +126 -0
  556. package/src/components/ShapeRows.tsx +118 -0
  557. package/src/components/SourceDoc.tsx +82 -0
  558. package/src/components/SourcePreview.tsx +441 -0
  559. package/src/components/TabRow.tsx +182 -0
  560. package/src/components/TechIcon.tsx +34 -0
  561. package/src/components/Toc.tsx +132 -0
  562. package/src/components/WhatLinksHere.tsx +273 -0
  563. package/src/components/WsdlReference.tsx +84 -0
  564. package/src/components/ddd-icons.tsx +107 -0
  565. package/src/components/kind.tsx +157 -0
  566. package/src/components/logo.tsx +61 -0
  567. package/src/components/primitives.tsx +264 -0
  568. package/src/data.test.ts +18 -0
  569. package/src/data.ts +202 -0
  570. package/src/enrich.test.ts +1150 -0
  571. package/src/enrich.ts +672 -0
  572. package/src/er/ErCanvas.tsx +533 -0
  573. package/src/er/RedisSchema.tsx +74 -0
  574. package/src/er/RelationCard.tsx +267 -0
  575. package/src/er/StoreHeader.tsx +155 -0
  576. package/src/er/TableNode.tsx +84 -0
  577. package/src/er/ViewNode.tsx +83 -0
  578. package/src/er/layout.test.ts +115 -0
  579. package/src/er/layout.ts +77 -0
  580. package/src/er/lineage.test.ts +140 -0
  581. package/src/er/lineage.ts +107 -0
  582. package/src/er/markers.tsx +79 -0
  583. package/src/er/spec.test.ts +344 -0
  584. package/src/er/spec.ts +426 -0
  585. package/src/flow/ChainList.tsx +162 -0
  586. package/src/flow/FlowTable.tsx +123 -0
  587. package/src/flow/FlowToolbar.tsx +372 -0
  588. package/src/flow/StepDetail.tsx +380 -0
  589. package/src/flow/StepRail.tsx +428 -0
  590. package/src/flow/answers.test.ts +50 -0
  591. package/src/flow/answers.ts +58 -0
  592. package/src/flow/chain.test.ts +383 -0
  593. package/src/flow/chain.ts +353 -0
  594. package/src/flow/chapters.test.ts +185 -0
  595. package/src/flow/chapters.ts +225 -0
  596. package/src/flow/continues.test.ts +102 -0
  597. package/src/flow/continues.ts +65 -0
  598. package/src/flow/cross-context.test.ts +97 -0
  599. package/src/flow/cross-context.ts +35 -0
  600. package/src/flow/mermaid.test.ts +22 -0
  601. package/src/flow/mermaid.ts +141 -0
  602. package/src/flow/outline.test.ts +212 -0
  603. package/src/flow/outline.ts +170 -0
  604. package/src/flow/paths.test.ts +129 -0
  605. package/src/flow/paths.ts +147 -0
  606. package/src/flow/prefs.test.ts +80 -0
  607. package/src/flow/prefs.ts +104 -0
  608. package/src/graph/DependencyGraph.tsx +283 -0
  609. package/src/graph/DependencyNodes.tsx +162 -0
  610. package/src/graph/EdgeLabel.tsx +61 -0
  611. package/src/graph/FocusedEventGraph.tsx +211 -0
  612. package/src/graph/GraphToolbar.tsx +187 -0
  613. package/src/graph/RoutedEdge.tsx +80 -0
  614. package/src/graph/dependency-layout.test.ts +360 -0
  615. package/src/graph/dependency-layout.ts +444 -0
  616. package/src/graph/elk.test.ts +70 -0
  617. package/src/graph/elk.ts +287 -0
  618. package/src/graph/nodes.tsx +97 -0
  619. package/src/graph/theme.ts +81 -0
  620. package/src/graph/useElkFlow.ts +125 -0
  621. package/src/index.css +1547 -0
  622. package/src/language/InLanguage.tsx +40 -0
  623. package/src/language/TermCard.tsx +132 -0
  624. package/src/language/cards.test.ts +114 -0
  625. package/src/language/cards.ts +111 -0
  626. package/src/lib/admonition.test.ts +49 -0
  627. package/src/lib/admonition.ts +48 -0
  628. package/src/lib/adr.test.ts +131 -0
  629. package/src/lib/adr.ts +68 -0
  630. package/src/lib/api.test.ts +122 -0
  631. package/src/lib/api.ts +135 -0
  632. package/src/lib/backlinks.test.ts +330 -0
  633. package/src/lib/backlinks.ts +567 -0
  634. package/src/lib/branch-compare.ts +16 -0
  635. package/src/lib/build-info.test.ts +76 -0
  636. package/src/lib/build-info.ts +80 -0
  637. package/src/lib/catalog-diff.test.ts +275 -0
  638. package/src/lib/catalog-diff.ts +655 -0
  639. package/src/lib/clipboard.ts +43 -0
  640. package/src/lib/comparison-memory.test.ts +40 -0
  641. package/src/lib/comparison-memory.ts +39 -0
  642. package/src/lib/context-color.ts +27 -0
  643. package/src/lib/context-map.test.ts +148 -0
  644. package/src/lib/context-map.ts +399 -0
  645. package/src/lib/data-model.test.ts +245 -0
  646. package/src/lib/data-model.ts +399 -0
  647. package/src/lib/data-problems.test.ts +178 -0
  648. package/src/lib/data-problems.ts +314 -0
  649. package/src/lib/derive.test.ts +251 -0
  650. package/src/lib/derive.ts +515 -0
  651. package/src/lib/editor-link.test.ts +125 -0
  652. package/src/lib/editor-link.ts +96 -0
  653. package/src/lib/editor-prefs.ts +27 -0
  654. package/src/lib/event-graph.test.ts +237 -0
  655. package/src/lib/event-graph.ts +327 -0
  656. package/src/lib/export-canvas.ts +37 -0
  657. package/src/lib/export-file.test.ts +30 -0
  658. package/src/lib/export-file.ts +20 -0
  659. package/src/lib/flow-tree.test.ts +177 -0
  660. package/src/lib/flow-tree.ts +160 -0
  661. package/src/lib/forge-refs.test.ts +50 -0
  662. package/src/lib/forge-refs.ts +60 -0
  663. package/src/lib/format.test.ts +35 -0
  664. package/src/lib/format.ts +64 -0
  665. package/src/lib/github-catalog.test.ts +301 -0
  666. package/src/lib/github-catalog.ts +610 -0
  667. package/src/lib/integrations.test.ts +119 -0
  668. package/src/lib/integrations.ts +133 -0
  669. package/src/lib/kinds.test.ts +91 -0
  670. package/src/lib/kinds.ts +204 -0
  671. package/src/lib/lifecycle.test.ts +93 -0
  672. package/src/lib/lifecycle.ts +165 -0
  673. package/src/lib/local-api.ts +194 -0
  674. package/src/lib/motion-features.ts +11 -0
  675. package/src/lib/motion.test.ts +120 -0
  676. package/src/lib/motion.tsx +269 -0
  677. package/src/lib/palette-recent.test.ts +80 -0
  678. package/src/lib/palette-recent.ts +90 -0
  679. package/src/lib/palette.test.ts +365 -0
  680. package/src/lib/palette.ts +593 -0
  681. package/src/lib/pins.test.ts +139 -0
  682. package/src/lib/pins.ts +130 -0
  683. package/src/lib/problems.test.ts +112 -0
  684. package/src/lib/project-preview.ts +5 -0
  685. package/src/lib/proto-problems.test.ts +121 -0
  686. package/src/lib/proto-problems.ts +65 -0
  687. package/src/lib/queries.test.ts +113 -0
  688. package/src/lib/queries.ts +131 -0
  689. package/src/lib/registry.test.ts +219 -0
  690. package/src/lib/registry.ts +208 -0
  691. package/src/lib/scenarios.ts +547 -0
  692. package/src/lib/setup-info.test.ts +199 -0
  693. package/src/lib/setup-info.ts +415 -0
  694. package/src/lib/shape.test.ts +289 -0
  695. package/src/lib/shape.ts +454 -0
  696. package/src/lib/sidebar-prefs.test.ts +32 -0
  697. package/src/lib/sidebar-prefs.ts +75 -0
  698. package/src/lib/source-code.test.ts +159 -0
  699. package/src/lib/source-code.ts +286 -0
  700. package/src/lib/source-doc.test.ts +131 -0
  701. package/src/lib/source-doc.ts +103 -0
  702. package/src/lib/source-highlight.test.ts +29 -0
  703. package/src/lib/source-highlight.ts +24 -0
  704. package/src/lib/source-link.test.ts +153 -0
  705. package/src/lib/source-link.ts +201 -0
  706. package/src/lib/spec-files.test.ts +24 -0
  707. package/src/lib/spec-files.ts +23 -0
  708. package/src/lib/table-directive.test.ts +139 -0
  709. package/src/lib/table-directive.ts +106 -0
  710. package/src/lib/tech.test.ts +120 -0
  711. package/src/lib/tech.ts +421 -0
  712. package/src/lib/terms.test.ts +67 -0
  713. package/src/lib/terms.ts +179 -0
  714. package/src/lib/wire-problems.test.ts +263 -0
  715. package/src/lib/wire-problems.ts +281 -0
  716. package/src/likec4/C4View.tsx +55 -0
  717. package/src/likec4/CanvasBridge.tsx +187 -0
  718. package/src/likec4/FlowView.tsx +235 -0
  719. package/src/likec4/InteractiveView.tsx +144 -0
  720. package/src/likec4/canvas-viewport.test.ts +149 -0
  721. package/src/likec4/canvas-viewport.ts +135 -0
  722. package/src/likec4/flow-edges.test.ts +51 -0
  723. package/src/likec4/flow-edges.ts +89 -0
  724. package/src/likec4/frame-theme.test.ts +76 -0
  725. package/src/likec4/frame-theme.ts +101 -0
  726. package/src/likec4/highlight-css.test.ts +49 -0
  727. package/src/likec4/highlight-css.ts +52 -0
  728. package/src/likec4/ids.test.ts +151 -0
  729. package/src/likec4/ids.ts +115 -0
  730. package/src/likec4/levels.tsx +70 -0
  731. package/src/likec4/mapping.test.ts +66 -0
  732. package/src/likec4/mapping.ts +54 -0
  733. package/src/likec4/reserved.json +141 -0
  734. package/src/likec4/view-index.ts +78 -0
  735. package/src/main.tsx +13 -0
  736. package/src/map/ContextMapGraph.tsx +274 -0
  737. package/src/map/ContextNode.tsx +70 -0
  738. package/src/map/FloatingEdge.tsx +86 -0
  739. package/src/merge.test.ts +1038 -0
  740. package/src/merge.ts +1004 -0
  741. package/src/pages/AdrDetail.tsx +242 -0
  742. package/src/pages/AdrIndex.tsx +114 -0
  743. package/src/pages/AggregatePage.tsx +727 -0
  744. package/src/pages/BlockPage.tsx +478 -0
  745. package/src/pages/CatalogFailure.tsx +111 -0
  746. package/src/pages/Changes.tsx +313 -0
  747. package/src/pages/ContextMap.tsx +534 -0
  748. package/src/pages/ContextPage.tsx +346 -0
  749. package/src/pages/EnumPage.tsx +282 -0
  750. package/src/pages/EventPage.tsx +686 -0
  751. package/src/pages/ExternalPage.tsx +204 -0
  752. package/src/pages/FlowDetail.tsx +663 -0
  753. package/src/pages/FlowIndex.tsx +303 -0
  754. package/src/pages/GraphPage.tsx +217 -0
  755. package/src/pages/Language.tsx +257 -0
  756. package/src/pages/ModulePage.tsx +366 -0
  757. package/src/pages/NotFound.tsx +54 -0
  758. package/src/pages/Overview.tsx +344 -0
  759. package/src/pages/Problems.tsx +360 -0
  760. package/src/pages/RegistryIndex.tsx +118 -0
  761. package/src/pages/ServicePage.tsx +746 -0
  762. package/src/pages/Settings.tsx +975 -0
  763. package/src/pages/StorePage.tsx +114 -0
  764. package/src/peek/PeekCard.test.ts +35 -0
  765. package/src/peek/PeekCard.tsx +118 -0
  766. package/src/peek/PeekLayer.tsx +147 -0
  767. package/src/peek/model.test.ts +156 -0
  768. package/src/peek/model.ts +253 -0
  769. package/src/routes.test.ts +222 -0
  770. package/src/routes.ts +449 -0
  771. package/src/selection/DetailPanel.tsx +1407 -0
  772. package/src/selection/hash.test.ts +73 -0
  773. package/src/selection/hash.ts +58 -0
  774. package/src/selection/model.test.ts +199 -0
  775. package/src/selection/model.ts +473 -0
  776. package/src/selection/pages.test.ts +115 -0
  777. package/src/selection/pages.ts +268 -0
  778. package/src/selection/store.test.ts +62 -0
  779. package/src/selection/store.ts +46 -0
  780. package/src/selection/sync.tsx +99 -0
  781. package/src/table/ColumnsMenu.tsx +72 -0
  782. package/src/table/DataTable.tsx +780 -0
  783. package/src/table/Facets.tsx +54 -0
  784. package/src/table/GroupMenu.tsx +69 -0
  785. package/src/table/Toolbar.tsx +190 -0
  786. package/src/table/cells.tsx +117 -0
  787. package/src/table/compare.test.ts +117 -0
  788. package/src/table/compare.ts +196 -0
  789. package/src/table/export.test.ts +88 -0
  790. package/src/table/export.ts +66 -0
  791. package/src/table/facet-groups.test.ts +74 -0
  792. package/src/table/facet-groups.ts +56 -0
  793. package/src/table/grouping.test.ts +84 -0
  794. package/src/table/grouping.ts +82 -0
  795. package/src/table/infer.test.ts +57 -0
  796. package/src/table/infer.ts +50 -0
  797. package/src/table/persist.test.ts +76 -0
  798. package/src/table/persist.ts +112 -0
  799. package/src/table/sort-url.test.ts +75 -0
  800. package/src/table/sort-url.ts +52 -0
  801. package/src/table/types.ts +80 -0
  802. package/src/table/useTableState.ts +220 -0
  803. package/src/test-catalog.ts +7 -0
  804. package/src/testing/estate/data__catalog.json +2847 -0
  805. package/src/testing/estate/data__flows.json +1724 -0
  806. package/src/testing/estate/examples__auth__portolan__api.json +212 -0
  807. package/src/testing/estate/examples__auth__portolan__domain.json +1204 -0
  808. package/src/testing/estate/examples__auth__portolan__glossary.json +226 -0
  809. package/src/testing/estate/examples__auth__portolan__observed.json +757 -0
  810. package/src/testing/estate/examples__auth__portolan__stores.json +195 -0
  811. package/src/testing/estate/examples__shop__cart__portolan__api.json +234 -0
  812. package/src/testing/estate/examples__shop__cart__portolan__commands.json +86 -0
  813. package/src/testing/estate/examples__shop__cart__portolan__domain.json +966 -0
  814. package/src/testing/estate/examples__shop__cart__portolan__observed.json +498 -0
  815. package/src/testing/estate/examples__shop__cart__portolan__stores.json +208 -0
  816. package/src/testing/estate/examples__shop__oms__portolan__glossary.json +98 -0
  817. package/src/testing/estate/examples__shop__oms__portolan__proto.json +186 -0
  818. package/src/testing/estate.ts +49 -0
  819. package/src/trail/Trail.tsx +81 -0
  820. package/src/trail/model.test.ts +157 -0
  821. package/src/trail/model.ts +189 -0
  822. package/src/trail/record.tsx +32 -0
  823. package/src/trail/store.test.ts +66 -0
  824. package/src/trail/store.ts +102 -0
  825. package/tsconfig.json +35 -0
  826. package/vite.config.ts +171 -0
@@ -0,0 +1,663 @@
1
+ import { useDocumentTitle } from "../app/title";
2
+ import {
3
+ useCallback,
4
+ useEffect,
5
+ useLayoutEffect,
6
+ useMemo,
7
+ useRef,
8
+ useState,
9
+ } from "react";
10
+ import { useParams } from "react-router";
11
+ import { ChevronLeft, ChevronRight } from "lucide-react";
12
+ import { saveCanvasImage } from "../lib/export-canvas";
13
+ import { allRepos, flowContexts, walkSteps } from "../catalog";
14
+ import type { Flow, Status, Step } from "../catalog";
15
+ import { catalog, index } from "../data";
16
+ import { contextName, ctxStyle } from "../lib/context-color";
17
+ import { middleTruncate } from "../lib/format";
18
+ import { toClipboard } from "../lib/clipboard";
19
+ import { flowRepoService } from "../lib/derive";
20
+ import { statusCounts } from "../lib/flow-tree";
21
+ import { sourceLocation } from "../lib/source-link";
22
+ import { EditorLink } from "../components/EditorLink";
23
+ import { SourcePreviewButton } from "../components/SourcePreview";
24
+ import { flowAnswers } from "../flow/answers";
25
+ import { flowMermaid } from "../flow/mermaid";
26
+ import { useToastStore } from "../app/toast";
27
+ import {
28
+ contextResolver,
29
+ hiddenStepIds,
30
+ isCrossContext,
31
+ } from "../flow/cross-context";
32
+ import { StepRail } from "../flow/StepRail";
33
+ import { FlowTable } from "../flow/FlowTable";
34
+ import { FlowToolbar } from "../flow/FlowToolbar";
35
+ import { buildChapters, groupRows } from "../flow/chapters";
36
+ import { continuationIndex } from "../flow/continues";
37
+ import { useFlowPrefs } from "../flow/prefs";
38
+ import { buildOutline, outlineSteps } from "../flow/outline";
39
+ import { findPath, flowPaths } from "../flow/paths";
40
+ import { FlowView } from "../likec4/FlowView";
41
+ import type { CanvasHandle } from "../likec4/CanvasBridge";
42
+ import { flowCrossViewId, flowViewId } from "../likec4/ids";
43
+ import { flowPairing } from "../likec4/view-index";
44
+ import { flowStepId, parseFlowStepId } from "../selection/model";
45
+ import { useSelectionStore } from "../selection/store";
46
+ import { PinButton } from "../app/pins";
47
+ import {
48
+ Panel,
49
+ ResizeHandle,
50
+ SavedGroup,
51
+ useCanvasResize,
52
+ } from "../app/panels";
53
+ import { Ident } from "../components/Ident";
54
+ import { ContextPill } from "../components/primitives";
55
+ import { WhatLinksHere } from "../components/WhatLinksHere";
56
+ import { FlowTrigger } from "../components/FlowTrigger";
57
+ import { NotFound } from "./NotFound";
58
+
59
+ /**
60
+ * The summary, clamped to two lines.
61
+ *
62
+ * The header's whole job on this page is to be short: every row it keeps is a
63
+ * row of canvas the reader does not get. Two lines is enough to say what a
64
+ * flow is; the rest is one word away, and the word is remembered per flow
65
+ * because whether a summary is worth ten lines is a fact about that summary.
66
+ */
67
+ function Summary({
68
+ text,
69
+ expanded,
70
+ onToggle,
71
+ }: {
72
+ text: string;
73
+ expanded: boolean;
74
+ onToggle: () => void;
75
+ }) {
76
+ const ref = useRef<HTMLParagraphElement | null>(null);
77
+ const [clipped, setClipped] = useState(false);
78
+
79
+ useLayoutEffect(() => {
80
+ const el = ref.current;
81
+ if (!el) return;
82
+ const measure = (): void => {
83
+ // Measured while clamped, which is the only state the question makes
84
+ // sense in: an expanded paragraph never overflows itself.
85
+ if (!expanded) setClipped(el.scrollHeight > el.clientHeight + 1);
86
+ };
87
+ measure();
88
+ window.addEventListener("resize", measure);
89
+ return () => window.removeEventListener("resize", measure);
90
+ }, [text, expanded]);
91
+
92
+ return (
93
+ <p className="mt-1.5 flex max-w-prose items-baseline gap-1.5 text-muted">
94
+ <span ref={ref} className={expanded ? "min-w-0" : "min-w-0 line-clamp-2"}>
95
+ {text}
96
+ </span>
97
+ {clipped || expanded ? (
98
+ <button
99
+ type="button"
100
+ onClick={onToggle}
101
+ className="mono shrink-0 rounded-control text-accent hover:underline"
102
+ >
103
+ {expanded ? "less" : "more"}
104
+ </button>
105
+ ) : null}
106
+ </p>
107
+ );
108
+ }
109
+
110
+ export function FlowDetail() {
111
+ const { flow: slug } = useParams();
112
+ const flow: Flow | undefined = slug ? index.flowBySlug.get(slug) : undefined;
113
+
114
+ const [crossOnly, setCrossOnly] = useState(false);
115
+ const [compact, setCompact] = useState(false);
116
+ /** The one status the rail is reading, or null for all of them. */
117
+ const [statusFilter, setStatusFilter] = useState<Status | null>(null);
118
+ const [exporting, setExporting] = useState(false);
119
+ const say = useToastStore((s) => s.say);
120
+ /** Empty means every branch at once — the union, not a run. */
121
+ const [pathId, setPathId] = useState("");
122
+ /** Chapters the reader has folded away. Empty means the whole flow is open. */
123
+ const [collapsed, setCollapsed] = useState<ReadonlySet<string>>(new Set());
124
+ const [hoverStep, setHoverStep] = useState<string | null>(null);
125
+ /** The step LikeC4's walkthrough is on, when it is running. */
126
+ const [walkStep, setWalkStep] = useState<string | null>(null);
127
+
128
+ const canvas = useRef<CanvasHandle | null>(null);
129
+
130
+ const selection = useSelectionStore((s) => s.selection);
131
+ const source = useSelectionStore((s) => s.source);
132
+ const select = useSelectionStore((s) => s.select);
133
+ const settle = useCanvasResize();
134
+
135
+ const allSteps = useMemo(() => (flow ? walkSteps(flow.steps) : []), [flow]);
136
+ const stepById = useMemo(() => {
137
+ const m = new Map<string, Step>();
138
+ for (const s of allSteps) m.set(s.id, s);
139
+ return m;
140
+ }, [allSteps]);
141
+
142
+ const [prefs, setPrefs] = useFlowPrefs(slug ?? "", allSteps.length);
143
+
144
+ const contextOf = useMemo(
145
+ () => (flow ? contextResolver(flow) : () => null),
146
+ [flow],
147
+ );
148
+ const hidden = useMemo(
149
+ () => (flow ? hiddenStepIds(flow) : new Set<string>()),
150
+ [flow],
151
+ );
152
+ const paths = useMemo(
153
+ () => (flow ? flowPaths(flow) : { paths: [], truncated: false }),
154
+ [flow],
155
+ );
156
+ const path = useMemo(
157
+ () => (pathId ? findPath(paths.paths, pathId) : null),
158
+ [paths, pathId],
159
+ );
160
+
161
+ // Frames are part of the rail, not something flattened out of it, so the
162
+ // outline is what the rail draws and what the arrow keys step through.
163
+ const statuses = useMemo(
164
+ () => (statusFilter ? new Set<Status>([statusFilter]) : null),
165
+ [statusFilter],
166
+ );
167
+ const rows = useMemo(
168
+ () =>
169
+ flow
170
+ ? buildOutline(flow, {
171
+ hidden,
172
+ crossOnly,
173
+ path: path ? path.stepIds : null,
174
+ statuses,
175
+ })
176
+ : [],
177
+ [flow, hidden, crossOnly, path, statuses],
178
+ );
179
+ /**
180
+ * What the canvas lifts: the chosen path, narrowed to the chosen status.
181
+ * A path also hides the other branches on the picture, while a status keeps
182
+ * them as context and lets them recede. Both still share one highlighted set
183
+ * on the canvas and remain separate controls on the rail.
184
+ */
185
+ const liftedSteps = useMemo(() => {
186
+ if (!path && !statusFilter) return null;
187
+ return allSteps
188
+ .filter(
189
+ (s) =>
190
+ (!path || path.stepIds.has(s.id)) &&
191
+ (!statusFilter || s.status === statusFilter),
192
+ )
193
+ .map((s) => s.id);
194
+ }, [allSteps, path, statusFilter]);
195
+ const counts = useMemo(
196
+ () =>
197
+ flow ? statusCounts(flow) : { verified: 0, declared: 0, unresolved: 0 },
198
+ [flow],
199
+ );
200
+ const walkable = useMemo(() => outlineSteps(rows), [rows]);
201
+
202
+ // Chapters come from the flow rather than from the filtered rail, so the
203
+ // cross-context switch cannot rename or renumber them.
204
+ const chapters = useMemo(() => (flow ? buildChapters(flow) : []), [flow]);
205
+ const groups = useMemo(() => groupRows(rows, chapters), [rows, chapters]);
206
+ const chapterOfStep = useMemo(() => {
207
+ const m = new Map<string, string>();
208
+ for (const chapter of chapters) {
209
+ for (const stepId of chapter.stepIds) m.set(stepId, chapter.id);
210
+ }
211
+ return m;
212
+ }, [chapters]);
213
+
214
+ const continuations = useMemo(
215
+ () => (flow ? continuationIndex(flow, catalog.flows) : new Map()),
216
+ [flow],
217
+ );
218
+
219
+ // What each step's callee hands back, read out of the contract rather than
220
+ // out of the flow: the rail says it beside the step, and the Mermaid copy
221
+ // draws it as a return.
222
+ const answers = useMemo(
223
+ () => (flow ? flowAnswers(index, flow) : new Map<string, string>()),
224
+ [flow],
225
+ );
226
+
227
+ const crossContextOf = useCallback(
228
+ (step: Step) =>
229
+ isCrossContext(step, contextOf) ? contextOf(step.to) : undefined,
230
+ [contextOf],
231
+ );
232
+
233
+ // --- what the selection means to this page -------------------------------
234
+
235
+ /** The step selected on this flow, if the selection is one. */
236
+ const selectedStepId = useMemo(() => {
237
+ if (!flow || selection?.kind !== "flow-step") return null;
238
+ const parsed = parseFlowStepId(selection.id);
239
+ if (!parsed || parsed.flowSlug !== flow.slug) return null;
240
+ return stepById.has(parsed.stepId) ? parsed.stepId : null;
241
+ }, [flow, selection, stepById]);
242
+
243
+ /**
244
+ * An event selected anywhere at all — sidebar, palette, another panel — lights
245
+ * every step of this flow that carries it.
246
+ */
247
+ const matches = useMemo(() => {
248
+ if (selection?.kind !== "event") return [];
249
+ return allSteps.filter((s) => s.ref === selection.id).map((s) => s.id);
250
+ }, [selection, allSteps]);
251
+
252
+ const [matchAt, setMatchAt] = useState(0);
253
+ useEffect(() => setMatchAt(0), [selection?.id]);
254
+
255
+ const matchIds = useMemo(
256
+ () => (matches.length > 0 ? new Set(matches) : null),
257
+ [matches],
258
+ );
259
+ const focusedMatch = matches[Math.min(matchAt, matches.length - 1)] ?? null;
260
+ // Playback outranks the selection while it is running: it is the reader
261
+ // watching rather than pointing, and the rail's job is to follow.
262
+ const activeId = walkStep ?? selectedStepId ?? focusedMatch;
263
+
264
+ const litSteps = useMemo(
265
+ () =>
266
+ walkStep
267
+ ? [walkStep]
268
+ : selectedStepId
269
+ ? [selectedStepId]
270
+ : hoverStep
271
+ ? [hoverStep]
272
+ : matches,
273
+ [walkStep, selectedStepId, hoverStep, matches],
274
+ );
275
+
276
+ useEffect(() => {
277
+ // Choosing a path that does not contain the selected step would leave the
278
+ // selected step outside the focused rail. The selection is the newer
279
+ // intent, so the path filter yields to it rather than the other way round.
280
+ if (path && selectedStepId && !path.stepIds.has(selectedStepId)) {
281
+ setPathId("");
282
+ }
283
+ }, [path, selectedStepId]);
284
+
285
+ useEffect(() => {
286
+ // A step the reader is being sent to must be visible when they arrive, so
287
+ // its chapter opens itself. Folding is a reading aid, not a filter, and it
288
+ // never gets to hide the one row the page is pointing at.
289
+ if (!activeId) return;
290
+ const chapterId = chapterOfStep.get(activeId);
291
+ if (!chapterId) return;
292
+ setCollapsed((current) => {
293
+ if (!current.has(chapterId)) return current;
294
+ const next = new Set(current);
295
+ next.delete(chapterId);
296
+ return next;
297
+ });
298
+ }, [activeId, chapterOfStep]);
299
+
300
+ const toggleChapter = useCallback((chapterId: string) => {
301
+ setCollapsed((current) => {
302
+ const next = new Set(current);
303
+ if (next.has(chapterId)) next.delete(chapterId);
304
+ else next.add(chapterId);
305
+ return next;
306
+ });
307
+ }, []);
308
+
309
+ const selectStep = useCallback(
310
+ (stepId: string) => {
311
+ if (flow) select(flowStepId(flow.slug, stepId), "rail");
312
+ },
313
+ [flow, select],
314
+ );
315
+
316
+ // Arrow keys walk the rail. Stepping the picture itself is LikeC4's
317
+ // walkthrough, in the canvas, so there is only ever one animator.
318
+ const move = useCallback(
319
+ (delta: number) => {
320
+ if (walkable.length === 0) return;
321
+ const at = walkable.findIndex((item) => item.step.id === activeId);
322
+ const nextIndex =
323
+ at < 0
324
+ ? delta > 0
325
+ ? 0
326
+ : walkable.length - 1
327
+ : Math.min(walkable.length - 1, Math.max(0, at + delta));
328
+ const next = walkable[nextIndex];
329
+ if (next) selectStep(next.step.id);
330
+ },
331
+ [walkable, activeId, selectStep],
332
+ );
333
+
334
+ useEffect(() => {
335
+ const onKey = (e: KeyboardEvent) => {
336
+ // Arrow keys walk the rail only when nothing else has a claim on them.
337
+ // A focused resize handle resizes with the same keys, and a text field
338
+ // moves its caret; stealing either would break the widget the reader is
339
+ // actually standing in.
340
+ const target = e.target as HTMLElement | null;
341
+ if (
342
+ target &&
343
+ (target.tagName === "INPUT" ||
344
+ target.tagName === "TEXTAREA" ||
345
+ target.tagName === "SELECT" ||
346
+ target.isContentEditable ||
347
+ target.closest('[role="separator"]') !== null)
348
+ )
349
+ return;
350
+ if (e.key === "ArrowRight") {
351
+ e.preventDefault();
352
+ move(1);
353
+ } else if (e.key === "ArrowLeft") {
354
+ e.preventDefault();
355
+ move(-1);
356
+ }
357
+ };
358
+ window.addEventListener("keydown", onKey);
359
+ return () => window.removeEventListener("keydown", onKey);
360
+ }, [move]);
361
+
362
+ useDocumentTitle(flow?.name ?? "Flow not found");
363
+
364
+ if (!flow) return <NotFound kind="Flow" id={slug} />;
365
+
366
+ const contexts = flowContexts(flow);
367
+ const viewId = crossOnly ? flowCrossViewId(flow) : flowViewId(flow);
368
+ const hiddenCount = crossOnly ? hidden.size : 0;
369
+ const flowSource = flow.source
370
+ ? sourceLocation(flow.source, flowRepoService(catalog, flow), allRepos(catalog))
371
+ : null;
372
+
373
+ const copyMermaid = () => {
374
+ void toClipboard(flowMermaid(flow, answers)).then((ok) => {
375
+ say(ok ? "Mermaid sequence copied" : "could not reach the clipboard");
376
+ });
377
+ };
378
+
379
+ const exportImage = async (kind: "png" | "svg"): Promise<void> => {
380
+ const viewport = canvas.current
381
+ ?.node()
382
+ ?.querySelector<HTMLElement>(".react-flow__viewport");
383
+ if (!viewport) {
384
+ say("the canvas is not on screen; leave compact to save a picture");
385
+ return;
386
+ }
387
+ setExporting(true);
388
+ try {
389
+ const file = await saveCanvasImage(viewport, flow.slug, kind);
390
+ say(`${kind.toUpperCase()} downloaded — ${file}`);
391
+ } catch (cause) {
392
+ say(
393
+ `could not export ${kind.toUpperCase()}: ${cause instanceof Error ? cause.message : String(cause)}`,
394
+ );
395
+ } finally {
396
+ setExporting(false);
397
+ }
398
+ };
399
+
400
+ /**
401
+ * Steps are paired to the diagram's arrows by position, which holds only
402
+ * while the generator and the view walk the sequence the same way. When it
403
+ * stops holding the pairing is abandoned and highlighting silently dies —
404
+ * so it is said out loud here instead, because a picture that quietly stops
405
+ * answering is worse than one that admits it cannot.
406
+ */
407
+ const pairingBroken =
408
+ allSteps.length > 0 && flowPairing(flow, crossOnly).edgeOf.size === 0;
409
+
410
+ const cycle = (delta: number): void => {
411
+ if (matches.length === 0) return;
412
+ setMatchAt((n) => (n + delta + matches.length) % matches.length);
413
+ };
414
+
415
+ const rail = (
416
+ <>
417
+ <MatchPill
418
+ selection={selection}
419
+ matches={matches}
420
+ at={matchAt}
421
+ onCycle={cycle}
422
+ />
423
+ <StepRail
424
+ groups={groups}
425
+ answers={answers}
426
+ activeId={activeId}
427
+ matchIds={matchIds}
428
+ collapsed={collapsed}
429
+ onToggleChapter={toggleChapter}
430
+ onSelect={selectStep}
431
+ onHover={setHoverStep}
432
+ crossContextOf={crossContextOf}
433
+ continuations={continuations}
434
+ />
435
+ </>
436
+ );
437
+
438
+ return (
439
+ <div className="flex h-full min-h-0 flex-col">
440
+ <div className="hero shrink-0 border-b px-gutter py-2.5 border-line bg-canvas">
441
+ {/* A flow belongs to no single context, so the wash takes the first one
442
+ it crosses - the context it starts in. */}
443
+ <div aria-hidden className="hero-wash" style={ctxStyle(contexts[0])} />
444
+ <div className="flex flex-wrap items-baseline gap-x-3 gap-y-1">
445
+ <h1 className="text-md font-semibold" title={flow.name}>
446
+ {flow.name}
447
+ </h1>
448
+ <Ident value={flow.id} className="text-muted" />
449
+ <div className="ml-auto flex items-center gap-2">
450
+ <PinButton kind="flow" id={flow.id} label={flow.name} />
451
+ </div>
452
+ </div>
453
+
454
+ <Summary
455
+ text={flow.summary}
456
+ expanded={prefs.expanded}
457
+ onToggle={() => setPrefs({ expanded: !prefs.expanded })}
458
+ />
459
+
460
+ {/* Everything the reader might want to know ABOUT this flow, as one
461
+ wrapping row. Four separate rows of one chip each is four rows of
462
+ canvas spent on decoration — and on this page every row of header
463
+ is a row the picture does not get.
464
+
465
+ What the row does NOT carry is anything about the picture: the view
466
+ id and the filter's own count are facts about what is on the canvas
467
+ right now, and they live with the controls that changed them. */}
468
+ <div className="mt-1.5 flex flex-wrap items-center gap-x-2.5 gap-y-1.5">
469
+ {flow.trigger ? <FlowTrigger trigger={flow.trigger} /> : null}
470
+ {flow.includes && flow.includes.length > 0 ? (
471
+ <span
472
+ className="chip mono"
473
+ title={`Composed from:\n${flow.includes.join("\n")}`}
474
+ >
475
+ cross-protocol · {flow.includes.length + 1} fragments
476
+ </span>
477
+ ) : null}
478
+ {/* The file the flow was read out of, spelled in full rather than
479
+ named: the point of putting it here is that a reader can copy it
480
+ and go and look. */}
481
+ {flow.source ? (
482
+ <Ident value={flow.source} className="text-muted">
483
+ {middleTruncate(flow.source, 40)}
484
+ </Ident>
485
+ ) : null}
486
+ <SourcePreviewButton location={flowSource} />
487
+ {flowSource?.href ? (
488
+ <a
489
+ href={flowSource.href}
490
+ target="_blank"
491
+ rel="noreferrer"
492
+ className="mono rounded-control text-accent hover:underline"
493
+ title="Open the file on the forge, at the built commit"
494
+ >
495
+ open ↗
496
+ </a>
497
+ ) : null}
498
+ <EditorLink location={flowSource} variant="text" />
499
+ <WhatLinksHere
500
+ target={{ kind: "flow", id: flow.slug }}
501
+ variant="line"
502
+ className="mt-0"
503
+ />
504
+ {contexts.map((c) => (
505
+ <ContextPill key={c} id={c} name={contextName(c)} />
506
+ ))}
507
+ </div>
508
+
509
+ <FlowToolbar
510
+ variant={prefs.variant}
511
+ onVariant={(variant) => setPrefs({ variant })}
512
+ playing={walkStep !== null}
513
+ onPlay={() => canvas.current?.start()}
514
+ onStep={(delta) => canvas.current?.step(delta)}
515
+ onStop={() => canvas.current?.stop()}
516
+ onFit={() => canvas.current?.fit()}
517
+ paths={paths.paths}
518
+ pathId={pathId}
519
+ onPath={setPathId}
520
+ pathSteps={path ? path.stepIds.size : null}
521
+ totalSteps={allSteps.length}
522
+ crossOnly={crossOnly}
523
+ onCrossOnly={setCrossOnly}
524
+ compact={compact}
525
+ onCompact={setCompact}
526
+ viewId={viewId}
527
+ hiddenCount={hiddenCount}
528
+ pairingBroken={pairingBroken}
529
+ statusFilter={statusFilter}
530
+ onStatusFilter={setStatusFilter}
531
+ statusCounts={counts}
532
+ onCopyMermaid={copyMermaid}
533
+ onExportPng={() => void exportImage("png")}
534
+ onExportSvg={() => void exportImage("svg")}
535
+ exporting={exporting}
536
+ />
537
+ </div>
538
+
539
+ <div className="flex min-h-0 flex-1">
540
+ {compact ? (
541
+ <div className="pane min-w-0 flex-1 overflow-y-auto">
542
+ <FlowTable
543
+ flow={flow}
544
+ rows={walkable}
545
+ chapters={chapters}
546
+ contextOf={contextOf}
547
+ />
548
+ </div>
549
+ ) : (
550
+ /* The rail and the picture are two readings of the same sequence, so
551
+ which one gets the room is the reader's call, and it is remembered
552
+ separately from the detail panel's width. */
553
+ <SavedGroup
554
+ id="portolan:flow-canvas"
555
+ orientation="horizontal"
556
+ className="h-full min-h-0 flex-1"
557
+ >
558
+ <Panel
559
+ id="rail"
560
+ defaultSize="320px"
561
+ minSize="200px"
562
+ maxSize="45"
563
+ className="pane h-full overflow-y-auto border-r border-line"
564
+ >
565
+ {rail}
566
+ </Panel>
567
+
568
+ <ResizeHandle id="rail" />
569
+
570
+ <Panel
571
+ id="canvas"
572
+ minSize="30"
573
+ className="h-full min-w-0"
574
+ onResize={settle}
575
+ >
576
+ <FlowView
577
+ flow={flow}
578
+ crossOnly={crossOnly}
579
+ variant={prefs.variant}
580
+ litSteps={
581
+ litSteps.length > 0
582
+ ? litSteps
583
+ : statusFilter
584
+ ? (liftedSteps ?? [])
585
+ : []
586
+ }
587
+ pathSteps={path ? [...path.stepIds] : null}
588
+ /* What the READER is pointing at, which is not the same thing
589
+ as what the rail is marking: during playback the rail marks
590
+ the step being played, and feeding that back to the canvas
591
+ would have the page and the walkthrough each insisting on a
592
+ different step forever. A step clicked ON the canvas is left
593
+ out too — it is already in view, and re-centring would move
594
+ the picture out from under the pointer. */
595
+ focusStep={
596
+ source === "diagram" ? null : (selectedStepId ?? focusedMatch)
597
+ }
598
+ canvas={canvas}
599
+ onWalkthroughStep={setWalkStep}
600
+ />
601
+ </Panel>
602
+ </SavedGroup>
603
+ )}
604
+ </div>
605
+ </div>
606
+ );
607
+ }
608
+
609
+ /**
610
+ * How much of this flow the selected event actually touches. It sits above the
611
+ * rail because that is where the answer is read, and it cycles rather than
612
+ * scrolls so a flow with three matches forty steps apart is still walkable.
613
+ */
614
+ function MatchPill({
615
+ selection,
616
+ matches,
617
+ at,
618
+ onCycle,
619
+ }: {
620
+ selection: { kind: string; id: string } | null;
621
+ matches: string[];
622
+ at: number;
623
+ onCycle: (delta: number) => void;
624
+ }) {
625
+ if (selection?.kind !== "event") return null;
626
+
627
+ if (matches.length === 0) {
628
+ return (
629
+ <div className="pill-rail text-muted">
630
+ no step here carries this event
631
+ </div>
632
+ );
633
+ }
634
+
635
+ return (
636
+ <div className="pill-rail">
637
+ <span className="text-accent">
638
+ {matches.length} matching step{matches.length === 1 ? "" : "s"}
639
+ </span>
640
+ <span className="ml-auto text-muted">
641
+ {Math.min(at, matches.length - 1) + 1}/{matches.length}
642
+ </span>
643
+ <span className="seg">
644
+ <button
645
+ type="button"
646
+ onClick={() => onCycle(-1)}
647
+ aria-label="Previous matching step"
648
+ className="!px-1"
649
+ >
650
+ <ChevronLeft size={14} aria-hidden />
651
+ </button>
652
+ <button
653
+ type="button"
654
+ onClick={() => onCycle(1)}
655
+ aria-label="Next matching step"
656
+ className="!px-1"
657
+ >
658
+ <ChevronRight size={14} aria-hidden />
659
+ </button>
660
+ </span>
661
+ </div>
662
+ );
663
+ }