@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,448 @@
1
+ import { Suspense, lazy, useCallback, useEffect, useState } from "react";
2
+ import {
3
+ BrowserRouter,
4
+ Navigate,
5
+ Route,
6
+ Routes,
7
+ useLocation,
8
+ useNavigate,
9
+ } from "react-router";
10
+ import type { Location } from "react-router";
11
+ import {
12
+ Panel,
13
+ ResizeHandle,
14
+ SavedGroup,
15
+ useCanvasResize,
16
+ usePanelRef,
17
+ } from "./panels";
18
+ import { FlowDetail } from "../pages/FlowDetail";
19
+ import { FlowIndex } from "../pages/FlowIndex";
20
+ import { AdrIndex } from "../pages/AdrIndex";
21
+ import { Language } from "../pages/Language";
22
+ import { AdrDetail } from "../pages/AdrDetail";
23
+ import { Overview } from "../pages/Overview";
24
+ import { ContextMap } from "../pages/ContextMap";
25
+ import { ContextPage } from "../pages/ContextPage";
26
+ import { ServicePage } from "../pages/ServicePage";
27
+ import { AggregatePage } from "../pages/AggregatePage";
28
+ import { BlockPage } from "../pages/BlockPage";
29
+ import { EnumPage } from "../pages/EnumPage";
30
+ import { EventPage } from "../pages/EventPage";
31
+ import { StorePage } from "../pages/StorePage";
32
+ import { GraphPage } from "../pages/GraphPage";
33
+ import { Problems } from "../pages/Problems";
34
+ import { Settings } from "../pages/Settings";
35
+ import { Changes } from "../pages/Changes";
36
+ import { RegistryIndex } from "../pages/RegistryIndex";
37
+ import { ModulePage } from "../pages/ModulePage";
38
+ import { ExternalPage } from "../pages/ExternalPage";
39
+ import { NotFoundPage } from "../pages/NotFound";
40
+ import { CatalogFailure } from "../pages/CatalogFailure";
41
+ import { activeCatalogProfile, catalogError } from "../data";
42
+ import { SidePanel } from "../components/Overlay";
43
+ import { Empty } from "../components/PageHeader";
44
+ import { WithDetail } from "../selection/DetailPanel";
45
+ import { SelectionSync } from "../selection/sync";
46
+ import { Trail } from "../trail/Trail";
47
+ import { TrailRecorder } from "../trail/record";
48
+ import { HashScroll } from "./HashScroll";
49
+ import { CommandPalette } from "./CommandPalette";
50
+ import { Sidebar } from "./Sidebar";
51
+ import { TopBar } from "./TopBar";
52
+ import { ThemeProvider } from "./theme";
53
+ import { DensityProvider } from "./density";
54
+ import { useNarrow, usePhone } from "./responsive";
55
+ import { ShortcutsSheet, useShortcuts } from "./shortcuts";
56
+ import { Toaster } from "./toast";
57
+ import { PeekLayer } from "../peek/PeekLayer";
58
+ import { useUiStore } from "./ui-store";
59
+ import { ForgeAccessProvider } from "./forge-access";
60
+ import { QueryClientProvider } from "@tanstack/react-query";
61
+ import { queryClient } from "./query-client";
62
+ import { AnimatePresence, MotionProvider, m, page } from "../lib/motion";
63
+ import { useChatUi } from "../chat/store";
64
+ import { projectPreview } from "../lib/project-preview";
65
+
66
+ // The chat is a chunk of its own, and a build with VITE_CHAT=off has no such
67
+ // chunk: the test is on the literal Vite substitutes, so the import below is
68
+ // dead code to the bundler and is dropped with everything it would pull in.
69
+ const ChatPanel =
70
+ import.meta.env.VITE_CHAT !== "off"
71
+ ? lazy(() => import("../chat/ChatPanel"))
72
+ : null;
73
+
74
+ /**
75
+ * What the click shows while that chunk is on its way: the same sheet, at the
76
+ * same width, with a word in it. A fallback of nothing meant the first "ask"
77
+ * on a slow line did nothing for a second and the reader clicked again.
78
+ */
79
+ function ChatLoading() {
80
+ const phone = usePhone();
81
+ return (
82
+ <SidePanel
83
+ open
84
+ onClose={() => useChatUi.getState().setOpen(false)}
85
+ side="right"
86
+ label="Ask the catalog"
87
+ width={phone ? "100vw" : "min(560px,92vw)"}
88
+ >
89
+ <div className="h-full bg-canvas p-gutter text-ink">
90
+ <Empty>loading the chat…</Empty>
91
+ </div>
92
+ </SidePanel>
93
+ );
94
+ }
95
+
96
+ /**
97
+ * Every route, once. Rendered inside a pane on wide layouts and alone below.
98
+ *
99
+ * The location is a prop, not read from the router: while a page is on its
100
+ * way out the router already says the next address, and the page leaving
101
+ * must keep drawing the one it was.
102
+ */
103
+ function AppRoutes({ location }: { location: Location }) {
104
+ return (
105
+ <Routes location={location}>
106
+ <Route path="/" element={<Overview />} />
107
+ <Route path="/flows" element={<FlowIndex />} />
108
+ <Route
109
+ path="/flows/:flow"
110
+ element={
111
+ <WithDetail id="flow">
112
+ <FlowDetail />
113
+ </WithDetail>
114
+ }
115
+ />
116
+ <Route path="/adrs" element={<AdrIndex />} />
117
+ <Route path="/language" element={<Language />} />
118
+ <Route path="/adrs/:adr" element={<AdrDetail />} />
119
+ <Route path="/problems" element={<Problems />} />
120
+ <Route path="/settings" element={<Settings />} />
121
+ <Route path="/changes" element={<Changes />} />
122
+ <Route path="/externals/:external" element={<ExternalPage />} />
123
+ {/* A module sits at the estate level, not under a service: it is
124
+ published by one and read by four, so hanging it off a service would
125
+ put one entity at four URLs. */}
126
+ <Route path="/registry" element={<RegistryIndex />} />
127
+ <Route
128
+ path="/registry/:module"
129
+ element={
130
+ <WithDetail id="module">
131
+ <ModulePage />
132
+ </WithDetail>
133
+ }
134
+ />
135
+ <Route
136
+ path="/map"
137
+ element={
138
+ <WithDetail id="map">
139
+ <ContextMap />
140
+ </WithDetail>
141
+ }
142
+ />
143
+ <Route
144
+ path="/c/:context"
145
+ element={
146
+ <WithDetail id="context">
147
+ <ContextPage />
148
+ </WithDetail>
149
+ }
150
+ />
151
+ <Route
152
+ path="/c/:context/:service"
153
+ element={
154
+ <WithDetail id="service">
155
+ <ServicePage />
156
+ </WithDetail>
157
+ }
158
+ />
159
+ {/* Before the aggregate routes: "data" is a literal sitting where an
160
+ aggregate slug would, and "/c/x/y/data/z" would otherwise be read as
161
+ the event "z" of an aggregate called "data". */}
162
+ <Route
163
+ path="/c/:context/:service/data/:store"
164
+ element={
165
+ <WithDetail id="store">
166
+ <StorePage />
167
+ </WithDetail>
168
+ }
169
+ />
170
+ <Route
171
+ path="/c/:context/:service/:aggregate"
172
+ element={<AggregatePage />}
173
+ />
174
+ {/* The two literal segments come first: a block page must not be
175
+ read as an event whose slug happens to be "vo". */}
176
+ <Route
177
+ path="/c/:context/:service/:aggregate/vo/:block"
178
+ element={<BlockPage kind="vo" />}
179
+ />
180
+ <Route
181
+ path="/c/:context/:service/:aggregate/entity/:block"
182
+ element={<BlockPage kind="entity" />}
183
+ />
184
+ <Route
185
+ path="/c/:context/:service/:aggregate/enum/:enum"
186
+ element={<EnumPage />}
187
+ />
188
+ <Route
189
+ path="/c/:context/:service/:aggregate/:event"
190
+ element={
191
+ <WithDetail id="event">
192
+ <EventPage />
193
+ </WithDetail>
194
+ }
195
+ />
196
+ <Route
197
+ path="/graph"
198
+ element={
199
+ <WithDetail id="graph">
200
+ <GraphPage />
201
+ </WithDetail>
202
+ }
203
+ />
204
+ <Route path="/index.html" element={<Navigate to="/" replace />} />
205
+ <Route path="*" element={<NotFoundPage />} />
206
+ </Routes>
207
+ );
208
+ }
209
+
210
+ /**
211
+ * The catalog tree as an overlay, below the narrow breakpoint. It is the same
212
+ * Sidebar; only the box around it changes, so nothing about the tree has two
213
+ * implementations.
214
+ */
215
+ function SidebarDrawer() {
216
+ const open = useUiStore((s) => s.drawer);
217
+ const setDrawer = useUiStore((s) => s.setDrawer);
218
+ const { pathname } = useLocation();
219
+
220
+ // Navigating is what the tree is for, so a click that moves the reader ends
221
+ // the drawer's job. Without this the page they asked for is behind a scrim.
222
+ useEffect(() => setDrawer(false), [pathname, setDrawer]);
223
+
224
+ return (
225
+ <SidePanel
226
+ open={open}
227
+ onClose={() => setDrawer(false)}
228
+ side="left"
229
+ label="Catalog"
230
+ width="min(320px,85vw)"
231
+ >
232
+ <Sidebar />
233
+ </SidePanel>
234
+ );
235
+ }
236
+
237
+ function Shell() {
238
+ const [palette, setPalette] = useState(false);
239
+ const [help, setHelp] = useState(false);
240
+ const chatOpen = useChatUi((s) => s.open);
241
+ const [railed, setRailed] = useState(false);
242
+ const location = useLocation();
243
+ const navigate = useNavigate();
244
+ const { pathname } = location;
245
+ const settle = useCanvasResize();
246
+ const sidebarRef = usePanelRef();
247
+ const narrow = useNarrow();
248
+ const toggleDrawer = useUiStore((s) => s.toggleDrawer);
249
+ const setDrawer = useUiStore((s) => s.setDrawer);
250
+ const toggleDetail = useUiStore((s) => s.toggleDetail);
251
+ const revealNonce = useUiStore((s) => s.revealNonce);
252
+
253
+ // Keep the selected catalog shareable even though most route builders do
254
+ // not know about presentation state. Changing the profile itself reloads
255
+ // the bundle; ordinary navigation only restores its URL marker.
256
+ useEffect(() => {
257
+ const params = new URLSearchParams(location.search);
258
+ if (params.get("catalog") === activeCatalogProfile.id) return;
259
+ params.set("catalog", activeCatalogProfile.id);
260
+ navigate(
261
+ { pathname: location.pathname, search: `?${params}`, hash: location.hash },
262
+ { replace: true },
263
+ );
264
+ }, [location.hash, location.pathname, location.search, navigate]);
265
+
266
+ // Collapsed is a fact about pixels, not about who dragged: the rail appears
267
+ // whether the reader dragged past the minimum or pressed a rail button.
268
+ const onSidebarResize = useCallback(
269
+ (size: { inPixels: number }) => setRailed(size.inPixels <= 56),
270
+ [],
271
+ );
272
+
273
+ // The rail's buttons ask the panel to expand; `railed` then follows from the
274
+ // resize that answers, so the flag can never disagree with the layout.
275
+ //
276
+ // `expand()` restores the width the panel had before it collapsed, which
277
+ // after a drag all the way to the edge is the minimum - a tree too narrow to
278
+ // read the names in. Anything at or under the minimum is treated as "no
279
+ // remembered width" and goes back to the default instead.
280
+ const expandSidebar = useCallback(() => {
281
+ const panel = sidebarRef.current;
282
+ if (!panel) return;
283
+ panel.expand();
284
+ if (panel.getSize().asPercentage <= 13) panel.resize("18");
285
+ }, [sidebarRef]);
286
+
287
+ // A layout restored from a previous session can arrive already collapsed,
288
+ // and no resize fires for a size that never changed - so the rail has to be
289
+ // read off the panel once on mount, or the full tree renders into 48px.
290
+ useEffect(() => {
291
+ setRailed(sidebarRef.current?.isCollapsed() ?? false);
292
+ }, [sidebarRef]);
293
+
294
+ // "[" means the same thing at both widths - show me the tree, or stop
295
+ // showing it - and reaches for whichever mechanism is on screen.
296
+ const toggleSidebar = useCallback(() => {
297
+ if (narrow) {
298
+ toggleDrawer();
299
+ return;
300
+ }
301
+ const panel = sidebarRef.current;
302
+ if (!panel) return;
303
+ if (panel.isCollapsed()) expandSidebar();
304
+ else panel.collapse();
305
+ }, [narrow, toggleDrawer, sidebarRef, expandSidebar]);
306
+
307
+ // "Reveal in tree" selects; the tree then opens its own ancestors and scrolls
308
+ // itself. The one thing it cannot do from inside a collapsed pane is become
309
+ // visible, which is this.
310
+ useEffect(() => {
311
+ if (revealNonce === 0) return;
312
+ if (narrow) setDrawer(true);
313
+ else if (sidebarRef.current?.isCollapsed()) expandSidebar();
314
+ }, [revealNonce, narrow, setDrawer, sidebarRef, expandSidebar]);
315
+
316
+ useShortcuts(
317
+ {
318
+ openPalette: () => setPalette(true),
319
+ openHelp: () => setHelp(true),
320
+ toggleSidebar,
321
+ toggleDetail,
322
+ },
323
+ // While a modal owns the keyboard the global bindings stand down; ⌘K and
324
+ // Esc are handled inside the modals themselves.
325
+ !palette && !help && !chatOpen,
326
+ );
327
+
328
+ return (
329
+ <div className="flex h-full flex-col bg-canvas text-ink">
330
+ <SelectionSync />
331
+ <TrailRecorder />
332
+ <HashScroll />
333
+ <TopBar
334
+ onOpenPalette={() => setPalette(true)}
335
+ onOpenHelp={() => setHelp(true)}
336
+ onToggleSidebar={toggleSidebar}
337
+ narrow={narrow}
338
+ />
339
+ {projectPreview ? (
340
+ <div className="shrink-0 border-b border-declared/40 bg-declared/10 px-gutter py-2 text-center text-sm text-declared">
341
+ Temporary project preview · the repository has not been changed · expires after 15 minutes
342
+ </div>
343
+ ) : null}
344
+ {/* Under the bar, above everything: the trail is about the whole shell,
345
+ not about the page inside it. */}
346
+ <Trail />
347
+
348
+ {narrow ? (
349
+ <>
350
+ {/* `key` on the route content is what makes the page transition
351
+ fire: a new pathname is a new element. The old one leaves first,
352
+ on the micro duration, then the new one rises. */}
353
+ <AnimatePresence mode="wait">
354
+ <m.main
355
+ key={pathname}
356
+ {...page}
357
+ className="min-h-0 flex-1 overflow-hidden"
358
+ >
359
+ <AppRoutes location={location} />
360
+ </m.main>
361
+ </AnimatePresence>
362
+ <SidebarDrawer />
363
+ </>
364
+ ) : (
365
+ <SavedGroup
366
+ id="portolan:shell"
367
+ orientation="horizontal"
368
+ className="min-h-0 flex-1"
369
+ >
370
+ <Panel
371
+ id="sidebar"
372
+ defaultSize="18"
373
+ minSize="12"
374
+ maxSize="28"
375
+ collapsible
376
+ collapsedSize="48px"
377
+ className="h-full text-sm"
378
+ panelRef={sidebarRef}
379
+ onResize={onSidebarResize}
380
+ >
381
+ <Sidebar railed={railed} onExpand={expandSidebar} />
382
+ </Panel>
383
+
384
+ <ResizeHandle id="shell" />
385
+
386
+ {/* The main pane owns a canvas on most routes, so a drag here has to
387
+ reach the diagram - debounced, once the reader lets go. */}
388
+ <Panel id="main" className="h-full min-w-0" onResize={settle}>
389
+ {/* The detail rail rides along with every page that draws a
390
+ diagram, so a selection made anywhere has somewhere to be read. */}
391
+ <AnimatePresence mode="wait">
392
+ <m.main
393
+ key={pathname}
394
+ {...page}
395
+ className="h-full overflow-hidden"
396
+ >
397
+ <AppRoutes location={location} />
398
+ </m.main>
399
+ </AnimatePresence>
400
+ </Panel>
401
+ </SavedGroup>
402
+ )}
403
+
404
+ <CommandPalette open={palette} onClose={() => setPalette(false)} />
405
+ <ShortcutsSheet open={help} onClose={() => setHelp(false)} />
406
+ {ChatPanel && chatOpen ? (
407
+ <Suspense fallback={<ChatLoading />}>
408
+ <ChatPanel />
409
+ </Suspense>
410
+ ) : null}
411
+ {/* The one thing the app says out loud, and it says it here rather than
412
+ in the sidebar: a pin can be taken from a page whose tree is folded
413
+ away to a 48px rail. */}
414
+ <Toaster />
415
+ {/* A card on hover, for any row that names an entity. Mounted once,
416
+ here, because the rows it answers for are on every page. */}
417
+ <PeekLayer />
418
+ </div>
419
+ );
420
+ }
421
+
422
+ export function App() {
423
+ // A catalog that failed validation is the whole app's answer, so it is
424
+ // decided above the router: there is no route worth reaching.
425
+ if (catalogError) {
426
+ return (
427
+ <ThemeProvider>
428
+ <CatalogFailure error={catalogError} />
429
+ </ThemeProvider>
430
+ );
431
+ }
432
+
433
+ return (
434
+ <ThemeProvider>
435
+ <MotionProvider>
436
+ <DensityProvider>
437
+ <QueryClientProvider client={queryClient}>
438
+ <ForgeAccessProvider>
439
+ <BrowserRouter basename={import.meta.env.BASE_URL}>
440
+ <Shell />
441
+ </BrowserRouter>
442
+ </ForgeAccessProvider>
443
+ </QueryClientProvider>
444
+ </DensityProvider>
445
+ </MotionProvider>
446
+ </ThemeProvider>
447
+ );
448
+ }
@@ -0,0 +1,46 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { branchCompareHref } from "../lib/branch-compare";
3
+ import type { BuildInfo } from "../lib/build-info";
4
+
5
+ const BUILD: BuildInfo = {
6
+ commit: "abc",
7
+ shortCommit: "abc",
8
+ branch: "feature/catalog",
9
+ builtAt: "",
10
+ commitUrl: "",
11
+ buildUrl: "",
12
+ buildNumber: "",
13
+ dirty: false,
14
+ repoUrl: "https://github.com/acme/portolan/",
15
+ };
16
+
17
+ describe("branchCompareHref", () => {
18
+ it("builds a GitHub comparison without losing slashes in branch names", () => {
19
+ expect(branchCompareHref("main", "feature/catalog", BUILD)).toBe(
20
+ "https://github.com/acme/portolan/compare/main...feature%2Fcatalog",
21
+ );
22
+ });
23
+
24
+ it("uses GitLab's namespaced comparison route", () => {
25
+ expect(
26
+ branchCompareHref("main", "feature", {
27
+ ...BUILD,
28
+ repoUrl: "https://gitlab.com/acme/portolan",
29
+ }),
30
+ ).toBe("https://gitlab.com/acme/portolan/-/compare/main...feature");
31
+ });
32
+
33
+ it("uses GitLab's route for self-hosted repositories", () => {
34
+ expect(
35
+ branchCompareHref("main", "feature", {
36
+ ...BUILD,
37
+ forge: "gitlab",
38
+ repoUrl: "https://code.example.com/acme/portolan",
39
+ }),
40
+ ).toBe("https://code.example.com/acme/portolan/-/compare/main...feature");
41
+ });
42
+
43
+ it("does not offer a comparison for the same branch", () => {
44
+ expect(branchCompareHref("main", "main", BUILD)).toBeNull();
45
+ });
46
+ });
@@ -0,0 +1,153 @@
1
+ import {
2
+ Listbox,
3
+ ListboxButton,
4
+ ListboxOption,
5
+ ListboxOptions,
6
+ } from "@headlessui/react";
7
+ import { Check, ChevronDown, ExternalLink, GitBranch, LoaderCircle, Tag } from "lucide-react";
8
+ import { useQuery } from "@tanstack/react-query";
9
+ import { useMemo } from "react";
10
+ import { Link, useLocation, useNavigate, useSearchParams } from "react-router";
11
+ import { branchCompareHref } from "../lib/branch-compare";
12
+ import { buildInfo } from "../lib/build-info";
13
+ import { findRef, sortRefs } from "../lib/forge-refs";
14
+ import { forgeRepoFromUrl } from "../lib/github-catalog";
15
+ import type { ForgeRef } from "../lib/github-catalog";
16
+ import { forgetComparison, rememberComparison } from "../lib/comparison-memory";
17
+ import { forgeRefsQuery } from "../lib/queries";
18
+ import { paths } from "../routes";
19
+ import { useForgeAccess } from "./forge-access";
20
+
21
+ const NO_REFS: ForgeRef[] = [];
22
+
23
+ function refNote(ref: ForgeRef, current: string): string {
24
+ if (ref.kind === "branch" && ref.name === current) return `${ref.commit.slice(0, 7)} · current catalog`;
25
+ return `${ref.commit.slice(0, 7)}${ref.protected ? " · protected" : ""}`;
26
+ }
27
+
28
+ /**
29
+ * Selects the comparison head. Branches and tags are read from the configured forge at
30
+ * runtime; a choice opens the first-class changes route, whose URL carries both heads.
31
+ */
32
+ export function BranchPicker({ compact = false }: { compact?: boolean }) {
33
+ const current = buildInfo.branch || "main";
34
+ const repo = forgeRepoFromUrl(buildInfo.repoUrl, buildInfo.forge);
35
+ const access = useForgeAccess();
36
+ const navigate = useNavigate();
37
+ const { pathname } = useLocation();
38
+ const [search] = useSearchParams();
39
+ const token = repo ? access.tokenFor(repo) : "";
40
+ // The same query the changes page reads: whichever mounts first fetches,
41
+ // the other one joins it.
42
+ const refsQuery = useQuery(forgeRefsQuery(repo, token));
43
+ const remote = refsQuery.data ?? NO_REFS;
44
+ const loading = refsQuery.isLoading;
45
+ const error = refsQuery.error?.message ?? "";
46
+
47
+ const refs = useMemo(() => {
48
+ const known = remote.some((ref) => ref.kind === "branch" && ref.name === current)
49
+ ? remote
50
+ : [...remote, { name: current, commit: buildInfo.commit, protected: false, kind: "branch" as const }];
51
+ return sortRefs(known, current);
52
+ }, [current, remote]);
53
+ const branches = refs.filter((ref) => ref.kind === "branch");
54
+ const tags = refs.filter((ref) => ref.kind === "tag");
55
+
56
+ const requested = pathname === paths.changes() ? search.get("head") ?? "" : "";
57
+ const selected = findRef(refs, requested) ? requested : current;
58
+ const comparing = selected !== current;
59
+ const compareHref = branchCompareHref(current, selected);
60
+
61
+ const choose = (name: string) => {
62
+ if (name === current) {
63
+ forgetComparison();
64
+ navigate(paths.changes());
65
+ return;
66
+ }
67
+ rememberComparison(current, name);
68
+ const next = new URLSearchParams({ base: current, head: name });
69
+ navigate(`${paths.changes()}?${next}`);
70
+ };
71
+
72
+ return (
73
+ <Listbox value={selected} onChange={choose}>
74
+ <ListboxButton
75
+ aria-label={comparing ? `Compare ${current} with ${selected}` : `Branch ${current}`}
76
+ title={comparing ? `${current} compared with ${selected}` : `Branch ${current}`}
77
+ className={({ open }) =>
78
+ compact
79
+ ? `flex size-8 shrink-0 items-center justify-center rounded-control border t-micro transition-colors border-line hover:bg-surface ${open || comparing ? "text-accent" : "text-muted hover:text-ink"}`
80
+ : `mono flex max-w-56 shrink-0 items-center gap-1.5 rounded-control border px-2 py-1.5 t-micro transition-colors ${open || comparing ? "border-accent text-accent" : "border-line text-muted hover:border-line-strong hover:bg-surface hover:text-ink"}`
81
+ }
82
+ >
83
+ {({ open }) => (
84
+ <>
85
+ <GitBranch size={16} aria-hidden className="shrink-0" />
86
+ {!compact ? <span className="min-w-0 truncate">{comparing ? `${current} → ${selected}` : current}</span> : null}
87
+ {!compact ? (
88
+ <ChevronDown size={13} aria-hidden className={`shrink-0 t-micro transition-transform ${open ? "rotate-180" : ""}`} />
89
+ ) : null}
90
+ </>
91
+ )}
92
+ </ListboxButton>
93
+
94
+ <ListboxOptions
95
+ aria-label="Comparison head branch or tag"
96
+ anchor={{ to: "bottom end", gap: 4, padding: 8 }}
97
+ className="branch-options palette-in z-50 w-80 overflow-y-auto rounded-control border bg-canvas py-1 border-line-strong shadow-md focus:outline-none"
98
+ >
99
+ <div className="label px-3 pt-2 pb-1">compare {current} with</div>
100
+ {branches.map((ref) => <RefOption key={`branch:${ref.name}`} item={ref} current={current} />)}
101
+ {/* A tag and a branch can share a name; the option's value is the
102
+ name, which the changes route resolves branch-first, so a tag
103
+ shadowed that way is listed but selects the branch. */}
104
+ {tags.length > 0 ? (
105
+ <div className="label flex items-center gap-1.5 border-t border-line px-3 pt-2 pb-1">
106
+ <Tag size={11} aria-hidden /> tags
107
+ </div>
108
+ ) : null}
109
+ {tags.map((ref) => <RefOption key={`tag:${ref.name}`} item={ref} current={current} />)}
110
+ {loading ? (
111
+ <div className="mono flex items-center gap-2 border-t border-line px-3 py-2 text-muted" role="status">
112
+ <LoaderCircle size={13} aria-hidden className="animate-spin" /> Loading {repo?.provider === "gitlab" ? "GitLab" : "GitHub"} branches and tags…
113
+ </div>
114
+ ) : error ? (
115
+ <div className="border-t border-line px-3 py-2 text-sm">
116
+ <div className="text-unresolved">{error}</div>
117
+ <Link to={paths.changes()} className="mt-1 inline-block text-accent hover:underline">
118
+ repository access →
119
+ </Link>
120
+ </div>
121
+ ) : !repo ? (
122
+ <div className="border-t border-line px-3 py-2 text-sm text-muted">Runtime comparison needs a GitHub or GitLab repository.</div>
123
+ ) : null}
124
+ {compareHref ? (
125
+ <div className="sticky bottom-0 mt-1 border-t border-line bg-canvas px-3 py-2">
126
+ <a href={compareHref} target="_blank" rel="noreferrer" className="mono flex items-center gap-1.5 rounded-control text-accent hover:underline">
127
+ open comparison on {repo?.provider === "gitlab" ? "GitLab" : "GitHub"} <ExternalLink size={12} aria-hidden />
128
+ </a>
129
+ </div>
130
+ ) : null}
131
+ </ListboxOptions>
132
+ </Listbox>
133
+ );
134
+ }
135
+
136
+ function RefOption({ item, current }: { item: ForgeRef; current: string }) {
137
+ return (
138
+ <ListboxOption
139
+ value={item.name}
140
+ className={({ focus }) => `mono flex cursor-pointer items-start gap-2 px-3 py-2 ${focus ? "bg-raised" : ""}`}
141
+ >
142
+ {({ selected: on }) => (
143
+ <>
144
+ <Check size={13} aria-hidden className="mt-0.5 shrink-0 text-accent" style={{ opacity: on ? 1 : 0 }} />
145
+ <span className="min-w-0 flex-1">
146
+ <span className={`block truncate ${on ? "text-accent" : "text-ink"}`}>{item.name}</span>
147
+ <span className="block truncate text-muted">{refNote(item, current)}</span>
148
+ </span>
149
+ </>
150
+ )}
151
+ </ListboxOption>
152
+ );
153
+ }
@@ -0,0 +1,38 @@
1
+ // The trail under the top bar, one crumb per segment. What matters is that no
2
+ // route falls through to nothing, and that a literal in the path - "data",
3
+ // "vo" - is never mistaken for the name of a thing.
4
+
5
+ import { describe, expect, it } from "vitest";
6
+ import { crumbsFor } from "./Breadcrumbs";
7
+
8
+ describe("crumbsFor", () => {
9
+ it("names the four index pages that used to have no trail", () => {
10
+ expect(crumbsFor("/language")).toEqual([
11
+ { label: "language", to: "/language" },
12
+ ]);
13
+ expect(crumbsFor("/problems")).toEqual([
14
+ { label: "problems", to: "/problems" },
15
+ ]);
16
+ expect(crumbsFor("/registry")).toEqual([
17
+ { label: "registry", to: "/registry" },
18
+ ]);
19
+ expect(crumbsFor("/registry/no-such-module").map((c) => c.to)).toEqual([
20
+ "/registry",
21
+ "/registry/no-such-module",
22
+ ]);
23
+ });
24
+
25
+ it("calls the decisions what the tree calls them", () => {
26
+ expect(crumbsFor("/adrs")[0]).toEqual({ label: "decisions", to: "/adrs" });
27
+ });
28
+
29
+ it("reads 'data' as a literal, not as an aggregate", () => {
30
+ const crumbs = crumbsFor("/c/ctx/svc/data/pg");
31
+ expect(crumbs.map((c) => c.to)).toEqual([
32
+ "/c/ctx",
33
+ "/c/ctx/svc",
34
+ "/c/ctx/svc/data/pg",
35
+ ]);
36
+ expect(crumbs.some((c) => c.label === "data")).toBe(false);
37
+ });
38
+ });