@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,82 @@
1
+ // A service's schema, drawn from the catalog rather than from the file.
2
+ //
3
+ // `ApiReference` draws the OpenAPI document itself, and its header explains
4
+ // why: the catalog cannot carry the document's own shape, and re-deriving that
5
+ // into the schema would be rebuilding OpenAPI inside it.
6
+ //
7
+ // For a proto that premise does not hold. The extractor reads exactly that
8
+ // shape — the interfaces, their methods, the two messages each one moves — so
9
+ // the catalog already has it. Drawing the raw text would be a worse copy of
10
+ // data we hold structured, and an unlinkable one: a `<pre>` cannot take a
11
+ // reader from a field to the shared type it refs, or from a method to the
12
+ // operation it exposes.
13
+
14
+ import { useState } from "react";
15
+ import { Link } from "react-router";
16
+ import { index } from "../data";
17
+ import { Empty, SectionTitle } from "./PageHeader";
18
+ import { Ident } from "./Ident";
19
+ import { KindIcon } from "./kind";
20
+ import { MessageList, MethodRows } from "./MethodRows";
21
+ import { paths } from "../routes";
22
+ import { packageOf } from "../lib/registry";
23
+
24
+ /** The interfaces of one module, as the service page's spec tab shows them. */
25
+ export function ModuleSpec({ moduleId }: { moduleId: string }) {
26
+ const [open, setOpen] = useState<ReadonlySet<string>>(new Set());
27
+
28
+ const toggle = (id: string) =>
29
+ setOpen((shown) => {
30
+ const next = new Set(shown);
31
+ if (!next.delete(id)) next.add(id);
32
+
33
+ return next;
34
+ });
35
+
36
+ const module = index.moduleById.get(moduleId);
37
+ const declared = index.interfacesByModule.get(moduleId) ?? [];
38
+
39
+ if (!module) {
40
+ return (
41
+ <Empty>
42
+ this service&apos;s interfaces name a module the catalog does not hold
43
+ </Empty>
44
+ );
45
+ }
46
+
47
+ return (
48
+ <div className="max-w-table">
49
+ <div className="mono flex flex-wrap items-center gap-x-3">
50
+ <KindIcon kind="module" />
51
+ <Link
52
+ to={paths.module(module.slug)}
53
+ className="text-ink hover:underline"
54
+ >
55
+ {module.name}
56
+ </Link>
57
+ {module.commit ? (
58
+ <Ident value={module.commit.slice(0, 12)} className="text-muted" />
59
+ ) : null}
60
+ </div>
61
+
62
+ {declared.length === 0 ? (
63
+ <div className="mt-section">
64
+ <Empty>nothing in this catalog was declared in this module</Empty>
65
+ </div>
66
+ ) : null}
67
+
68
+ {declared.map(({ provided }) => (
69
+ <section key={provided.id} className="mt-section">
70
+ <SectionTitle>{packageOf(provided.id) || provided.id}</SectionTitle>
71
+ <div className="rounded-card border border-line">
72
+ <div className="mono border-b px-3 py-1.5 border-line bg-surface">
73
+ <Ident value={provided.id} className="text-ink" />
74
+ </div>
75
+ <MethodRows provided={provided} open={open} onToggle={toggle} />
76
+ <MessageList provided={provided} open={open} onToggle={toggle} />
77
+ </div>
78
+ </section>
79
+ ))}
80
+ </div>
81
+ );
82
+ }
@@ -0,0 +1,441 @@
1
+ import {
2
+ Code2,
3
+ ExternalLink,
4
+ LoaderCircle,
5
+ LockKeyhole,
6
+ Pin,
7
+ X,
8
+ } from "lucide-react";
9
+ import { EditorLink } from "./EditorLink";
10
+ import {
11
+ useCallback,
12
+ useEffect,
13
+ useLayoutEffect,
14
+ useMemo,
15
+ useRef,
16
+ useState,
17
+ } from "react";
18
+ import type {
19
+ CSSProperties,
20
+ FormEvent,
21
+ PointerEvent as ReactPointerEvent,
22
+ ReactNode,
23
+ } from "react";
24
+ import { createPortal } from "react-dom";
25
+ import { useQuery } from "@tanstack/react-query";
26
+ import { useForgeAccess } from "../app/forge-access";
27
+ import { sourceCodeQuery } from "../lib/queries";
28
+ import {
29
+ sourceLanguage,
30
+ sourceWindow,
31
+ SourceLoadError,
32
+ } from "../lib/source-code";
33
+ import type { SourceFile } from "../lib/source-code";
34
+ import { highlightSource } from "../lib/source-highlight";
35
+ import type { HighlightNode } from "../lib/source-highlight";
36
+ import type { SourceLocation } from "../lib/source-link";
37
+
38
+ const OPEN_DELAY_MS = 220;
39
+ const CLOSE_DELAY_MS = 140;
40
+ const PANEL_WIDTH = 720;
41
+ const PANEL_HEIGHT = 430;
42
+ const EDGE = 8;
43
+
44
+ type Position = { left: number; top: number; width: number; maxHeight: number };
45
+
46
+ function positionFor(trigger: DOMRect): Position {
47
+ const width = Math.min(PANEL_WIDTH, window.innerWidth - EDGE * 2);
48
+ const maxHeight = Math.min(PANEL_HEIGHT, window.innerHeight - EDGE * 2);
49
+ const left = Math.max(
50
+ EDGE,
51
+ Math.min(trigger.left, window.innerWidth - width - EDGE),
52
+ );
53
+ const below = window.innerHeight - trigger.bottom - EDGE;
54
+ const top =
55
+ below >= Math.min(maxHeight, 260)
56
+ ? trigger.bottom + 6
57
+ : Math.max(EDGE, trigger.top - maxHeight - 6);
58
+ return { left, top, width, maxHeight };
59
+ }
60
+
61
+ function AccessForm({
62
+ location,
63
+ message,
64
+ }: {
65
+ location: Extract<SourceLocation, { kind: "remote" }>;
66
+ message: string;
67
+ }) {
68
+ const access = useForgeAccess();
69
+ const [draft, setDraft] = useState("");
70
+ const connected = access.connectedTo(location.origin);
71
+ const provider = location.provider === "github" ? "GitHub" : "GitLab";
72
+
73
+ const submit = (event: FormEvent) => {
74
+ event.preventDefault();
75
+ if (!draft.trim()) return;
76
+ access.connect(location.origin, draft);
77
+ setDraft("");
78
+ };
79
+
80
+ return (
81
+ <div className="p-4">
82
+ <div className="flex items-start gap-2 text-ink">
83
+ <LockKeyhole
84
+ size={16}
85
+ className="mt-0.5 shrink-0 text-accent"
86
+ aria-hidden
87
+ />
88
+ <div>
89
+ <div className="font-medium">Repository access required</div>
90
+ <p className="mt-1 text-sm text-muted">{message}</p>
91
+ </div>
92
+ </div>
93
+ <form onSubmit={submit} className="mt-4 flex gap-2">
94
+ <label className="min-w-0 flex-1">
95
+ <span className="sr-only">{provider} read-only access token</span>
96
+ <input
97
+ type="password"
98
+ value={draft}
99
+ onChange={(event) => setDraft(event.target.value)}
100
+ autoComplete="off"
101
+ spellCheck={false}
102
+ placeholder={`${provider} read-only access token`}
103
+ className="mono w-full rounded-control border border-line bg-canvas px-3 py-2 text-sm text-ink outline-none placeholder:text-muted focus:border-accent"
104
+ />
105
+ </label>
106
+ <button
107
+ type="submit"
108
+ disabled={!draft.trim()}
109
+ className="btn-accent shrink-0 disabled:opacity-40"
110
+ >
111
+ {connected ? "Replace token" : "Connect"}
112
+ </button>
113
+ </form>
114
+ <p className="mt-2 text-xs text-muted">
115
+ Kept only in this tab&apos;s memory. Never written to URLs, Web Storage,
116
+ or Cache Storage.
117
+ </p>
118
+ {connected ? (
119
+ <button
120
+ type="button"
121
+ className="mono mt-3 text-xs text-muted hover:text-ink"
122
+ onClick={() => access.disconnect(location.origin)}
123
+ >
124
+ Forget current token
125
+ </button>
126
+ ) : null}
127
+ </div>
128
+ );
129
+ }
130
+
131
+ function CodeWindow({
132
+ file,
133
+ location,
134
+ }: {
135
+ file: SourceFile;
136
+ location: SourceLocation;
137
+ }) {
138
+ const rows = useMemo(
139
+ () => sourceWindow(file.content, location.line),
140
+ [file.content, location.line],
141
+ );
142
+ const highlighted = useMemo(
143
+ () => highlightSource(location.path, rows.map((row) => row.text).join("\n")),
144
+ [location.path, rows],
145
+ );
146
+ const focusedIndex = rows.findIndex((row) => row.focused);
147
+
148
+ const renderNode = (node: HighlightNode, key: string): ReactNode => {
149
+ if (node.type === "text") return node.value;
150
+ const names = node.properties?.className;
151
+ const className = Array.isArray(names)
152
+ ? names.filter((name): name is string => typeof name === "string").join(" ")
153
+ : typeof names === "string"
154
+ ? names
155
+ : undefined;
156
+ return (
157
+ <span key={key} className={className}>
158
+ {node.children.map((child, index) =>
159
+ renderNode(child, `${key}-${index}`),
160
+ )}
161
+ </span>
162
+ );
163
+ };
164
+
165
+ return (
166
+ <div
167
+ className="min-h-0 flex-1 overflow-auto bg-canvas py-2 text-[12px] leading-5"
168
+ tabIndex={0}
169
+ >
170
+ <div className="mono relative grid min-w-max grid-cols-[4rem_minmax(max-content,1fr)] text-ink">
171
+ {focusedIndex >= 0 ? (
172
+ <span
173
+ className="pointer-events-none absolute inset-x-0 h-5 bg-accent/10"
174
+ style={{ top: `${focusedIndex * 20}px` }}
175
+ aria-hidden
176
+ />
177
+ ) : null}
178
+ <div className="relative z-10">
179
+ {rows.map((row) => (
180
+ <div
181
+ key={row.number}
182
+ className={`h-5 select-none border-r pr-3 text-right ${row.focused ? "border-accent text-accent" : "border-line text-muted"}`}
183
+ aria-hidden
184
+ >
185
+ {row.number}
186
+ </div>
187
+ ))}
188
+ </div>
189
+ <pre className="source-code relative z-10 min-w-max pl-3 pr-4 text-ink">
190
+ <code>{highlighted.map((node, index) => renderNode(node, String(index)))}</code>
191
+ </pre>
192
+ </div>
193
+ </div>
194
+ );
195
+ }
196
+
197
+ function PreviewPanel({
198
+ id,
199
+ location,
200
+ pinned,
201
+ position,
202
+ panelRef,
203
+ onEnter,
204
+ onLeave,
205
+ onClose,
206
+ }: {
207
+ id: string;
208
+ location: SourceLocation;
209
+ pinned: boolean;
210
+ position: Position;
211
+ panelRef: React.RefObject<HTMLDivElement | null>;
212
+ onEnter: () => void;
213
+ onLeave: () => void;
214
+ onClose: () => void;
215
+ }) {
216
+ const access = useForgeAccess();
217
+ const token =
218
+ location.kind === "remote" ? access.tokenFor(location.origin) : "";
219
+ const source = useQuery(sourceCodeQuery(location, token));
220
+ const file = source.data ?? null;
221
+ const error = source.error;
222
+ const loading = source.isLoading;
223
+
224
+ const auth =
225
+ location.kind === "remote" &&
226
+ error instanceof SourceLoadError &&
227
+ error.authRequired;
228
+ const ref =
229
+ location.kind === "remote" ? location.ref.slice(0, 12) : "working tree";
230
+
231
+ return (
232
+ <div
233
+ id={id}
234
+ ref={panelRef}
235
+ role="dialog"
236
+ aria-label={`Source preview for ${location.path}`}
237
+ tabIndex={-1}
238
+ onPointerEnter={onEnter}
239
+ onPointerLeave={onLeave}
240
+ onKeyDown={(event) => {
241
+ if (event.key === "Escape") {
242
+ event.stopPropagation();
243
+ onClose();
244
+ }
245
+ }}
246
+ className="fixed z-[80] flex overflow-hidden rounded-control border border-line-strong bg-canvas shadow-md outline-none"
247
+ style={
248
+ {
249
+ position: "fixed",
250
+ zIndex: 80,
251
+ left: position.left,
252
+ top: position.top,
253
+ width: position.width,
254
+ maxHeight: position.maxHeight,
255
+ minHeight: 180,
256
+ } satisfies CSSProperties
257
+ }
258
+ >
259
+ <div className="flex min-h-0 min-w-0 flex-1 flex-col">
260
+ <div className="flex items-center gap-2 border-b border-line bg-surface px-3 py-2">
261
+ <Code2 size={15} className="shrink-0 text-accent" aria-hidden />
262
+ <span
263
+ className="mono min-w-0 flex-1 truncate text-xs text-ink"
264
+ title={location.path}
265
+ >
266
+ {location.path}
267
+ </span>
268
+ <span className="chip mono shrink-0">
269
+ {sourceLanguage(location.path)}
270
+ </span>
271
+ <span className="mono shrink-0 text-[11px] text-muted">{ref}</span>
272
+ {pinned ? (
273
+ <Pin
274
+ size={13}
275
+ className="shrink-0 text-accent"
276
+ aria-label="Preview pinned"
277
+ />
278
+ ) : null}
279
+ <EditorLink location={location} />
280
+ {location.href ? (
281
+ <a
282
+ href={location.href}
283
+ target="_blank"
284
+ rel="noreferrer"
285
+ className="rounded-control p-1 text-muted hover:bg-raised hover:text-accent"
286
+ aria-label="Open source on the forge"
287
+ title="Open on the forge"
288
+ >
289
+ <ExternalLink size={14} aria-hidden />
290
+ </a>
291
+ ) : null}
292
+ <button
293
+ type="button"
294
+ onClick={onClose}
295
+ className="rounded-control p-1 text-muted hover:bg-raised hover:text-ink"
296
+ aria-label="Close source preview"
297
+ >
298
+ <X size={14} aria-hidden />
299
+ </button>
300
+ </div>
301
+ {loading ? (
302
+ <div className="flex min-h-44 items-center justify-center gap-2 text-sm text-muted">
303
+ <LoaderCircle size={16} className="animate-spin" aria-hidden />{" "}
304
+ Loading source…
305
+ </div>
306
+ ) : auth ? (
307
+ <AccessForm location={location} message={error.message} />
308
+ ) : error ? (
309
+ <div className="p-4 text-sm text-muted">{error.message}</div>
310
+ ) : file ? (
311
+ <CodeWindow file={file} location={location} />
312
+ ) : null}
313
+ </div>
314
+ </div>
315
+ );
316
+ }
317
+
318
+ /** Hover/focus for a glance; click pins the same preview for scrolling and copying. */
319
+ export function SourcePreviewButton({
320
+ location,
321
+ className = "",
322
+ }: {
323
+ location: SourceLocation | null;
324
+ className?: string;
325
+ }) {
326
+ const [open, setOpen] = useState(false);
327
+ const [pinned, setPinned] = useState(false);
328
+ const [position, setPosition] = useState<Position | null>(null);
329
+ const triggerRef = useRef<HTMLButtonElement>(null);
330
+ const panelRef = useRef<HTMLDivElement>(null);
331
+ const pinnedRef = useRef(false);
332
+ const timer = useRef<ReturnType<typeof setTimeout> | null>(null);
333
+ const id = useMemo(
334
+ () => `source-preview-${Math.random().toString(36).slice(2)}`,
335
+ [],
336
+ );
337
+
338
+ const cancelTimer = useCallback(() => {
339
+ if (timer.current) clearTimeout(timer.current);
340
+ timer.current = null;
341
+ }, []);
342
+ const close = useCallback(() => {
343
+ cancelTimer();
344
+ pinnedRef.current = false;
345
+ setOpen(false);
346
+ setPinned(false);
347
+ }, [cancelTimer]);
348
+ const openSoon = useCallback(() => {
349
+ cancelTimer();
350
+ timer.current = setTimeout(() => setOpen(true), OPEN_DELAY_MS);
351
+ }, [cancelTimer]);
352
+ const closeSoon = useCallback(() => {
353
+ if (pinnedRef.current) return;
354
+ cancelTimer();
355
+ timer.current = setTimeout(() => setOpen(false), CLOSE_DELAY_MS);
356
+ }, [cancelTimer]);
357
+
358
+ useEffect(() => () => cancelTimer(), [cancelTimer]);
359
+
360
+ useLayoutEffect(() => {
361
+ if (!open || !triggerRef.current) return;
362
+ const place = () => {
363
+ if (triggerRef.current)
364
+ setPosition(positionFor(triggerRef.current.getBoundingClientRect()));
365
+ };
366
+ place();
367
+ window.addEventListener("resize", place);
368
+ window.addEventListener("scroll", place, true);
369
+ return () => {
370
+ window.removeEventListener("resize", place);
371
+ window.removeEventListener("scroll", place, true);
372
+ };
373
+ }, [open]);
374
+
375
+ useEffect(() => {
376
+ if (!pinned) return;
377
+ const outside = (event: globalThis.PointerEvent) => {
378
+ const node = event.target as Node;
379
+ if (
380
+ !triggerRef.current?.contains(node) &&
381
+ !panelRef.current?.contains(node)
382
+ )
383
+ close();
384
+ };
385
+ document.addEventListener("pointerdown", outside);
386
+ return () => document.removeEventListener("pointerdown", outside);
387
+ }, [close, pinned]);
388
+
389
+ if (!location) return null;
390
+
391
+ const hover = (event: ReactPointerEvent) => {
392
+ if (event.pointerType === "mouse" || event.pointerType === "pen")
393
+ openSoon();
394
+ };
395
+
396
+ return (
397
+ <>
398
+ <button
399
+ ref={triggerRef}
400
+ type="button"
401
+ aria-haspopup="dialog"
402
+ aria-expanded={open}
403
+ aria-controls={open ? id : undefined}
404
+ className={`mono inline-flex items-center gap-1 rounded-control text-accent hover:underline focus-visible:outline focus-visible:outline-1 focus-visible:outline-accent ${className}`}
405
+ title="Preview source · click to pin"
406
+ onPointerEnter={hover}
407
+ onPointerLeave={closeSoon}
408
+ onFocus={() => setOpen(true)}
409
+ onBlur={closeSoon}
410
+ onClick={() => {
411
+ cancelTimer();
412
+ if (open && pinned) {
413
+ close();
414
+ return;
415
+ }
416
+ setOpen(true);
417
+ pinnedRef.current = true;
418
+ setPinned(true);
419
+ setTimeout(() => panelRef.current?.focus(), 0);
420
+ }}
421
+ >
422
+ <Code2 size={13} aria-hidden /> preview
423
+ </button>
424
+ {open && position && typeof document !== "undefined"
425
+ ? createPortal(
426
+ <PreviewPanel
427
+ id={id}
428
+ location={location}
429
+ pinned={pinned}
430
+ position={position}
431
+ panelRef={panelRef}
432
+ onEnter={cancelTimer}
433
+ onLeave={closeSoon}
434
+ onClose={close}
435
+ />,
436
+ document.body,
437
+ )
438
+ : null}
439
+ </>
440
+ );
441
+ }
@@ -0,0 +1,182 @@
1
+ import { useCallback, useEffect, useId, useRef, useState } from "react";
2
+ import type { ReactNode } from "react";
3
+ import { Tab } from "@headlessui/react";
4
+ import { ChevronLeft, ChevronRight } from "lucide-react";
5
+ import { LayoutGroup, m, transitions } from "../lib/motion";
6
+
7
+ /**
8
+ * The strip a page's tab list scrolls inside.
9
+ *
10
+ * Eight tabs are wider than a phone and wider than a laptop with something
11
+ * beside it, and a plain flex row answers that by cutting the last of them off
12
+ * mid-word - which reads as a rendering fault, not as a row that continues.
13
+ * So the row scrolls, and says so: a fade at whichever edge still has tabs
14
+ * behind it, and a chevron on the fade for a reader who has no trackpad to
15
+ * flick with. Both edges are tested independently, because the row can be
16
+ * scrolled to the middle and be cut off at both ends at once.
17
+ *
18
+ * The fades are drawn in the header's own background, so the tabs pass under
19
+ * them rather than into a band of some other colour; they take no pointer
20
+ * events, and the chevrons on top of them take theirs back.
21
+ */
22
+ export function TabRow({
23
+ active,
24
+ children,
25
+ }: {
26
+ /** The selected tab. Changing it scrolls that tab back into view. */
27
+ active?: string;
28
+ children: ReactNode;
29
+ }) {
30
+ const ref = useRef<HTMLDivElement>(null);
31
+ // Namespaces the bar's layoutId to this strip, for a screen with two.
32
+ const group = useId();
33
+ const [edges, setEdges] = useState({ left: false, right: false });
34
+
35
+ const measure = useCallback(() => {
36
+ const el = ref.current;
37
+ if (!el) return;
38
+ // Sub-pixel layout leaves a fraction of overflow on a row that is not
39
+ // actually cut off, and a fade over nothing is a lie about the row. One
40
+ // pixel of slack at each end is what keeps the affordance honest.
41
+ const max = el.scrollWidth - el.clientWidth;
42
+ const left = el.scrollLeft > 1;
43
+ const right = el.scrollLeft < max - 1;
44
+ setEdges((was) =>
45
+ was.left === left && was.right === right ? was : { left, right },
46
+ );
47
+ }, []);
48
+
49
+ useEffect(() => {
50
+ const el = ref.current;
51
+ if (!el) return;
52
+ measure();
53
+ if (typeof ResizeObserver === "undefined") return;
54
+ // The strip's own width says how much room there is; the list inside it
55
+ // says how much is wanted, and that changes when the counts arrive.
56
+ const observer = new ResizeObserver(measure);
57
+ observer.observe(el);
58
+ if (el.firstElementChild) observer.observe(el.firstElementChild);
59
+ return () => observer.disconnect();
60
+ }, [measure]);
61
+
62
+ // A tab picked from the url can start life off the end of the strip, and a
63
+ // reader who cannot see the selected tab cannot tell which page they are on.
64
+ useEffect(() => {
65
+ const selected = ref.current?.querySelector('[aria-selected="true"]');
66
+ selected?.scrollIntoView({ block: "nearest", inline: "nearest" });
67
+ }, [active]);
68
+
69
+ const nudge = (direction: -1 | 1) => {
70
+ const el = ref.current;
71
+ if (!el) return;
72
+ el.scrollBy({
73
+ left: direction * Math.max(120, el.clientWidth * 0.6),
74
+ behavior: "smooth",
75
+ });
76
+ };
77
+
78
+ return (
79
+ <LayoutGroup id={group}>
80
+ <div className="relative">
81
+ {/* `layoutScroll`: the bar under the selected tab measures its trip
82
+ through this box, and the box scrolls the new tab into view in the
83
+ same breath. Without it the bar would start its slide from where
84
+ the old tab was before the scroll. */}
85
+ <m.div
86
+ ref={ref}
87
+ layoutScroll
88
+ onScroll={measure}
89
+ className="tab-scroll overflow-x-auto overscroll-x-contain"
90
+ >
91
+ {children}
92
+ </m.div>
93
+ <Edge side="left" show={edges.left} onClick={() => nudge(-1)} />
94
+ <Edge side="right" show={edges.right} onClick={() => nudge(1)} />
95
+ </div>
96
+ </LayoutGroup>
97
+ );
98
+ }
99
+
100
+ function Edge({
101
+ side,
102
+ show,
103
+ onClick,
104
+ }: {
105
+ side: "left" | "right";
106
+ show: boolean;
107
+ onClick: () => void;
108
+ }) {
109
+ const Icon = side === "left" ? ChevronLeft : ChevronRight;
110
+ return (
111
+ <div
112
+ aria-hidden
113
+ className={`pointer-events-none absolute inset-y-0 flex items-center transition-opacity ${
114
+ side === "left"
115
+ ? "left-0 justify-start bg-gradient-to-r"
116
+ : "right-0 justify-end bg-gradient-to-l"
117
+ } w-12 from-canvas to-transparent ${show ? "opacity-100" : "opacity-0"}`}
118
+ >
119
+ {/* Not a tab stop: everything behind the fade is reachable by tabbing
120
+ through the tabs themselves, which scrolls the strip on its own. The
121
+ chevron is for the pointer that has no wheel to spin sideways. */}
122
+ <button
123
+ type="button"
124
+ tabIndex={-1}
125
+ onClick={onClick}
126
+ disabled={!show}
127
+ className={`pointer-events-auto flex size-6 items-center justify-center rounded-control text-muted transition-colors hover:bg-surface hover:text-ink ${
128
+ show ? "" : "invisible"
129
+ }`}
130
+ >
131
+ <Icon size={14} />
132
+ </button>
133
+ </div>
134
+ );
135
+ }
136
+
137
+ /**
138
+ * The count beside a tab's name. A bare number ran straight into the label -
139
+ * "provides 6" read as one token at a glance - so it gets a box of its own:
140
+ * the same hairline the rest of the chrome is drawn in, tight enough that the
141
+ * tab does not grow a second word.
142
+ */
143
+ export function TabCount({ children }: { children: ReactNode }) {
144
+ return (
145
+ <span className="tnum ml-1.5 rounded-[4px] border px-1 border-line text-muted">
146
+ {children}
147
+ </span>
148
+ );
149
+ }
150
+
151
+ /**
152
+ * A tab. The same on every page that has tabs, and the strip's one moving
153
+ * part: the accent bar under the selected tab is a single element that slides
154
+ * from the old tab to the new, not a border each tab paints for itself. Every
155
+ * tab keeps a transparent 2px border so the bar has a slot to sit in and
156
+ * selecting one never nudges the text.
157
+ */
158
+ export function TabButton({ children }: { children: ReactNode }) {
159
+ return (
160
+ <Tab
161
+ className={({ selected }) =>
162
+ `mono relative shrink-0 rounded-t-control border-b-2 border-transparent px-3 py-1.5 t-micro transition-colors focus:outline-none ${
163
+ selected ? "text-ink" : "text-muted hover:text-ink"
164
+ }`
165
+ }
166
+ >
167
+ {({ selected }) => (
168
+ <>
169
+ {children}
170
+ {selected ? (
171
+ <m.span
172
+ layoutId="tab-bar"
173
+ aria-hidden
174
+ className="pointer-events-none absolute inset-x-0 -bottom-0.5 h-0.5 bg-accent"
175
+ transition={transitions.settle}
176
+ />
177
+ ) : null}
178
+ </>
179
+ )}
180
+ </Tab>
181
+ );
182
+ }