@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,235 @@
1
+ import { useCallback, useMemo } from "react";
2
+ import type { RefObject } from "react";
3
+ import type { DiagramApi } from "likec4/react";
4
+ import type { Flow } from "../catalog";
5
+ import { walkSteps } from "../catalog";
6
+ import { InteractiveView } from "./InteractiveView";
7
+ import { CanvasBridge } from "./CanvasBridge";
8
+ import type { CanvasHandle } from "./CanvasBridge";
9
+ import { flowCrossViewId, flowViewId, fqn, participantFqn } from "./ids";
10
+ import { catalogIdOf } from "./mapping";
11
+ import {
12
+ flowPairing,
13
+ viewBounds,
14
+ viewEdgeIds,
15
+ viewHasNode,
16
+ viewNodeIds,
17
+ } from "./view-index";
18
+ import { offPathFrameIds } from "./flow-edges";
19
+ import {
20
+ buildFrameCss,
21
+ buildFocusedPathCss,
22
+ buildWalkthroughCss,
23
+ } from "./frame-theme";
24
+ import { centredViewport, readableViewport } from "./canvas-viewport";
25
+ import type { Size } from "./canvas-viewport";
26
+ import { flowStepId } from "../selection/model";
27
+ import { useSelectionStore } from "../selection/store";
28
+
29
+ /** A stable empty list, so `useMemo` below is not defeated by a fresh `[]`. */
30
+ const NOTHING: readonly string[] = [];
31
+
32
+ /**
33
+ * The flow picture. LikeC4 owns it end to end: the sequence is declared in
34
+ * likec4/views.c4, generated from the same catalog.json the rest of the page
35
+ * reads. Portolan draws nothing here itself — it says which steps are lit,
36
+ * where to look, and what colour the frames should be, and hears which step
37
+ * was clicked and which one playback is on.
38
+ */
39
+ export function FlowView({
40
+ flow,
41
+ crossOnly,
42
+ variant,
43
+ litSteps = [],
44
+ pathSteps = null,
45
+ focusStep = null,
46
+ canvas,
47
+ onWalkthroughStep,
48
+ }: {
49
+ flow: Flow;
50
+ crossOnly: boolean;
51
+ variant: "diagram" | "sequence";
52
+ /** Catalog step ids to mark; the rest of the sequence is dimmed. */
53
+ litSteps?: readonly string[];
54
+ /**
55
+ * Steps of the path being read, when one is chosen. Everything outside that
56
+ * executable path leaves the picture, matching the focused rail beside it.
57
+ */
58
+ pathSteps?: readonly string[] | null;
59
+ /** The step the canvas should bring into view, when the rail asks for one. */
60
+ focusStep?: string | null;
61
+ canvas: RefObject<CanvasHandle | null>;
62
+ /** The step playback is on, in catalog terms, or null when it is stopped. */
63
+ onWalkthroughStep: (stepId: string | null) => void;
64
+ }) {
65
+ const viewId = crossOnly ? flowCrossViewId(flow) : flowViewId(flow);
66
+ const pairing = flowPairing(flow, crossOnly);
67
+
68
+ const selection = useSelectionStore((s) => s.selection);
69
+ const source = useSelectionStore((s) => s.source);
70
+ const select = useSelectionStore((s) => s.select);
71
+ const clear = useSelectionStore((s) => s.clear);
72
+
73
+ const onNode = useCallback(
74
+ (likec4Id: string) => select(catalogIdOf(likec4Id), "diagram"),
75
+ [select],
76
+ );
77
+
78
+ // A clicked arrow is a step, not a relation: the sequence is what this view
79
+ // is about, so the click resolves through the edge-to-step pairing.
80
+ const onEdge = useCallback(
81
+ (edgeId: string) => {
82
+ const stepId = pairing.stepOf.get(edgeId);
83
+ if (stepId) select(flowStepId(flow.slug, stepId), "diagram");
84
+ },
85
+ [pairing, flow.slug, select],
86
+ );
87
+
88
+ const onCanvasClick = useCallback(() => clear("diagram"), [clear]);
89
+
90
+ const onWalkthroughEdge = useCallback(
91
+ (edgeId: string | null) => {
92
+ onWalkthroughStep(edgeId ? (pairing.stepOf.get(edgeId) ?? null) : null);
93
+ },
94
+ [pairing, onWalkthroughStep],
95
+ );
96
+
97
+ // A selection is narrower than a path, so it wins: choosing a path lifts the
98
+ // branch you are reading, and clicking a step then narrows to that one step.
99
+ const marked = litSteps.length > 0 ? litSteps : (pathSteps ?? NOTHING);
100
+
101
+ const highlightEdges = useMemo(
102
+ () =>
103
+ marked
104
+ .map((stepId) => pairing.edgeOf.get(stepId))
105
+ .filter((id): id is string => id !== undefined),
106
+ [marked, pairing],
107
+ );
108
+
109
+ const focusedPathEdges = useMemo(
110
+ () =>
111
+ pathSteps
112
+ ? pathSteps
113
+ .map((stepId) => pairing.edgeOf.get(stepId))
114
+ .filter((id): id is string => id !== undefined)
115
+ : null,
116
+ [pathSteps, pairing],
117
+ );
118
+
119
+ // Frames go with their steps. A chosen path is a structural focus, so its
120
+ // unused arrows and empty frames disappear; selecting one step remains a
121
+ // paint-only highlight and never changes the surrounding graph.
122
+ const extraCss = useMemo(() => {
123
+ const theme = [buildFrameCss(), buildWalkthroughCss()].join("\n");
124
+ if (!pathSteps) return theme;
125
+ const allEdges = viewEdgeIds(viewId);
126
+ const onPath = new Set(focusedPathEdges ?? []);
127
+ const edges = allEdges.filter((id) => !onPath.has(id));
128
+ const edgeLabelPositions = allEdges
129
+ .map((id, index) => (onPath.has(id) ? null : index + 1))
130
+ .filter((position): position is number => position !== null);
131
+ const pathStepIds = new Set(
132
+ pathSteps.filter((stepId) => pairing.edgeOf.has(stepId)),
133
+ );
134
+ const participantsById = new Map(
135
+ flow.participants.map((participant) => [
136
+ participant.id,
137
+ participantFqn(participant),
138
+ ]),
139
+ );
140
+ const onPathNodes = new Set(
141
+ walkSteps(flow.steps)
142
+ .filter((step) => pathStepIds.has(step.id))
143
+ .flatMap((step) => [step.from, step.to])
144
+ .map((id) => participantsById.get(id) ?? fqn(id)),
145
+ );
146
+ const nodes = viewNodeIds(viewId).filter((id) => !onPathNodes.has(id));
147
+ const frames = offPathFrameIds(allEdges, onPath);
148
+ return [
149
+ theme,
150
+ buildFocusedPathCss(edges, edgeLabelPositions, nodes, frames),
151
+ ]
152
+ .filter(Boolean)
153
+ .join("\n");
154
+ }, [flow, pathSteps, focusedPathEdges, pairing, viewId]);
155
+
156
+ // Lanes are marked only for a selection made somewhere else, and only when
157
+ // this variant of the view actually has that lane.
158
+ const highlightNodes =
159
+ selection &&
160
+ source !== "diagram" &&
161
+ (selection.kind === "service" || selection.kind === "unknown") &&
162
+ viewHasNode(viewId, fqn(selection.id))
163
+ ? [fqn(selection.id)]
164
+ : [];
165
+
166
+ /**
167
+ * The opening viewport, applied once the canvas is live.
168
+ *
169
+ * Both variants open at the size their labels were drawn for — fitting a
170
+ * flow of this length puts every label at four pixels, which shows that it
171
+ * is long and nothing else. What differs is where they open. A sequence has
172
+ * a beginning: its top-left corner is step one. A diagram does not — it
173
+ * folds forty-five steps onto nine boxes and has no reading order — so it
174
+ * opens centred on the arrow that draws step one instead.
175
+ *
176
+ * The second `send` is the point of the first one. LikeC4 refits on resize
177
+ * unless the viewport was moved by hand, and the page resizes the canvas
178
+ * itself whenever the rail is dragged — so a viewport set programmatically
179
+ * and left unclaimed would be thrown away the first time the reader touched
180
+ * the divider. Claiming it says: this is where the reader is looking.
181
+ */
182
+ const onReady = useCallback(
183
+ ({ diagram, canvas: box }: { diagram: DiagramApi; canvas: Size }) => {
184
+ if (box.width === 0) return;
185
+
186
+ let viewport;
187
+ if (variant === "sequence") {
188
+ const bounds = viewBounds(viewId, variant);
189
+ if (!bounds) return;
190
+ viewport = readableViewport(bounds, box);
191
+ } else {
192
+ const firstEdge = viewEdgeIds(viewId)[0];
193
+ const start = firstEdge
194
+ ? diagram.findDiagramEdge(firstEdge)?.points[0]
195
+ : undefined;
196
+ // No geometry to aim at is not worth a guess: LikeC4's own fit is
197
+ // already on screen, and leaving it beats jumping somewhere arbitrary.
198
+ if (!start) return;
199
+ viewport = centredViewport({ x: start[0], y: start[1] }, box);
200
+ }
201
+
202
+ diagram.send({ type: "xyflow.setViewport", viewport, duration: 0 });
203
+ diagram.send({ type: "xyflow.viewportMoved", viewport, manually: true });
204
+ },
205
+ [viewId, variant],
206
+ );
207
+
208
+ return (
209
+ <InteractiveView
210
+ viewId={viewId}
211
+ variant={variant}
212
+ // LikeC4's own controls panel carries a toolbar and a wordmark. The page
213
+ // already has one toolbar, and two is one too many, so the panel is off
214
+ // and every control it held has a place in ours.
215
+ controls={false}
216
+ walkthrough
217
+ highlightNodes={highlightNodes}
218
+ highlightEdges={highlightEdges}
219
+ extraCss={extraCss}
220
+ onNode={onNode}
221
+ onEdge={onEdge}
222
+ onCanvas={onCanvasClick}
223
+ onReady={onReady}
224
+ >
225
+ <CanvasBridge
226
+ handle={canvas}
227
+ activeEdgeId={
228
+ focusStep ? (pairing.edgeOf.get(focusStep) ?? null) : null
229
+ }
230
+ focusEdgeIds={focusedPathEdges}
231
+ onWalkthroughEdge={onWalkthroughEdge}
232
+ />
233
+ </InteractiveView>
234
+ );
235
+ }
@@ -0,0 +1,144 @@
1
+ import { useCallback, useMemo, useRef } from "react";
2
+ import type { ReactNode } from "react";
3
+ import type { DiagramApi } from "likec4/react";
4
+ import { ReactLikeC4, isLikeC4ViewId } from "./generated";
5
+ import { useTheme } from "../app/theme";
6
+ import { Ident } from "../components/Ident";
7
+ import { buildHighlightCss } from "./highlight-css";
8
+ import type { Size } from "./canvas-viewport";
9
+
10
+ export interface InteractiveViewProps {
11
+ viewId: string;
12
+ /** Sequence for flows, diagram for element views. */
13
+ variant?: "diagram" | "sequence";
14
+ controls?: boolean;
15
+ walkthrough?: boolean;
16
+ /** LikeC4 element ids to mark; everything else on the canvas is dimmed. */
17
+ highlightNodes?: readonly string[];
18
+ /** LikeC4 edge ids to mark; the other edges are dimmed. */
19
+ highlightEdges?: readonly string[];
20
+ /**
21
+ * More rules for the same stylesheet — retuned theme tokens, dimmed frames.
22
+ * Anything that paints rather than lays out belongs here rather than in a
23
+ * prop the layout would have to be recomputed for.
24
+ */
25
+ extraCss?: string;
26
+ onNode?: (likec4Id: string) => void;
27
+ onEdge?: (edgeId: string) => void;
28
+ onCanvas?: () => void;
29
+ /**
30
+ * Once the canvas is live, with the diagram's API and the size of the box it
31
+ * was given. The size is measured here because this is the component that
32
+ * owns the box; nothing inside LikeC4 will say how big it was made.
33
+ */
34
+ onReady?: (params: { diagram: DiagramApi; canvas: Size }) => void;
35
+ /**
36
+ * Rendered inside the canvas, where LikeC4's own hooks work. For bridges,
37
+ * not for decoration: anything drawn over the picture belongs beside it.
38
+ */
39
+ children?: ReactNode;
40
+ }
41
+
42
+ /**
43
+ * A view id that resolves to nothing. This is not an error the reader caused
44
+ * and not one they can fix from here, so it is stated rather than alarmed
45
+ * about: the id in monospace, because that is the thing to go and look for,
46
+ * and one muted line saying why the box is empty.
47
+ *
48
+ * The box keeps its size. A diagram that fails to exist must not also make the
49
+ * page around it change shape.
50
+ */
51
+ function Missing({ viewId }: { viewId: string }) {
52
+ return (
53
+ <div className="flex h-full items-center justify-center p-6">
54
+ <div className="max-w-prose rounded-card border border-dashed px-4 py-3 text-center border-line-strong">
55
+ <Ident value={viewId} className="text-ink" />
56
+ <p className="meta mt-1.5">
57
+ no such view in the generated bundle — it was renamed, or the bundle
58
+ is older than the model
59
+ </p>
60
+ </div>
61
+ </div>
62
+ );
63
+ }
64
+
65
+ /**
66
+ * The one place portolan embeds a LikeC4 canvas. The packaged LikeC4View gives
67
+ * no way to hear a click, so the lower-level component is used instead and the
68
+ * clicks are forwarded straight to the selection store by the caller.
69
+ */
70
+ export function InteractiveView({
71
+ viewId,
72
+ variant = "diagram",
73
+ controls = false,
74
+ walkthrough = false,
75
+ highlightNodes = [],
76
+ highlightEdges = [],
77
+ extraCss = "",
78
+ onNode,
79
+ onEdge,
80
+ onCanvas,
81
+ onReady,
82
+ children,
83
+ }: InteractiveViewProps) {
84
+ const { theme } = useTheme();
85
+ const boxRef = useRef<HTMLDivElement | null>(null);
86
+
87
+ // Keyed on contents rather than array identity: callers build these lists
88
+ // inline, so comparing by reference would rebuild the sheet every render.
89
+ const nodeKey = highlightNodes.join(" ");
90
+ const edgeKey = highlightEdges.join(" ");
91
+ const css = useMemo(() => {
92
+ const rules = buildHighlightCss(
93
+ nodeKey ? nodeKey.split(" ") : [],
94
+ edgeKey ? edgeKey.split(" ") : [],
95
+ );
96
+ return [extraCss, rules].filter(Boolean).join("\n");
97
+ }, [nodeKey, edgeKey, extraCss]);
98
+
99
+ const onInitialized = useCallback(
100
+ ({ diagram }: { diagram: DiagramApi }) => {
101
+ const box = boxRef.current?.getBoundingClientRect();
102
+ if (!box) return;
103
+ onReady?.({ diagram, canvas: { width: box.width, height: box.height } });
104
+ },
105
+ [onReady],
106
+ );
107
+
108
+ if (!isLikeC4ViewId(viewId)) return <Missing viewId={viewId} />;
109
+
110
+ return (
111
+ <div ref={boxRef} className="h-full w-full bg-canvas">
112
+ <ReactLikeC4
113
+ // Remounting on viewId keeps LikeC4's own walkthrough state from
114
+ // leaking across a filter or variant switch. The variant is in the key
115
+ // for a second reason: it is an initial value inside LikeC4, so the
116
+ // only honest way to change it is to start the canvas again. The
117
+ // selection is deliberately absent: re-keying would re-layout.
118
+ key={`${viewId}:${variant}:${theme}`}
119
+ viewId={viewId}
120
+ dynamicViewVariant={variant}
121
+ colorScheme={theme}
122
+ background="dots"
123
+ controls={controls}
124
+ fitView
125
+ pannable
126
+ zoomable
127
+ enableDynamicViewWalkthrough={walkthrough}
128
+ enableNotes
129
+ enableElementDetails={false}
130
+ enableRelationshipDetails={false}
131
+ enableSearch={false}
132
+ injectFontCss={false}
133
+ onNodeClick={(node) => onNode?.(String(node.id))}
134
+ onEdgeClick={(edge) => onEdge?.(String(edge.id))}
135
+ onCanvasClick={() => onCanvas?.()}
136
+ onInitialized={onInitialized}
137
+ style={{ width: "100%", height: "100%" }}
138
+ >
139
+ {css ? <style>{css}</style> : null}
140
+ {children}
141
+ </ReactLikeC4>
142
+ </div>
143
+ );
144
+ }
@@ -0,0 +1,149 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import {
3
+ CANVAS_PAD,
4
+ MIN_FOCUS_ZOOM,
5
+ READABLE_ZOOM,
6
+ boundsOf,
7
+ centredViewport,
8
+ focusViewport,
9
+ readableViewport,
10
+ } from "./canvas-viewport";
11
+
12
+ /** Where a point of the diagram lands on screen under a viewport. */
13
+ const project = (
14
+ value: number,
15
+ offset: number,
16
+ zoom: number,
17
+ ): number => value * zoom + offset;
18
+
19
+ describe("readableViewport", () => {
20
+ const canvas = { width: 1000, height: 700 };
21
+
22
+ it("opens at the authored size rather than at whatever fits", () => {
23
+ const huge = { x: 0, y: 0, width: 4000, height: 9000 };
24
+ expect(readableViewport(huge, canvas).zoom).toBe(READABLE_ZOOM);
25
+ });
26
+
27
+ it("puts the top of the diagram at the top of the canvas", () => {
28
+ const bounds = { x: 120, y: 60, width: 400, height: 3000 };
29
+ const viewport = readableViewport(bounds, canvas);
30
+ expect(project(bounds.y, viewport.y, viewport.zoom)).toBe(CANVAS_PAD);
31
+ });
32
+
33
+ it("centres a diagram narrower than the canvas", () => {
34
+ const bounds = { x: 40, y: 0, width: 400, height: 300 };
35
+ const viewport = readableViewport(bounds, canvas);
36
+ const left = project(bounds.x, viewport.x, viewport.zoom);
37
+ const right = project(bounds.x + bounds.width, viewport.x, viewport.zoom);
38
+ expect(left).toBeCloseTo(canvas.width - right, 0);
39
+ });
40
+
41
+ it("left-aligns a diagram wider than the canvas, so step one is on screen", () => {
42
+ const bounds = { x: 40, y: 0, width: 3000, height: 300 };
43
+ const viewport = readableViewport(bounds, canvas);
44
+ expect(project(bounds.x, viewport.x, viewport.zoom)).toBe(CANVAS_PAD);
45
+ });
46
+
47
+ it("does not centre a diagram that only just overflows", () => {
48
+ // Exactly canvas-wide still needs its padding, so it counts as overflow
49
+ // rather than as a fit with no room to breathe.
50
+ const bounds = { x: 0, y: 0, width: canvas.width, height: 300 };
51
+ expect(readableViewport(bounds, canvas).x).toBe(CANVAS_PAD);
52
+ });
53
+
54
+ it("honours a zoom the caller asks for", () => {
55
+ const bounds = { x: 0, y: 0, width: 400, height: 300 };
56
+ const viewport = readableViewport(bounds, canvas, 2);
57
+ expect(viewport.zoom).toBe(2);
58
+ expect(project(bounds.y, viewport.y, viewport.zoom)).toBe(CANVAS_PAD);
59
+ });
60
+
61
+ it("returns whole pixels, so nothing lands on a half-device-pixel", () => {
62
+ const bounds = { x: 3, y: 7, width: 333, height: 111 };
63
+ const viewport = readableViewport(bounds, canvas);
64
+ expect(Number.isInteger(viewport.x)).toBe(true);
65
+ expect(Number.isInteger(viewport.y)).toBe(true);
66
+ });
67
+ });
68
+
69
+ describe("boundsOf", () => {
70
+ it("has nothing to say about a line with no points", () => {
71
+ expect(boundsOf([])).toBeNull();
72
+ });
73
+
74
+ it("boxes a polyline, wherever its ends happen to be", () => {
75
+ // Deliberately not monotonic: an edge routed round a lane doubles back,
76
+ // and the box has to hold the detour rather than just the endpoints.
77
+ expect(
78
+ boundsOf([
79
+ [830, 1769],
80
+ [1245, 1510],
81
+ [1917, 1647],
82
+ ]),
83
+ ).toEqual({ x: 830, y: 1510, width: 1087, height: 259 });
84
+ });
85
+ });
86
+
87
+ describe("centredViewport", () => {
88
+ it("puts the point in the middle of the canvas", () => {
89
+ const vp = centredViewport({ x: 300, y: 200 }, { width: 800, height: 600 });
90
+ expect(300 * vp.zoom + vp.x).toBe(400);
91
+ expect(200 * vp.zoom + vp.y).toBe(300);
92
+ });
93
+ });
94
+
95
+ describe("focusViewport", () => {
96
+ const canvas = { width: 703, height: 665 };
97
+
98
+ /** Where the box lands on screen under a viewport. */
99
+ const project = (b: ReturnType<typeof boundsOf>, vp: { x: number; y: number; zoom: number }) => {
100
+ if (!b) throw new Error("no bounds");
101
+ return {
102
+ left: b.x * vp.zoom + vp.x,
103
+ right: (b.x + b.width) * vp.zoom + vp.x,
104
+ top: b.y * vp.zoom + vp.y,
105
+ bottom: (b.y + b.height) * vp.zoom + vp.y,
106
+ };
107
+ };
108
+
109
+ it("gets an arrow wider than the canvas wholly on screen", () => {
110
+ // checkout's step 12 in the diagram variant: 1087pt of arrow in a 703px
111
+ // pane. LikeC4's own centring caps the zoom at 1 and leaves it hanging off
112
+ // the right-hand edge; this is the case the helper exists for.
113
+ const bounds = { x: 830, y: 1509, width: 1087, height: 260 };
114
+ const vp = focusViewport(bounds, canvas);
115
+ const at = project(bounds, vp);
116
+ // Within a pixel: the viewport is rounded to whole pixels on purpose, so
117
+ // nothing lands on a half-device-pixel and blurs.
118
+ expect(at.left).toBeCloseTo(CANVAS_PAD, 0);
119
+ expect(canvas.width - at.right).toBeCloseTo(CANVAS_PAD, 0);
120
+ expect(at.top).toBeGreaterThanOrEqual(0);
121
+ expect(at.bottom).toBeLessThanOrEqual(canvas.height);
122
+ });
123
+
124
+ it("does not magnify a short arrow past the readable size", () => {
125
+ const bounds = { x: 0, y: 0, width: 40, height: 10 };
126
+ expect(focusViewport(bounds, canvas).zoom).toBe(READABLE_ZOOM);
127
+ });
128
+
129
+ it("centres whatever it is shown", () => {
130
+ const bounds = { x: 100, y: 4000, width: 200, height: 80 };
131
+ const vp = focusViewport(bounds, canvas);
132
+ const at = project(bounds, vp);
133
+ // Equal margins either side, to within the whole-pixel rounding.
134
+ expect(Math.abs(at.left - (canvas.width - at.right))).toBeLessThanOrEqual(1);
135
+ expect(Math.abs(at.top - (canvas.height - at.bottom))).toBeLessThanOrEqual(1);
136
+ });
137
+
138
+ it("stops zooming out at the floor rather than vanishing", () => {
139
+ const monstrous = { x: 0, y: 0, width: 100000, height: 100000 };
140
+ expect(focusViewport(monstrous, canvas).zoom).toBe(MIN_FOCUS_ZOOM);
141
+ });
142
+
143
+ it("survives a zero-height arrow, which is what a straight one is", () => {
144
+ const flat = { x: 0, y: 500, width: 300, height: 0 };
145
+ const vp = focusViewport(flat, canvas);
146
+ expect(vp.zoom).toBe(READABLE_ZOOM);
147
+ expect(Number.isFinite(vp.x) && Number.isFinite(vp.y)).toBe(true);
148
+ });
149
+ });
@@ -0,0 +1,135 @@
1
+ // Where the canvas starts looking, and at what size.
2
+ //
3
+ // Fit-to-view is the wrong opening move for a flow. Fitting a forty-five step
4
+ // sequence into a pane puts every label at three or four pixels, which is a
5
+ // picture of a flow rather than a flow: the reader can see that it is long and
6
+ // nothing else. So the canvas opens at the size the labels were drawn for and
7
+ // shows the beginning, which is where reading starts anyway. Fit-to-view stays
8
+ // one button away, for the reader who wants the shape rather than the words.
9
+ //
10
+ // Pure arithmetic, kept out of the component so the one decision worth arguing
11
+ // about — what "the beginning" means when the diagram is narrower than the
12
+ // pane — is stated once and tested.
13
+
14
+ export interface BBox {
15
+ x: number;
16
+ y: number;
17
+ width: number;
18
+ height: number;
19
+ }
20
+
21
+ export interface Size {
22
+ width: number;
23
+ height: number;
24
+ }
25
+
26
+ export interface Viewport {
27
+ x: number;
28
+ y: number;
29
+ zoom: number;
30
+ }
31
+
32
+ /** The authored size. Below roughly 0.7 the 16px labels stop being words. */
33
+ export const READABLE_ZOOM = 1;
34
+
35
+ /** Breathing room between the diagram and the edge of the pane. */
36
+ export const CANVAS_PAD = 24;
37
+
38
+ /**
39
+ * The opening viewport: the top of the diagram, at readable zoom.
40
+ *
41
+ * Horizontally it centres when the diagram fits and left-aligns when it does
42
+ * not. Centring a diagram that overflows would open the reader in the middle
43
+ * of the first row with the start scrolled off to the left, and left-aligning
44
+ * one that fits leaves a dead half-canvas on the right — so it is neither rule
45
+ * everywhere, it is each rule where it is the true one.
46
+ *
47
+ * Vertically it always aligns to the top: the first step is the first step
48
+ * whether or not the sequence fits, and centring a short flow would push it
49
+ * down away from the participants header it belongs under.
50
+ */
51
+ export function readableViewport(
52
+ bounds: BBox,
53
+ canvas: Size,
54
+ zoom: number = READABLE_ZOOM,
55
+ ): Viewport {
56
+ const width = bounds.width * zoom;
57
+ const fits = width + CANVAS_PAD * 2 <= canvas.width;
58
+ const x = fits
59
+ ? (canvas.width - width) / 2 - bounds.x * zoom
60
+ : CANVAS_PAD - bounds.x * zoom;
61
+ return {
62
+ x: Math.round(x),
63
+ y: Math.round(CANVAS_PAD - bounds.y * zoom),
64
+ zoom,
65
+ };
66
+ }
67
+
68
+ /** How far the canvas may zoom out to get one step wholly on screen. */
69
+ export const MIN_FOCUS_ZOOM = 0.2;
70
+
71
+ /** The box a polyline occupies, or null for a line with no points. */
72
+ export function boundsOf(
73
+ points: readonly (readonly [number, number])[],
74
+ ): BBox | null {
75
+ if (points.length === 0) return null;
76
+ let minX = Infinity;
77
+ let minY = Infinity;
78
+ let maxX = -Infinity;
79
+ let maxY = -Infinity;
80
+ for (const [x, y] of points) {
81
+ if (x < minX) minX = x;
82
+ if (y < minY) minY = y;
83
+ if (x > maxX) maxX = x;
84
+ if (y > maxY) maxY = y;
85
+ }
86
+ return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };
87
+ }
88
+
89
+ /**
90
+ * A viewport that shows one thing whole, as large as it will go.
91
+ *
92
+ * This exists because "bring step 12 into view" has to mean it. LikeC4's own
93
+ * centring caps the zoom at what the viewport already had, which for a step
94
+ * whose arrow is wider than the pane leaves the arrow half off the screen —
95
+ * technically centred, not actually visible. So the zoom comes down as far as
96
+ * it must and no further, and stops at MIN_FOCUS_ZOOM rather than diving to
97
+ * whatever a pathological arrow would need.
98
+ */
99
+ export function focusViewport(
100
+ bounds: BBox,
101
+ canvas: Size,
102
+ max: number = READABLE_ZOOM,
103
+ ): Viewport {
104
+ const room = (span: number, size: number): number =>
105
+ span > 0 ? (size - CANVAS_PAD * 2) / span : max;
106
+ const zoom = Math.max(
107
+ MIN_FOCUS_ZOOM,
108
+ Math.min(max, room(bounds.width, canvas.width), room(bounds.height, canvas.height)),
109
+ );
110
+ return centredViewport(
111
+ { x: bounds.x + bounds.width / 2, y: bounds.y + bounds.height / 2 },
112
+ canvas,
113
+ zoom,
114
+ );
115
+ }
116
+
117
+ /**
118
+ * Readable zoom, centred on one point of the diagram.
119
+ *
120
+ * For the variant that has no beginning. A diagram folds a flow's steps onto
121
+ * the handful of boxes that repeat, so there is no top-left corner that means
122
+ * "start here" — but there is still a first step, and its arrow is a place the
123
+ * reader can be put down at a size where the labels are words.
124
+ */
125
+ export function centredViewport(
126
+ point: { x: number; y: number },
127
+ canvas: Size,
128
+ zoom: number = READABLE_ZOOM,
129
+ ): Viewport {
130
+ return {
131
+ x: Math.round(canvas.width / 2 - point.x * zoom),
132
+ y: Math.round(canvas.height / 2 - point.y * zoom),
133
+ zoom,
134
+ };
135
+ }