@shortlink-org/portolan 0.1.0 → 0.2.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 (381) hide show
  1. package/README.md +69 -205
  2. package/cli/init.mjs +285 -0
  3. package/cli/init.test.mjs +158 -0
  4. package/cli/portolan.mjs +37 -73
  5. package/index.html +19 -0
  6. package/package.json +5 -10
  7. package/plugins/README.md +66 -27
  8. package/plugins/portolan-go.wasm +0 -0
  9. package/portolan.json +52 -73
  10. package/public/landing/cats/bug-hunter.webp +0 -0
  11. package/public/landing/cats/compass-nap.webp +0 -0
  12. package/public/landing/cats/diagrammer.webp +0 -0
  13. package/public/landing/cats/docs-reader.webp +0 -0
  14. package/public/landing/cats/laptop.webp +0 -0
  15. package/public/landing/cats/map-inspector.webp +0 -0
  16. package/public/landing/cats/server-break.webp +0 -0
  17. package/public/landing/cats/star-mapper.webp +0 -0
  18. package/public/landing/cats/system-builder.webp +0 -0
  19. package/public/landing/cats/thread-tangle.webp +0 -0
  20. package/public/og.png +0 -0
  21. package/public/readme-header.webp +0 -0
  22. package/schema/portolan.schema.json +180 -0
  23. package/scripts/builtin-plugins.mjs +14 -0
  24. package/scripts/delivery-presets.mjs +353 -0
  25. package/scripts/gen.mjs +37 -4
  26. package/scripts/history.mjs +126 -0
  27. package/scripts/history.test.mjs +110 -0
  28. package/scripts/host-plugins/fetch-bsr.mjs +351 -0
  29. package/{plugins/fetch-bsr/options.schema.json → scripts/host-plugins/fetch-bsr.options.json} +4 -0
  30. package/scripts/host-plugins/fetch-bsr.test.mjs +212 -0
  31. package/scripts/host-plugins/fetch-csr.mjs +386 -0
  32. package/scripts/host-plugins/fetch-csr.test.mjs +178 -0
  33. package/scripts/host-plugins/fetch-git.mjs +379 -0
  34. package/scripts/host-plugins/fetch-git.test.mjs +210 -0
  35. package/scripts/local-api.mjs +22 -4
  36. package/scripts/local-api.test.mjs +84 -2
  37. package/scripts/manifest.mjs +3 -2
  38. package/scripts/package-smoke.mjs +6 -2
  39. package/scripts/plugin-host.mjs +77 -18
  40. package/scripts/plugin-host.test.mjs +50 -1
  41. package/scripts/plugin-wasm-worker.mjs +6 -3
  42. package/scripts/run-builtin.mjs +6 -26
  43. package/scripts/schema.mjs +6 -1
  44. package/src/app/App.tsx +24 -429
  45. package/src/app/Breadcrumbs.test.ts +7 -0
  46. package/src/app/Breadcrumbs.tsx +15 -2
  47. package/src/app/CatalogApp.tsx +436 -0
  48. package/src/app/Sidebar.tsx +0 -1
  49. package/src/chat/ChatPanel.tsx +98 -41
  50. package/src/chat/Composer.tsx +4 -2
  51. package/src/chat/Conversation.tsx +79 -15
  52. package/src/chat/Header.tsx +43 -9
  53. package/src/chat/Starter.tsx +64 -3
  54. package/src/chat/Steps.tsx +1 -1
  55. package/src/chat/flags.test.ts +51 -14
  56. package/src/chat/flags.ts +8 -2
  57. package/src/chat/page-context.test.ts +51 -0
  58. package/src/chat/page-context.ts +128 -0
  59. package/src/chat/prompt.test.ts +49 -1
  60. package/src/chat/prompt.ts +42 -1
  61. package/src/chat/tools.ts +7 -2
  62. package/src/chat/transport.ts +16 -4
  63. package/src/components/ProblemRow.tsx +182 -0
  64. package/src/data.ts +20 -5
  65. package/src/er/ErCanvas.tsx +1 -1
  66. package/src/graph/DependencyGraph.tsx +10 -1
  67. package/src/graph/FocusedEventGraph.tsx +1 -1
  68. package/src/graph/GraphToolbar.tsx +2 -4
  69. package/src/index.css +220 -2
  70. package/src/landing/DraggableReveal.tsx +168 -0
  71. package/src/landing/EstateGraph.tsx +28 -0
  72. package/src/landing/FlowPlayback.tsx +282 -0
  73. package/src/landing/HeroMap.tsx +128 -0
  74. package/src/landing/LandingPage.tsx +757 -0
  75. package/src/landing/ProductFrame.tsx +68 -0
  76. package/src/landing/ProductTour.tsx +371 -0
  77. package/src/landing/catalog.ts +25 -0
  78. package/src/landing/motion.tsx +52 -0
  79. package/src/lib/all-problems.ts +28 -0
  80. package/src/lib/local-api.ts +35 -1
  81. package/src/lib/setup-info.test.ts +8 -0
  82. package/src/lib/setup-info.ts +3 -2
  83. package/src/lib/source-code.ts +2 -1
  84. package/src/likec4/C4View.tsx +5 -0
  85. package/src/likec4/InteractiveView.tsx +5 -1
  86. package/src/map/ContextMapGraph.tsx +47 -20
  87. package/src/pages/ContextMap.tsx +5 -1
  88. package/src/pages/FlowDetail.tsx +24 -7
  89. package/src/pages/Overview.tsx +68 -12
  90. package/src/pages/Problems.tsx +6 -193
  91. package/src/pages/Settings.tsx +133 -108
  92. package/src/pages/settings/DeliverySettings.tsx +229 -0
  93. package/src/pages/settings/PreferencesSettings.tsx +101 -0
  94. package/src/routes.test.ts +20 -0
  95. package/src/routes.ts +13 -1
  96. package/vite.config.ts +12 -8
  97. package/catalog/enum_test.go +0 -46
  98. package/catalog/model.go +0 -932
  99. package/catalog/roundtrip_test.go +0 -185
  100. package/catalog/via_test.go +0 -38
  101. package/go.mod +0 -14
  102. package/go.sum +0 -14
  103. package/internal/commands/cargo.go +0 -154
  104. package/internal/commands/commands.go +0 -143
  105. package/internal/commands/commands_test.go +0 -323
  106. package/internal/commands/gradle.go +0 -77
  107. package/internal/commands/justfile.go +0 -82
  108. package/internal/commands/makefile.go +0 -106
  109. package/internal/commands/maven.go +0 -93
  110. package/internal/commands/packagejson.go +0 -159
  111. package/internal/commands/pyproject.go +0 -169
  112. package/internal/commands/taskfile.go +0 -101
  113. package/internal/commands/testdata/estate/.cargo/config.toml +0 -7
  114. package/internal/commands/testdata/estate/Makefile +0 -33
  115. package/internal/commands/testdata/estate/Taskfile.yml +0 -21
  116. package/internal/commands/testdata/estate/build.gradle.kts +0 -21
  117. package/internal/commands/testdata/estate/justfile +0 -20
  118. package/internal/commands/testdata/estate/package.json +0 -13
  119. package/internal/commands/testdata/estate/pom.xml +0 -29
  120. package/internal/commands/testdata/estate/pyproject.toml +0 -27
  121. package/internal/commands/testdata/estate/xtask/src/main.rs +0 -27
  122. package/internal/commands/testdata/golden/commands.json +0 -305
  123. package/internal/gohttp/analyze.go +0 -2500
  124. package/internal/gohttp/endpoints.go +0 -1067
  125. package/internal/gohttp/roots.go +0 -320
  126. package/internal/gohttp/typed.go +0 -143
  127. package/internal/goscan/constants.go +0 -85
  128. package/internal/goscan/goscan_test.go +0 -227
  129. package/internal/goscan/names.go +0 -52
  130. package/internal/goscan/parse_test.go +0 -11
  131. package/internal/goscan/source.go +0 -36
  132. package/internal/goscan/tree.go +0 -155
  133. package/internal/goscan/types.go +0 -99
  134. package/internal/wsdl/ids.go +0 -127
  135. package/internal/wsdl/ids_test.go +0 -21
  136. package/internal/wsdl/model.go +0 -62
  137. package/internal/wsdl/parse.go +0 -920
  138. package/internal/wsdl/parse_test.go +0 -133
  139. package/plugin/describe.go +0 -107
  140. package/plugin/describe_test.go +0 -114
  141. package/plugin/protocol.go +0 -117
  142. package/plugin/schematest/schematest.go +0 -126
  143. package/plugins/extract-adr/describe.go +0 -19
  144. package/plugins/extract-adr/describe_test.go +0 -11
  145. package/plugins/extract-adr/extract.go +0 -153
  146. package/plugins/extract-adr/extract_test.go +0 -350
  147. package/plugins/extract-adr/history.go +0 -99
  148. package/plugins/extract-adr/main.go +0 -65
  149. package/plugins/extract-adr/parse.go +0 -642
  150. package/plugins/extract-adr/parse_test.go +0 -405
  151. package/plugins/extract-asyncapi/describe.go +0 -19
  152. package/plugins/extract-asyncapi/describe_test.go +0 -11
  153. package/plugins/extract-asyncapi/extract.go +0 -315
  154. package/plugins/extract-asyncapi/extract_test.go +0 -197
  155. package/plugins/extract-asyncapi/main.go +0 -48
  156. package/plugins/extract-asyncapi/spec.go +0 -151
  157. package/plugins/extract-commands/describe.go +0 -19
  158. package/plugins/extract-commands/describe_test.go +0 -11
  159. package/plugins/extract-commands/extract.go +0 -79
  160. package/plugins/extract-commands/extract_test.go +0 -92
  161. package/plugins/extract-commands/main.go +0 -55
  162. package/plugins/extract-csr/avro.go +0 -258
  163. package/plugins/extract-csr/describe.go +0 -19
  164. package/plugins/extract-csr/describe_test.go +0 -11
  165. package/plugins/extract-csr/extract.go +0 -338
  166. package/plugins/extract-csr/extract_test.go +0 -374
  167. package/plugins/extract-csr/jsonschema.go +0 -343
  168. package/plugins/extract-csr/lock.go +0 -25
  169. package/plugins/extract-csr/main.go +0 -84
  170. package/plugins/extract-csr/subject.go +0 -102
  171. package/plugins/extract-flows/describe.go +0 -19
  172. package/plugins/extract-flows/describe_test.go +0 -11
  173. package/plugins/extract-flows/extract.go +0 -91
  174. package/plugins/extract-flows/main.go +0 -45
  175. package/plugins/extract-flows/parse.go +0 -593
  176. package/plugins/extract-flows/parse_test.go +0 -204
  177. package/plugins/extract-glossary/describe.go +0 -19
  178. package/plugins/extract-glossary/describe_test.go +0 -11
  179. package/plugins/extract-glossary/extract.go +0 -115
  180. package/plugins/extract-glossary/extract_test.go +0 -220
  181. package/plugins/extract-glossary/main.go +0 -59
  182. package/plugins/extract-glossary/parse.go +0 -214
  183. package/plugins/extract-glossary/parse_test.go +0 -203
  184. package/plugins/extract-go/aggregate.go +0 -214
  185. package/plugins/extract-go/client.go +0 -409
  186. package/plugins/extract-go/client_test.go +0 -266
  187. package/plugins/extract-go/describe.go +0 -19
  188. package/plugins/extract-go/describe_test.go +0 -11
  189. package/plugins/extract-go/enum.go +0 -195
  190. package/plugins/extract-go/enum_test.go +0 -82
  191. package/plugins/extract-go/event.go +0 -99
  192. package/plugins/extract-go/extract.go +0 -191
  193. package/plugins/extract-go/extract_test.go +0 -261
  194. package/plugins/extract-go/flow.go +0 -1441
  195. package/plugins/extract-go/flow_test.go +0 -609
  196. package/plugins/extract-go/httpclient.go +0 -174
  197. package/plugins/extract-go/httpclient_test.go +0 -296
  198. package/plugins/extract-go/ids.go +0 -92
  199. package/plugins/extract-go/layout.go +0 -225
  200. package/plugins/extract-go/layout_test.go +0 -125
  201. package/plugins/extract-go/lifecycle.go +0 -324
  202. package/plugins/extract-go/lifecycle_test.go +0 -108
  203. package/plugins/extract-go/main.go +0 -79
  204. package/plugins/extract-go/operation.go +0 -157
  205. package/plugins/extract-go/source.go +0 -316
  206. package/plugins/extract-go/transport.go +0 -321
  207. package/plugins/extract-go/transport_test.go +0 -145
  208. package/plugins/extract-go/wiring.go +0 -434
  209. package/plugins/extract-go-nats/describe.go +0 -19
  210. package/plugins/extract-go-nats/describe_test.go +0 -11
  211. package/plugins/extract-go-nats/extract.go +0 -177
  212. package/plugins/extract-go-nats/extract_test.go +0 -389
  213. package/plugins/extract-go-nats/index.go +0 -379
  214. package/plugins/extract-go-nats/main.go +0 -42
  215. package/plugins/extract-go-nats/resolve.go +0 -161
  216. package/plugins/extract-go-nats/sites.go +0 -224
  217. package/plugins/extract-graphql/describe.go +0 -19
  218. package/plugins/extract-graphql/describe_test.go +0 -11
  219. package/plugins/extract-graphql/extract.go +0 -433
  220. package/plugins/extract-graphql/extract_test.go +0 -256
  221. package/plugins/extract-graphql/ids.go +0 -49
  222. package/plugins/extract-graphql/lex.go +0 -237
  223. package/plugins/extract-graphql/main.go +0 -51
  224. package/plugins/extract-graphql/parse.go +0 -621
  225. package/plugins/extract-graphql/parse_test.go +0 -122
  226. package/plugins/extract-http-clients/describe.go +0 -19
  227. package/plugins/extract-http-clients/describe_test.go +0 -11
  228. package/plugins/extract-http-clients/extract.go +0 -705
  229. package/plugins/extract-http-clients/extract_test.go +0 -1263
  230. package/plugins/extract-http-clients/main.go +0 -42
  231. package/plugins/extract-openapi/describe.go +0 -19
  232. package/plugins/extract-openapi/describe_test.go +0 -11
  233. package/plugins/extract-openapi/discover.go +0 -243
  234. package/plugins/extract-openapi/extract.go +0 -526
  235. package/plugins/extract-openapi/extract_test.go +0 -545
  236. package/plugins/extract-openapi/main.go +0 -75
  237. package/plugins/extract-openapi/spec.go +0 -350
  238. package/plugins/extract-project/describe.go +0 -19
  239. package/plugins/extract-project/describe_test.go +0 -11
  240. package/plugins/extract-project/extract.go +0 -221
  241. package/plugins/extract-project/extract_test.go +0 -109
  242. package/plugins/extract-project/main.go +0 -41
  243. package/plugins/extract-proto/ast.go +0 -125
  244. package/plugins/extract-proto/consumes.go +0 -77
  245. package/plugins/extract-proto/describe.go +0 -19
  246. package/plugins/extract-proto/extract.go +0 -293
  247. package/plugins/extract-proto/extract_test.go +0 -459
  248. package/plugins/extract-proto/ids.go +0 -89
  249. package/plugins/extract-proto/ids_test.go +0 -57
  250. package/plugins/extract-proto/lex.go +0 -285
  251. package/plugins/extract-proto/main.go +0 -100
  252. package/plugins/extract-proto/module.go +0 -120
  253. package/plugins/extract-proto/parse.go +0 -720
  254. package/plugins/extract-proto/parse_test.go +0 -307
  255. package/plugins/extract-proto/provides.go +0 -236
  256. package/plugins/extract-proto/resolve.go +0 -222
  257. package/plugins/extract-proto/resolve_test.go +0 -119
  258. package/plugins/extract-redis/describe.go +0 -19
  259. package/plugins/extract-redis/describe_test.go +0 -11
  260. package/plugins/extract-redis/extract.go +0 -183
  261. package/plugins/extract-redis/extract_test.go +0 -168
  262. package/plugins/extract-redis/keyspaces.go +0 -469
  263. package/plugins/extract-redis/main.go +0 -44
  264. package/plugins/extract-river/describe.go +0 -19
  265. package/plugins/extract-river/describe_test.go +0 -11
  266. package/plugins/extract-river/extract.go +0 -507
  267. package/plugins/extract-river/extract_test.go +0 -132
  268. package/plugins/extract-river/main.go +0 -42
  269. package/plugins/extract-sql/ddl.go +0 -893
  270. package/plugins/extract-sql/ddl_test.go +0 -401
  271. package/plugins/extract-sql/describe.go +0 -19
  272. package/plugins/extract-sql/describe_test.go +0 -11
  273. package/plugins/extract-sql/layout.go +0 -221
  274. package/plugins/extract-sql/layout_test.go +0 -55
  275. package/plugins/extract-sql/lineage.go +0 -107
  276. package/plugins/extract-sql/main.go +0 -141
  277. package/plugins/extract-sql/maps.go +0 -564
  278. package/plugins/extract-sql/maps_java.go +0 -117
  279. package/plugins/extract-sql/maps_rust.go +0 -333
  280. package/plugins/extract-sql/maps_rust_test.go +0 -70
  281. package/plugins/extract-sql/maps_test.go +0 -204
  282. package/plugins/extract-sql/maps_ts.go +0 -398
  283. package/plugins/extract-sql/maps_ts_test.go +0 -136
  284. package/plugins/extract-sql/projection.go +0 -70
  285. package/plugins/extract-sql/projection_test.go +0 -50
  286. package/plugins/extract-sql/store.go +0 -420
  287. package/plugins/extract-sql/store_test.go +0 -233
  288. package/plugins/extract-sql/view.go +0 -295
  289. package/plugins/extract-watermill/describe.go +0 -19
  290. package/plugins/extract-watermill/describe_test.go +0 -11
  291. package/plugins/extract-watermill/extract.go +0 -1228
  292. package/plugins/extract-watermill/extract_test.go +0 -234
  293. package/plugins/extract-watermill/main.go +0 -41
  294. package/plugins/extract-wsdl/describe.go +0 -19
  295. package/plugins/extract-wsdl/describe_test.go +0 -11
  296. package/plugins/extract-wsdl/extract.go +0 -148
  297. package/plugins/extract-wsdl/extract_test.go +0 -45
  298. package/plugins/extract-wsdl/main.go +0 -53
  299. package/plugins/fetch-bsr/auth.go +0 -114
  300. package/plugins/fetch-bsr/bsr.go +0 -240
  301. package/plugins/fetch-bsr/cache.go +0 -66
  302. package/plugins/fetch-bsr/describe.go +0 -19
  303. package/plugins/fetch-bsr/fetch.go +0 -192
  304. package/plugins/fetch-bsr/fetch_test.go +0 -465
  305. package/plugins/fetch-bsr/lock.go +0 -71
  306. package/plugins/fetch-bsr/main.go +0 -117
  307. package/plugins/fetch-csr/auth.go +0 -95
  308. package/plugins/fetch-csr/cache.go +0 -69
  309. package/plugins/fetch-csr/describe.go +0 -19
  310. package/plugins/fetch-csr/describe_test.go +0 -11
  311. package/plugins/fetch-csr/fetch.go +0 -254
  312. package/plugins/fetch-csr/fetch_test.go +0 -494
  313. package/plugins/fetch-csr/lock.go +0 -95
  314. package/plugins/fetch-csr/main.go +0 -122
  315. package/plugins/fetch-csr/registry.go +0 -209
  316. package/plugins/fetch-git/cache.go +0 -65
  317. package/plugins/fetch-git/describe.go +0 -19
  318. package/plugins/fetch-git/describe_test.go +0 -11
  319. package/plugins/fetch-git/fetch.go +0 -181
  320. package/plugins/fetch-git/fetch_test.go +0 -332
  321. package/plugins/fetch-git/git.go +0 -169
  322. package/plugins/fetch-git/lock.go +0 -72
  323. package/plugins/fetch-git/main.go +0 -127
  324. package/plugins/fetch-git/offline.go +0 -39
  325. package/plugins/fetch-git/pin.go +0 -90
  326. package/plugins/fetch-git/pin_test.go +0 -74
  327. package/plugins/gen-backstage/describe.go +0 -17
  328. package/plugins/gen-backstage/main.go +0 -22
  329. package/plugins/gen-backstage/plugin.go +0 -473
  330. package/plugins/gen-backstage/plugin_test.go +0 -145
  331. package/plugins/gen-backstage.wasm +0 -0
  332. package/plugins/gen-markdown/adr.go +0 -162
  333. package/plugins/gen-markdown/aggregate.go +0 -362
  334. package/plugins/gen-markdown/canonical.go +0 -93
  335. package/plugins/gen-markdown/context.go +0 -90
  336. package/plugins/gen-markdown/coverage_test.go +0 -89
  337. package/plugins/gen-markdown/describe.go +0 -19
  338. package/plugins/gen-markdown/describe_test.go +0 -11
  339. package/plugins/gen-markdown/external.go +0 -71
  340. package/plugins/gen-markdown/flow.go +0 -277
  341. package/plugins/gen-markdown/glossary.go +0 -76
  342. package/plugins/gen-markdown/glossary_test.go +0 -148
  343. package/plugins/gen-markdown/llms.go +0 -302
  344. package/plugins/gen-markdown/main.go +0 -22
  345. package/plugins/gen-markdown/markdown.go +0 -254
  346. package/plugins/gen-markdown/markdown_test.go +0 -100
  347. package/plugins/gen-markdown/module.go +0 -107
  348. package/plugins/gen-markdown/plugin.go +0 -39
  349. package/plugins/gen-markdown/quality_test.go +0 -176
  350. package/plugins/gen-markdown/redis_test.go +0 -38
  351. package/plugins/gen-markdown/render.go +0 -344
  352. package/plugins/gen-markdown/render_test.go +0 -186
  353. package/plugins/gen-markdown/service.go +0 -456
  354. package/plugins/gen-markdown/source.go +0 -177
  355. package/plugins/gen-markdown/store.go +0 -201
  356. package/plugins/gen-markdown.wasm +0 -0
  357. package/plugins/gen-mermaid/describe.go +0 -17
  358. package/plugins/gen-mermaid/main.go +0 -22
  359. package/plugins/gen-mermaid/plugin.go +0 -104
  360. package/plugins/gen-mermaid/plugin_test.go +0 -33
  361. package/plugins/gen-mermaid.wasm +0 -0
  362. package/plugins/openapi/ids.go +0 -261
  363. package/plugins/openapi/ids_test.go +0 -98
  364. package/plugins/verify-codeowners/describe.go +0 -19
  365. package/plugins/verify-codeowners/describe_test.go +0 -11
  366. package/plugins/verify-codeowners/main.go +0 -75
  367. package/plugins/verify-codeowners/match.go +0 -85
  368. package/plugins/verify-codeowners/match_test.go +0 -47
  369. package/plugins/verify-codeowners/owners.go +0 -164
  370. package/plugins/verify-codeowners/owners_test.go +0 -225
  371. package/plugins/verify-codeowners/parse.go +0 -90
  372. package/plugins/verify-codeowners/parse_test.go +0 -62
  373. package/plugins/verify-otel/describe.go +0 -19
  374. package/plugins/verify-otel/describe_test.go +0 -11
  375. package/plugins/verify-otel/main.go +0 -53
  376. package/plugins/verify-otel/match.go +0 -336
  377. package/plugins/verify-otel/otlp.go +0 -200
  378. package/plugins/verify-otel/verify.go +0 -734
  379. package/plugins/verify-otel/verify_test.go +0 -460
  380. /package/{plugins/fetch-csr/options.schema.json → scripts/host-plugins/fetch-csr.options.json} +0 -0
  381. /package/{plugins/fetch-git/options.schema.json → scripts/host-plugins/fetch-git.options.json} +0 -0
@@ -0,0 +1,436 @@
1
+ import { Suspense, lazy, useCallback, useEffect, useState } from "react";
2
+ import {
3
+ Navigate,
4
+ Route,
5
+ Routes,
6
+ useLocation,
7
+ useNavigate,
8
+ } from "react-router";
9
+ import type { Location } from "react-router";
10
+ import {
11
+ Panel,
12
+ ResizeHandle,
13
+ SavedGroup,
14
+ useCanvasResize,
15
+ usePanelRef,
16
+ } from "./panels";
17
+ import { FlowDetail } from "../pages/FlowDetail";
18
+ import { FlowIndex } from "../pages/FlowIndex";
19
+ import { AdrIndex } from "../pages/AdrIndex";
20
+ import { Language } from "../pages/Language";
21
+ import { AdrDetail } from "../pages/AdrDetail";
22
+ import { Overview } from "../pages/Overview";
23
+ import { ContextMap } from "../pages/ContextMap";
24
+ import { ContextPage } from "../pages/ContextPage";
25
+ import { ServicePage } from "../pages/ServicePage";
26
+ import { AggregatePage } from "../pages/AggregatePage";
27
+ import { BlockPage } from "../pages/BlockPage";
28
+ import { EnumPage } from "../pages/EnumPage";
29
+ import { EventPage } from "../pages/EventPage";
30
+ import { StorePage } from "../pages/StorePage";
31
+ import { GraphPage } from "../pages/GraphPage";
32
+ import { Problems } from "../pages/Problems";
33
+ import { Settings } from "../pages/Settings";
34
+ import { Changes } from "../pages/Changes";
35
+ import { RegistryIndex } from "../pages/RegistryIndex";
36
+ import { ModulePage } from "../pages/ModulePage";
37
+ import { ExternalPage } from "../pages/ExternalPage";
38
+ import { NotFoundPage } from "../pages/NotFound";
39
+ import { CatalogFailure } from "../pages/CatalogFailure";
40
+ import { activeCatalogProfile, catalogError } from "../data";
41
+ import { SidePanel } from "../components/Overlay";
42
+ import { Empty } from "../components/PageHeader";
43
+ import { WithDetail } from "../selection/DetailPanel";
44
+ import { SelectionSync } from "../selection/sync";
45
+ import { Trail } from "../trail/Trail";
46
+ import { TrailRecorder } from "../trail/record";
47
+ import { HashScroll } from "./HashScroll";
48
+ import { CommandPalette } from "./CommandPalette";
49
+ import { Sidebar } from "./Sidebar";
50
+ import { TopBar } from "./TopBar";
51
+ import { DensityProvider } from "./density";
52
+ import { useNarrow, usePhone } from "./responsive";
53
+ import { ShortcutsSheet, useShortcuts } from "./shortcuts";
54
+ import { Toaster } from "./toast";
55
+ import { PeekLayer } from "../peek/PeekLayer";
56
+ import { useUiStore } from "./ui-store";
57
+ import { ForgeAccessProvider } from "./forge-access";
58
+ import { QueryClientProvider } from "@tanstack/react-query";
59
+ import { queryClient } from "./query-client";
60
+ import { AnimatePresence, m, page } from "../lib/motion";
61
+ import { useChatUi } from "../chat/store";
62
+ import { projectPreview } from "../lib/project-preview";
63
+
64
+ // The chat is a chunk of its own, and a build with VITE_CHAT=off has no such
65
+ // chunk: the test is on the literal Vite substitutes, so the import below is
66
+ // dead code to the bundler and is dropped with everything it would pull in.
67
+ const ChatPanel =
68
+ import.meta.env.VITE_CHAT !== "off"
69
+ ? lazy(() => import("../chat/ChatPanel"))
70
+ : null;
71
+
72
+ /**
73
+ * What the click shows while that chunk is on its way: the same sheet, at the
74
+ * same width, with a word in it. A fallback of nothing meant the first "ask"
75
+ * on a slow line did nothing for a second and the reader clicked again.
76
+ */
77
+ function ChatLoading() {
78
+ const phone = usePhone();
79
+ return (
80
+ <SidePanel
81
+ open
82
+ onClose={() => useChatUi.getState().setOpen(false)}
83
+ side="right"
84
+ label="Ask the catalog"
85
+ width={phone ? "100vw" : "min(560px,92vw)"}
86
+ >
87
+ <div className="h-full bg-canvas p-gutter text-ink">
88
+ <Empty>loading the chat…</Empty>
89
+ </div>
90
+ </SidePanel>
91
+ );
92
+ }
93
+
94
+ /**
95
+ * Every route, once. Rendered inside a pane on wide layouts and alone below.
96
+ *
97
+ * The location is a prop, not read from the router: while a page is on its
98
+ * way out the router already says the next address, and the page leaving
99
+ * must keep drawing the one it was.
100
+ */
101
+ function AppRoutes({ location }: { location: Location }) {
102
+ return (
103
+ <Routes location={location}>
104
+ <Route path="/" element={<Overview />} />
105
+ <Route path="/flows" element={<FlowIndex />} />
106
+ <Route
107
+ path="/flows/:flow"
108
+ element={
109
+ <WithDetail id="flow">
110
+ <FlowDetail />
111
+ </WithDetail>
112
+ }
113
+ />
114
+ <Route path="/adrs" element={<AdrIndex />} />
115
+ <Route path="/language" element={<Language />} />
116
+ <Route path="/adrs/:adr" element={<AdrDetail />} />
117
+ <Route path="/problems" element={<Problems />} />
118
+ <Route path="/settings/*" element={<Settings />} />
119
+ <Route path="/changes" element={<Changes />} />
120
+ <Route path="/externals/:external" element={<ExternalPage />} />
121
+ {/* A module sits at the estate level, not under a service: it is
122
+ published by one and read by four, so hanging it off a service would
123
+ put one entity at four URLs. */}
124
+ <Route path="/registry" element={<RegistryIndex />} />
125
+ <Route
126
+ path="/registry/:module"
127
+ element={
128
+ <WithDetail id="module">
129
+ <ModulePage />
130
+ </WithDetail>
131
+ }
132
+ />
133
+ <Route
134
+ path="/map"
135
+ element={
136
+ <WithDetail id="map">
137
+ <ContextMap />
138
+ </WithDetail>
139
+ }
140
+ />
141
+ <Route
142
+ path="/c/:context"
143
+ element={
144
+ <WithDetail id="context">
145
+ <ContextPage />
146
+ </WithDetail>
147
+ }
148
+ />
149
+ <Route
150
+ path="/c/:context/:service"
151
+ element={
152
+ <WithDetail id="service">
153
+ <ServicePage />
154
+ </WithDetail>
155
+ }
156
+ />
157
+ {/* Before the aggregate routes: "data" is a literal sitting where an
158
+ aggregate slug would, and "/c/x/y/data/z" would otherwise be read as
159
+ the event "z" of an aggregate called "data". */}
160
+ <Route
161
+ path="/c/:context/:service/data/:store"
162
+ element={
163
+ <WithDetail id="store">
164
+ <StorePage />
165
+ </WithDetail>
166
+ }
167
+ />
168
+ <Route
169
+ path="/c/:context/:service/:aggregate"
170
+ element={<AggregatePage />}
171
+ />
172
+ {/* The two literal segments come first: a block page must not be
173
+ read as an event whose slug happens to be "vo". */}
174
+ <Route
175
+ path="/c/:context/:service/:aggregate/vo/:block"
176
+ element={<BlockPage kind="vo" />}
177
+ />
178
+ <Route
179
+ path="/c/:context/:service/:aggregate/entity/:block"
180
+ element={<BlockPage kind="entity" />}
181
+ />
182
+ <Route
183
+ path="/c/:context/:service/:aggregate/enum/:enum"
184
+ element={<EnumPage />}
185
+ />
186
+ <Route
187
+ path="/c/:context/:service/:aggregate/:event"
188
+ element={
189
+ <WithDetail id="event">
190
+ <EventPage />
191
+ </WithDetail>
192
+ }
193
+ />
194
+ <Route
195
+ path="/graph"
196
+ element={
197
+ <WithDetail id="graph">
198
+ <GraphPage />
199
+ </WithDetail>
200
+ }
201
+ />
202
+ <Route path="/index.html" element={<Navigate to="/" replace />} />
203
+ <Route path="*" element={<NotFoundPage />} />
204
+ </Routes>
205
+ );
206
+ }
207
+
208
+ /**
209
+ * The catalog tree as an overlay, below the narrow breakpoint. It is the same
210
+ * Sidebar; only the box around it changes, so nothing about the tree has two
211
+ * implementations.
212
+ */
213
+ function SidebarDrawer() {
214
+ const open = useUiStore((s) => s.drawer);
215
+ const setDrawer = useUiStore((s) => s.setDrawer);
216
+ const { pathname } = useLocation();
217
+
218
+ // Navigating is what the tree is for, so a click that moves the reader ends
219
+ // the drawer's job. Without this the page they asked for is behind a scrim.
220
+ useEffect(() => setDrawer(false), [pathname, setDrawer]);
221
+
222
+ return (
223
+ <SidePanel
224
+ open={open}
225
+ onClose={() => setDrawer(false)}
226
+ side="left"
227
+ label="Catalog"
228
+ width="min(320px,85vw)"
229
+ >
230
+ <Sidebar />
231
+ </SidePanel>
232
+ );
233
+ }
234
+
235
+ function Shell() {
236
+ const [palette, setPalette] = useState(false);
237
+ const [help, setHelp] = useState(false);
238
+ const chatOpen = useChatUi((s) => s.open);
239
+ const [railed, setRailed] = useState(false);
240
+ const location = useLocation();
241
+ const navigate = useNavigate();
242
+ const { pathname } = location;
243
+ const settle = useCanvasResize();
244
+ const sidebarRef = usePanelRef();
245
+ const narrow = useNarrow();
246
+ const toggleDrawer = useUiStore((s) => s.toggleDrawer);
247
+ const setDrawer = useUiStore((s) => s.setDrawer);
248
+ const toggleDetail = useUiStore((s) => s.toggleDetail);
249
+ const revealNonce = useUiStore((s) => s.revealNonce);
250
+
251
+ // Keep the selected catalog shareable even though most route builders do
252
+ // not know about presentation state. Changing the profile itself reloads
253
+ // the bundle; ordinary navigation only restores its URL marker.
254
+ useEffect(() => {
255
+ const params = new URLSearchParams(location.search);
256
+ if (params.get("catalog") === activeCatalogProfile.id) return;
257
+ params.set("catalog", activeCatalogProfile.id);
258
+ navigate(
259
+ { pathname: location.pathname, search: `?${params}`, hash: location.hash },
260
+ { replace: true },
261
+ );
262
+ }, [location.hash, location.pathname, location.search, navigate]);
263
+
264
+ // Collapsed is a fact about pixels, not about who dragged: the rail appears
265
+ // whether the reader dragged past the minimum or pressed a rail button.
266
+ const onSidebarResize = useCallback(
267
+ (size: { inPixels: number }) => setRailed(size.inPixels <= 56),
268
+ [],
269
+ );
270
+
271
+ // The rail's buttons ask the panel to expand; `railed` then follows from the
272
+ // resize that answers, so the flag can never disagree with the layout.
273
+ //
274
+ // `expand()` restores the width the panel had before it collapsed, which
275
+ // after a drag all the way to the edge is the minimum - a tree too narrow to
276
+ // read the names in. Anything at or under the minimum is treated as "no
277
+ // remembered width" and goes back to the default instead.
278
+ const expandSidebar = useCallback(() => {
279
+ const panel = sidebarRef.current;
280
+ if (!panel) return;
281
+ panel.expand();
282
+ if (panel.getSize().asPercentage <= 13) panel.resize("18");
283
+ }, [sidebarRef]);
284
+
285
+ // A layout restored from a previous session can arrive already collapsed,
286
+ // and no resize fires for a size that never changed - so the rail has to be
287
+ // read off the panel once on mount, or the full tree renders into 48px.
288
+ useEffect(() => {
289
+ setRailed(sidebarRef.current?.isCollapsed() ?? false);
290
+ }, [sidebarRef]);
291
+
292
+ // "[" means the same thing at both widths - show me the tree, or stop
293
+ // showing it - and reaches for whichever mechanism is on screen.
294
+ const toggleSidebar = useCallback(() => {
295
+ if (narrow) {
296
+ toggleDrawer();
297
+ return;
298
+ }
299
+ const panel = sidebarRef.current;
300
+ if (!panel) return;
301
+ if (panel.isCollapsed()) expandSidebar();
302
+ else panel.collapse();
303
+ }, [narrow, toggleDrawer, sidebarRef, expandSidebar]);
304
+
305
+ // "Reveal in tree" selects; the tree then opens its own ancestors and scrolls
306
+ // itself. The one thing it cannot do from inside a collapsed pane is become
307
+ // visible, which is this.
308
+ useEffect(() => {
309
+ if (revealNonce === 0) return;
310
+ if (narrow) setDrawer(true);
311
+ else if (sidebarRef.current?.isCollapsed()) expandSidebar();
312
+ }, [revealNonce, narrow, setDrawer, sidebarRef, expandSidebar]);
313
+
314
+ useShortcuts(
315
+ {
316
+ openPalette: () => setPalette(true),
317
+ openHelp: () => setHelp(true),
318
+ toggleSidebar,
319
+ toggleDetail,
320
+ },
321
+ // While a modal owns the keyboard the global bindings stand down; ⌘K and
322
+ // Esc are handled inside the modals themselves.
323
+ !palette && !help && !chatOpen,
324
+ );
325
+
326
+ return (
327
+ <div className="flex h-full flex-col bg-canvas text-ink">
328
+ <SelectionSync />
329
+ <TrailRecorder />
330
+ <HashScroll />
331
+ <TopBar
332
+ onOpenPalette={() => setPalette(true)}
333
+ onOpenHelp={() => setHelp(true)}
334
+ onToggleSidebar={toggleSidebar}
335
+ narrow={narrow}
336
+ />
337
+ {projectPreview ? (
338
+ <div className="shrink-0 border-b border-declared/40 bg-declared/10 px-gutter py-2 text-center text-sm text-declared">
339
+ Temporary project preview · the repository has not been changed · expires after 15 minutes
340
+ </div>
341
+ ) : null}
342
+ {/* Under the bar, above everything: the trail is about the whole shell,
343
+ not about the page inside it. */}
344
+ <Trail />
345
+
346
+ {narrow ? (
347
+ <>
348
+ {/* `key` on the route content is what makes the page transition
349
+ fire: a new pathname is a new element. The old one leaves first,
350
+ on the micro duration, then the new one rises. */}
351
+ <AnimatePresence mode="wait">
352
+ <m.main
353
+ key={pathname}
354
+ {...page}
355
+ className="min-h-0 flex-1 overflow-hidden"
356
+ >
357
+ <AppRoutes location={location} />
358
+ </m.main>
359
+ </AnimatePresence>
360
+ <SidebarDrawer />
361
+ </>
362
+ ) : (
363
+ <SavedGroup
364
+ id="portolan:shell"
365
+ orientation="horizontal"
366
+ className="min-h-0 flex-1"
367
+ >
368
+ <Panel
369
+ id="sidebar"
370
+ defaultSize="18"
371
+ minSize="12"
372
+ maxSize="28"
373
+ collapsible
374
+ collapsedSize="48px"
375
+ className="h-full text-sm"
376
+ panelRef={sidebarRef}
377
+ onResize={onSidebarResize}
378
+ >
379
+ <Sidebar railed={railed} onExpand={expandSidebar} />
380
+ </Panel>
381
+
382
+ <ResizeHandle id="shell" />
383
+
384
+ {/* The main pane owns a canvas on most routes, so a drag here has to
385
+ reach the diagram - debounced, once the reader lets go. */}
386
+ <Panel id="main" className="h-full min-w-0" onResize={settle}>
387
+ {/* The detail rail rides along with every page that draws a
388
+ diagram, so a selection made anywhere has somewhere to be read. */}
389
+ <AnimatePresence mode="wait">
390
+ <m.main
391
+ key={pathname}
392
+ {...page}
393
+ className="h-full overflow-hidden"
394
+ >
395
+ <AppRoutes location={location} />
396
+ </m.main>
397
+ </AnimatePresence>
398
+ </Panel>
399
+ </SavedGroup>
400
+ )}
401
+
402
+ <CommandPalette open={palette} onClose={() => setPalette(false)} />
403
+ <ShortcutsSheet open={help} onClose={() => setHelp(false)} />
404
+ {ChatPanel && chatOpen ? (
405
+ <Suspense fallback={<ChatLoading />}>
406
+ <ChatPanel />
407
+ </Suspense>
408
+ ) : null}
409
+ {/* The one thing the app says out loud, and it says it here rather than
410
+ in the sidebar: a pin can be taken from a page whose tree is folded
411
+ away to a 48px rail. */}
412
+ <Toaster />
413
+ {/* A card on hover, for any row that names an entity. Mounted once,
414
+ here, because the rows it answers for are on every page. */}
415
+ <PeekLayer />
416
+ </div>
417
+ );
418
+ }
419
+
420
+ export function CatalogApp() {
421
+ // A catalog that failed validation is the catalog app's whole answer. The
422
+ // public landing page is routed before this module is loaded.
423
+ if (catalogError) {
424
+ return <CatalogFailure error={catalogError} />;
425
+ }
426
+
427
+ return (
428
+ <DensityProvider>
429
+ <QueryClientProvider client={queryClient}>
430
+ <ForgeAccessProvider>
431
+ <Shell />
432
+ </ForgeAccessProvider>
433
+ </QueryClientProvider>
434
+ </DensityProvider>
435
+ );
436
+ }
@@ -833,7 +833,6 @@ function BottomGroup() {
833
833
  </NavLink>
834
834
  <NavLink
835
835
  to={paths.settings()}
836
- end
837
836
  data-nav-item
838
837
  title="Settings — projects, plugins and appearance"
839
838
  style={({ isActive }) => ({
@@ -16,41 +16,56 @@ import { ACTION_ACCENT, Notice } from "./Notice";
16
16
  import { useChatRoute } from "./prefs";
17
17
  import { useChatUi } from "./store";
18
18
 
19
- export default function ChatPanel() {
20
- const open = useChatUi((s) => s.open);
21
- const setOpen = useChatUi((s) => s.setOpen);
19
+ function ChatBody({
20
+ onClose,
21
+ onSettings,
22
+ embedded,
23
+ }: {
24
+ onClose?: () => void;
25
+ onSettings: () => void;
26
+ embedded: boolean;
27
+ }) {
22
28
  const route = useChatRoute();
23
- const phone = usePhone();
24
- const [settings, setSettings] = useState(false);
25
- const close = () => setOpen(false);
26
29
  const answering = route.kind === "proxy" || route.kind === "own";
27
30
 
28
31
  return (
29
- <>
30
- <SidePanel
31
- open={open}
32
- onClose={close}
33
- side="right"
34
- label="Ask the catalog"
35
- width={phone ? "100vw" : "min(560px,92vw)"}
36
- >
37
- <div className="flex h-full flex-col bg-canvas text-ink">
38
- {answering ? (
39
- <Conversation
40
- route={route}
41
- onOwnKey={() => setSettings(true)}
42
- onSettings={() => setSettings(true)}
43
- onClose={close}
44
- />
45
- ) : (
46
- <>
47
- <Header route={route} onSettings={() => setSettings(true)} onClose={close} />
48
- <div className="px-4 py-4">
32
+ <div className="flex h-full flex-col bg-canvas text-ink">
33
+ {answering ? (
34
+ <Conversation
35
+ route={route}
36
+ onOwnKey={onSettings}
37
+ onSettings={onSettings}
38
+ seeded={embedded}
39
+ {...(onClose ? { onClose } : {})}
40
+ />
41
+ ) : (
42
+ <>
43
+ <Header
44
+ route={route}
45
+ onSettings={onSettings}
46
+ {...(onClose ? { onClose } : {})}
47
+ />
48
+ <div className="min-h-0 flex-1 overflow-y-auto px-4 py-4">
49
+ {embedded ? (
50
+ <>
51
+ <Notice tone="info" title="bring the model you already use">
52
+ Connect any OpenAI-compatible endpoint. Its address and key
53
+ stay in this browser; Portolan supplies the catalog context.
54
+ </Notice>
55
+ <div className="mt-5">
56
+ <ModelForm />
57
+ </div>
58
+ </>
59
+ ) : (
49
60
  <Notice
50
61
  tone="info"
51
62
  title="nothing answers yet"
52
63
  actions={
53
- <button type="button" onClick={() => setSettings(true)} className={ACTION_ACCENT}>
64
+ <button
65
+ type="button"
66
+ onClick={onSettings}
67
+ className={ACTION_ACCENT}
68
+ >
54
69
  <KeyRound size={13} aria-hidden /> set a model
55
70
  </button>
56
71
  }
@@ -59,21 +74,63 @@ export default function ChatPanel() {
59
74
  yours: any OpenAI-compatible endpoint and, usually, a key. Both
60
75
  stay in this browser.
61
76
  </Notice>
62
- </div>
63
- </>
64
- )}
65
- </div>
66
- </SidePanel>
67
- <Modal
68
- open={settings}
69
- onClose={() => setSettings(false)}
70
- label="Model settings"
71
- width="min(540px,92vw)"
77
+ )}
78
+ </div>
79
+ </>
80
+ )}
81
+ </div>
82
+ );
83
+ }
84
+
85
+ function ModelModal({ open, onClose }: { open: boolean; onClose: () => void }) {
86
+ return (
87
+ <Modal
88
+ open={open}
89
+ onClose={onClose}
90
+ label="Model settings"
91
+ width="min(540px,92vw)"
92
+ >
93
+ <div className="overflow-y-auto p-4">
94
+ <ModelForm onDone={onClose} />
95
+ </div>
96
+ </Modal>
97
+ );
98
+ }
99
+
100
+ export function ChatSurface({ embedded = false }: { embedded?: boolean }) {
101
+ const [settings, setSettings] = useState(false);
102
+
103
+ return (
104
+ <>
105
+ <ChatBody embedded={embedded} onSettings={() => setSettings(true)} />
106
+ <ModelModal open={settings} onClose={() => setSettings(false)} />
107
+ </>
108
+ );
109
+ }
110
+
111
+ export default function ChatPanel() {
112
+ const open = useChatUi((s) => s.open);
113
+ const setOpen = useChatUi((s) => s.setOpen);
114
+ const phone = usePhone();
115
+ const [settings, setSettings] = useState(false);
116
+ const close = () => setOpen(false);
117
+
118
+ return (
119
+ <>
120
+ <SidePanel
121
+ open={open}
122
+ onClose={close}
123
+ side="right"
124
+ label="Ask the catalog"
125
+ width={phone ? "100vw" : "min(560px,92vw)"}
72
126
  >
73
- <div className="overflow-y-auto p-4">
74
- <ModelForm onDone={() => setSettings(false)} />
75
- </div>
76
- </Modal>
127
+ <ChatBody
128
+ embedded={false}
129
+ onClose={close}
130
+ onSettings={() => setSettings(true)}
131
+ />
132
+ </SidePanel>
133
+ <ModelModal open={settings} onClose={() => setSettings(false)} />
77
134
  </>
78
135
  );
79
136
  }
@@ -13,17 +13,19 @@ export function Composer({
13
13
  busy,
14
14
  onSend,
15
15
  onStop,
16
+ focusOnMount = true,
16
17
  }: {
17
18
  busy: boolean;
18
19
  onSend: (question: string) => void;
19
20
  onStop: () => void;
21
+ focusOnMount?: boolean;
20
22
  }) {
21
23
  const [draft, setDraft] = useState("");
22
24
  const field = useRef<HTMLTextAreaElement>(null);
23
25
 
24
26
  useEffect(() => {
25
- field.current?.focus();
26
- }, []);
27
+ if (focusOnMount) field.current?.focus();
28
+ }, [focusOnMount]);
27
29
 
28
30
  // Grow with the text, up to the limit; shrink back when it is deleted.
29
31
  useEffect(() => {