@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,68 @@
1
+ import type { ReactNode } from "react";
2
+ import { CompassRose } from "../components/logo";
3
+
4
+ export function ProductFrame({
5
+ title,
6
+ eyebrow,
7
+ children,
8
+ className = "",
9
+ aside = true,
10
+ }: {
11
+ title: string;
12
+ eyebrow: string;
13
+ children: ReactNode;
14
+ className?: string;
15
+ /** The sketched catalog tree down the left. Off for a canvas that wants the whole width. */
16
+ aside?: boolean;
17
+ }) {
18
+ return (
19
+ <div
20
+ className={`overflow-hidden rounded-[18px] border border-line bg-canvas shadow-md ${className}`}
21
+ >
22
+ <div className="flex h-11 items-center gap-3 border-b border-line bg-surface px-4">
23
+ <div className="flex gap-1.5" aria-hidden>
24
+ <span className="size-2 rounded-full bg-unresolved/70" />
25
+ <span className="size-2 rounded-full bg-declared/70" />
26
+ <span className="size-2 rounded-full bg-verified/70" />
27
+ </div>
28
+ <div className="mono flex min-w-0 flex-1 items-center justify-center gap-1.5 text-faint">
29
+ <CompassRose size={13} />
30
+ <span className="truncate">{title}</span>
31
+ </div>
32
+ <span className="w-[35px]" aria-hidden />
33
+ </div>
34
+ <div
35
+ className={`grid min-h-0 grid-cols-1 ${
36
+ aside ? "sm:grid-cols-[132px_minmax(0,1fr)]" : ""
37
+ }`}
38
+ >
39
+ {aside ? (
40
+ <aside className="hidden border-r border-line bg-canvas p-3 sm:block">
41
+ <div className="label mb-3">{eyebrow}</div>
42
+ <div className="space-y-1.5" aria-hidden>
43
+ <div className="h-2 w-20 rounded-full bg-line-strong" />
44
+ <div className="h-2 w-16 rounded-full bg-line" />
45
+ <div className="h-2 w-24 rounded-full bg-line" />
46
+ </div>
47
+ <div className="label mt-6 mb-3">catalog</div>
48
+ <div className="space-y-2" aria-hidden>
49
+ <div className="flex items-center gap-2">
50
+ <span className="size-1.5 rounded-[1px] bg-[var(--ctx-0)]" />
51
+ <div className="h-2 w-16 rounded-full bg-line" />
52
+ </div>
53
+ <div className="flex items-center gap-2">
54
+ <span className="size-1.5 rounded-[1px] bg-[var(--ctx-1)]" />
55
+ <div className="h-2 w-20 rounded-full bg-line" />
56
+ </div>
57
+ <div className="flex items-center gap-2">
58
+ <span className="size-1.5 rounded-[1px] bg-[var(--ctx-2)]" />
59
+ <div className="h-2 w-14 rounded-full bg-line" />
60
+ </div>
61
+ </div>
62
+ </aside>
63
+ ) : null}
64
+ <div className="landing-grid min-w-0">{children}</div>
65
+ </div>
66
+ </div>
67
+ );
68
+ }
@@ -0,0 +1,371 @@
1
+ // Four stops through the product, on the example estate. Two of them are the
2
+ // product's own canvases in a smaller box; the other two are read straight
3
+ // out of the same catalog. Nothing on this page is drawn from a mock-up.
4
+
5
+ import { lazy, Suspense, useMemo, useState } from "react";
6
+ import { Link } from "react-router";
7
+ import {
8
+ AlertTriangle,
9
+ ArrowRight,
10
+ Braces,
11
+ ExternalLink,
12
+ FileCode2,
13
+ GitBranch,
14
+ Network,
15
+ Radio,
16
+ } from "lucide-react";
17
+ import { catalog, index } from "../data";
18
+ import { DiagramSkeleton } from "../components/DiagramSkeleton";
19
+ import { contextVar } from "../lib/context-color";
20
+ import { allProblems } from "../lib/all-problems";
21
+ import {
22
+ AnimatePresence,
23
+ LayoutGroup,
24
+ m,
25
+ page,
26
+ staggerStyle,
27
+ transitions,
28
+ } from "../lib/motion";
29
+ import { eventPath, paths } from "../routes";
30
+ import { catalogTo } from "./catalog";
31
+ import { ProductFrame } from "./ProductFrame";
32
+ import { DraggableReveal } from "./DraggableReveal";
33
+
34
+ type DemoId = "estate" | "flow" | "contract" | "problems";
35
+
36
+ // The two canvases carry elk and React Flow; they arrive when their tab does.
37
+ const EstateGraph = lazy(() =>
38
+ import("./EstateGraph").then((mod) => ({ default: mod.EstateGraph })),
39
+ );
40
+ const FlowPlayback = lazy(() =>
41
+ import("./FlowPlayback").then((mod) => ({ default: mod.FlowPlayback })),
42
+ );
43
+
44
+ const tours: Array<{
45
+ id: DemoId;
46
+ number: string;
47
+ title: string;
48
+ copy: string;
49
+ icon: typeof Network;
50
+ }> = [
51
+ {
52
+ id: "estate",
53
+ number: "01",
54
+ title: "See the whole estate",
55
+ copy: "See every service and event between them. Switch views, focus a service, or export the map.",
56
+ icon: Network,
57
+ },
58
+ {
59
+ id: "flow",
60
+ number: "02",
61
+ title: "Follow one real flow",
62
+ copy: "Follow a checkout across HTTP, gRPC, the database and the bus, one step at a time, with its source line.",
63
+ icon: GitBranch,
64
+ },
65
+ {
66
+ id: "contract",
67
+ number: "03",
68
+ title: "Inspect the contract",
69
+ copy: "The event's payload, the channel it leaves on, who listens, and the file it was read from.",
70
+ icon: Braces,
71
+ },
72
+ {
73
+ id: "problems",
74
+ number: "04",
75
+ title: "Catch architecture drift",
76
+ copy: "Catch unresolved calls, second writers and unpublished channels during the merge, before review.",
77
+ icon: AlertTriangle,
78
+ },
79
+ ];
80
+
81
+ const EVENT_ID = "shop.cart.basket.BasketCheckedOut";
82
+
83
+ function ContractDemo() {
84
+ const event = index.eventById.get(EVENT_ID);
85
+ const owner = index.eventOwner.get(EVENT_ID);
86
+ const version = event?.versions.at(-1);
87
+ const href = eventPath(EVENT_ID);
88
+ if (!event || !owner || !version) {
89
+ return (
90
+ <div className="p-6 text-sm text-muted">
91
+ The example catalog does not carry {EVENT_ID}.
92
+ </div>
93
+ );
94
+ }
95
+ const context = index.serviceContext.get(owner.service.id);
96
+
97
+ return (
98
+ <div className="p-4 sm:p-6">
99
+ <div className="flex flex-wrap items-start justify-between gap-3">
100
+ <div className="min-w-0">
101
+ <div className="mono text-event">domain event · {version.version}</div>
102
+ <div className="mt-1 text-md font-semibold text-ink">{event.name}</div>
103
+ <div className="mono mt-1 truncate text-muted">{event.id}</div>
104
+ </div>
105
+ {event.wire ? (
106
+ <span className="chip status-verified">
107
+ <Radio size={12} /> {event.wire.channel ?? event.wire.name}
108
+ </span>
109
+ ) : null}
110
+ </div>
111
+ {version.doc ? (
112
+ <p className="mt-3 max-w-[560px] text-sm leading-6 text-muted">
113
+ {version.doc}
114
+ </p>
115
+ ) : null}
116
+ <div className="mt-5 grid gap-4 lg:grid-cols-[minmax(0,1fr)_200px]">
117
+ <div className="overflow-hidden rounded-card border border-line bg-canvas">
118
+ <div className="label border-b border-line bg-surface px-3 py-2">
119
+ payload
120
+ </div>
121
+ {version.fields.map((field, i) => (
122
+ <div
123
+ key={field.name}
124
+ style={staggerStyle(i)}
125
+ className="stagger-in grid grid-cols-[minmax(0,1fr)_auto] gap-3 border-b border-line px-3 py-2 last:border-b-0"
126
+ >
127
+ <span className="mono text-ink">{field.name}</span>
128
+ <span className="mono text-muted">{field.type}</span>
129
+ </div>
130
+ ))}
131
+ </div>
132
+ <div>
133
+ <div className="label mb-2">
134
+ published by
135
+ </div>
136
+ <div
137
+ className="rounded-control border border-line bg-canvas p-2.5"
138
+ style={{ color: contextVar(context?.id) }}
139
+ >
140
+ <div className="font-medium text-ink">{owner.service.id}</div>
141
+ <div className="mono mt-0.5 text-muted">{owner.aggregate.name}</div>
142
+ </div>
143
+ <div className="label mt-4 mb-2">
144
+ consumed by
145
+ </div>
146
+ {event.consumers.length === 0 ? (
147
+ <div className="mono text-faint">nobody yet</div>
148
+ ) : (
149
+ <div className="space-y-2">
150
+ {event.consumers.map((consumer, i) => (
151
+ <div
152
+ key={consumer.service}
153
+ style={staggerStyle(i + 2)}
154
+ className="stagger-in flex items-center justify-between gap-2 rounded-control border border-line bg-canvas p-2.5"
155
+ >
156
+ <div className="min-w-0">
157
+ <div className="truncate font-medium text-ink">
158
+ {consumer.service}
159
+ </div>
160
+ {consumer.note ? (
161
+ <div className="mono mt-0.5 truncate text-muted">
162
+ {consumer.note}
163
+ </div>
164
+ ) : null}
165
+ </div>
166
+ <span className={`chip status-${consumer.status}`}>
167
+ {consumer.status}
168
+ </span>
169
+ </div>
170
+ ))}
171
+ </div>
172
+ )}
173
+ </div>
174
+ </div>
175
+ <div className="mt-4 flex flex-wrap items-center gap-3">
176
+ <div className="mono flex min-w-0 flex-1 items-center gap-2 rounded-control border border-line bg-surface px-3 py-2 text-muted">
177
+ <FileCode2 size={13} className="shrink-0" />
178
+ <span className="truncate">{version.source}</span>
179
+ <ExternalLink size={12} className="ml-auto shrink-0" />
180
+ </div>
181
+ {href ? (
182
+ <Link
183
+ to={catalogTo(href)}
184
+ className="inline-flex shrink-0 items-center gap-1 text-sm text-accent hover:underline"
185
+ >
186
+ Open the event <ArrowRight size={13} />
187
+ </Link>
188
+ ) : null}
189
+ </div>
190
+ </div>
191
+ );
192
+ }
193
+
194
+ const SHOWN_PROBLEMS = 4;
195
+
196
+ function ProblemsDemo() {
197
+ const all = useMemo(() => allProblems(catalog, index), []);
198
+ const errors = all.filter((p) => p.severity === "error").length;
199
+ const shown = all.slice(0, SHOWN_PROBLEMS);
200
+
201
+ return (
202
+ <div className="p-4 sm:p-6">
203
+ <div className="mb-5 flex flex-wrap items-start justify-between gap-3">
204
+ <div>
205
+ <div className="text-md font-semibold text-ink">
206
+ Architecture problems
207
+ </div>
208
+ <div className="mt-1 text-sm text-muted">
209
+ Every edge that leaves the measured model, found at merge time
210
+ </div>
211
+ </div>
212
+ <div className="flex gap-2">
213
+ {errors > 0 ? (
214
+ <span className="chip status-unresolved">{errors} errors</span>
215
+ ) : null}
216
+ {all.length - errors > 0 ? (
217
+ <span className="chip status-declared">
218
+ {all.length - errors} warnings
219
+ </span>
220
+ ) : null}
221
+ </div>
222
+ </div>
223
+ {shown.length === 0 ? (
224
+ <div className="rounded-card border border-line bg-canvas p-4 text-sm text-muted">
225
+ Nothing leaves the model: every call and every consumer resolves.
226
+ </div>
227
+ ) : (
228
+ <div className="space-y-2">
229
+ {shown.map((problem, i) => (
230
+ <div
231
+ key={`${problem.kind}:${problem.id}:${problem.peer}`}
232
+ style={staggerStyle(i)}
233
+ className="stagger-in grid grid-cols-[20px_minmax(0,1fr)_auto] items-start gap-3 rounded-card border border-line bg-canvas p-3"
234
+ >
235
+ <AlertTriangle
236
+ size={16}
237
+ className={
238
+ problem.severity === "error"
239
+ ? "mt-0.5 text-unresolved"
240
+ : "mt-0.5 text-declared"
241
+ }
242
+ />
243
+ <div className="min-w-0">
244
+ <div className="truncate font-medium text-ink">
245
+ {problem.note ?? problem.id}
246
+ </div>
247
+ <div
248
+ className="mono mt-1 truncate text-muted"
249
+ title={`${problem.service} → ${problem.peer}`}
250
+ >
251
+ <span style={{ color: contextVar(problem.context) }}>
252
+ {problem.service}
253
+ </span>{" "}
254
+ → {problem.peer}
255
+ </div>
256
+ </div>
257
+ <span className="chip border-line text-muted">
258
+ {problem.kind}
259
+ </span>
260
+ </div>
261
+ ))}
262
+ </div>
263
+ )}
264
+ <div className="mt-4 flex items-center justify-between gap-3">
265
+ <span className="mono text-faint">
266
+ {all.length > shown.length
267
+ ? `${all.length - shown.length} more on the problems page`
268
+ : `${all.length} in total`}
269
+ </span>
270
+ <Link
271
+ to={catalogTo(paths.problems())}
272
+ className="inline-flex items-center gap-1 text-sm text-accent hover:underline"
273
+ >
274
+ Open the problems page <ArrowRight size={13} />
275
+ </Link>
276
+ </div>
277
+ </div>
278
+ );
279
+ }
280
+
281
+ function Demo({ id }: { id: DemoId }) {
282
+ switch (id) {
283
+ case "flow":
284
+ return <FlowPlayback />;
285
+ case "contract":
286
+ return <ContractDemo />;
287
+ case "problems":
288
+ return <ProblemsDemo />;
289
+ default:
290
+ return <EstateGraph />;
291
+ }
292
+ }
293
+
294
+ export function ProductTour() {
295
+ const [active, setActive] = useState<DemoId>("estate");
296
+ const selected = tours.find((tour) => tour.id === active) ?? tours[0]!;
297
+
298
+ return (
299
+ <div className="grid items-start gap-8 lg:grid-cols-[minmax(260px,0.72fr)_minmax(0,1.55fr)] lg:gap-12">
300
+ <LayoutGroup id="product-tour">
301
+ <div
302
+ role="tablist"
303
+ aria-label="Portolan product tour"
304
+ className="grid gap-2 sm:grid-cols-2 lg:grid-cols-1"
305
+ >
306
+ {tours.map((tour) => {
307
+ const Icon = tour.icon;
308
+ const on = tour.id === active;
309
+ return (
310
+ <button
311
+ key={tour.id}
312
+ type="button"
313
+ role="tab"
314
+ aria-selected={on}
315
+ aria-controls="landing-product-demo"
316
+ onClick={() => setActive(tour.id)}
317
+ className={`group relative rounded-card border border-line/60 p-4 text-left transition-colors ${
318
+ on ? "" : "hover:border-line-strong hover:bg-surface"
319
+ }`}
320
+ >
321
+ {on ? (
322
+ <m.span
323
+ layoutId="tour-ring"
324
+ transition={transitions.settle}
325
+ className="absolute inset-0 rounded-card border border-accent bg-accent/5"
326
+ aria-hidden
327
+ />
328
+ ) : null}
329
+ <div className="relative flex items-center gap-3">
330
+ <span
331
+ className={`mono flex size-8 items-center justify-center rounded-control border transition-colors ${
332
+ on
333
+ ? "border-accent text-accent"
334
+ : "border-line text-muted group-hover:text-ink"
335
+ }`}
336
+ >
337
+ <Icon size={15} aria-hidden />
338
+ </span>
339
+ <div>
340
+ <div className="mono text-faint">{tour.number}</div>
341
+ <div className="font-semibold text-ink">{tour.title}</div>
342
+ </div>
343
+ </div>
344
+ <p className="relative mt-3 text-sm text-muted">{tour.copy}</p>
345
+ </button>
346
+ );
347
+ })}
348
+ </div>
349
+ </LayoutGroup>
350
+
351
+ <div id="landing-product-demo" role="tabpanel" aria-live="polite">
352
+ <DraggableReveal>
353
+ <ProductFrame
354
+ title={`${selected.title} · portolan`}
355
+ eyebrow={active}
356
+ // The two canvases want every pixel of width they can get.
357
+ aside={active === "contract" || active === "problems"}
358
+ >
359
+ <AnimatePresence mode="wait" initial={false}>
360
+ <m.div key={active} {...page} className="relative min-h-[470px]">
361
+ <Suspense fallback={<DiagramSkeleton />}>
362
+ <Demo id={active} />
363
+ </Suspense>
364
+ </m.div>
365
+ </AnimatePresence>
366
+ </ProductFrame>
367
+ </DraggableReveal>
368
+ </div>
369
+ </div>
370
+ );
371
+ }
@@ -0,0 +1,25 @@
1
+ // The landing's link into the catalog it demonstrates.
2
+ //
3
+ // The landing shows the example estate (data.ts picks that profile for the
4
+ // `/landing` route), and every "open this in the catalog" it offers has to
5
+ // land on the same estate after a reload as it does on a click. So the profile
6
+ // is spelled on the URL, the way the catalog app itself keeps it there.
7
+
8
+ import { useEffect } from "react";
9
+ import { activeCatalogProfile } from "../data";
10
+ import { useSelectionStore } from "../selection/store";
11
+
12
+ export function catalogTo(pathname: string): { pathname: string; search: string } {
13
+ return { pathname, search: `?catalog=${activeCatalogProfile.id}` };
14
+ }
15
+
16
+ /**
17
+ * The canvases on the landing write to the app's one selection store, the
18
+ * same as they do on their own pages. A demo that is unmounted - a tab
19
+ * switched, the page left - must not leave its selection behind for the
20
+ * catalog's detail panel to open on.
21
+ */
22
+ export function useSelectionCleared(): void {
23
+ const clear = useSelectionStore((s) => s.clear);
24
+ useEffect(() => () => clear("diagram"), [clear]);
25
+ }
@@ -0,0 +1,52 @@
1
+ // Motion the landing adds on top of lib/motion: things that arrive as the
2
+ // reader scrolls to them, and a hero that arrives in order. Same clock as the
3
+ // app - the narrative duration and the ease-out - so a card rising on the
4
+ // landing rises the way a row rises in the catalog.
5
+
6
+ import type { ReactNode } from "react";
7
+ import type { Variants } from "motion/react";
8
+ import { DURATION, EASE_OUT, m } from "../lib/motion";
9
+
10
+ const VIEWPORT = { once: true, margin: "-10% 0px -10% 0px" } as const;
11
+
12
+ /**
13
+ * A block that rises the fourteen pixels once, the first time it scrolls into
14
+ * view. `delay` staggers siblings; keep it under a third of a second, or the
15
+ * last card in a row is still arriving when the reader has read the first.
16
+ */
17
+ export function Reveal({
18
+ children,
19
+ delay = 0,
20
+ className,
21
+ }: {
22
+ children: ReactNode;
23
+ delay?: number;
24
+ className?: string;
25
+ }) {
26
+ return (
27
+ <m.div
28
+ className={className}
29
+ initial={{ opacity: 0, y: 14 }}
30
+ whileInView={{ opacity: 1, y: 0 }}
31
+ viewport={VIEWPORT}
32
+ transition={{ duration: DURATION.narrative, ease: EASE_OUT, delay }}
33
+ >
34
+ {children}
35
+ </m.div>
36
+ );
37
+ }
38
+
39
+ /** The hero's column: eyebrow, headline, copy, buttons, one after another. */
40
+ export const heroColumn: Variants = {
41
+ hidden: {},
42
+ shown: { transition: { staggerChildren: 0.08, delayChildren: 0.05 } },
43
+ };
44
+
45
+ export const heroLine: Variants = {
46
+ hidden: { opacity: 0, y: 14 },
47
+ shown: {
48
+ opacity: 1,
49
+ y: 0,
50
+ transition: { duration: DURATION.narrative, ease: EASE_OUT },
51
+ },
52
+ };
@@ -0,0 +1,28 @@
1
+ // Every reading the catalog makes of itself, in one list.
2
+ //
3
+ // Four readers, each answering a different question: edges that resolve to
4
+ // nothing, schemas that name a method the module lacks, tables and columns
5
+ // that disagree with the model, and channels that documents and events do not
6
+ // agree on. The problems page, the overview and the landing all want the
7
+ // union, errors first - a boundary leak is not the same kind of news as a
8
+ // column whose type has drifted, and mixing them buries the first.
9
+
10
+ import type { Catalog, CatalogIndex } from "../catalog";
11
+ import { dataProblems } from "./data-problems";
12
+ import { problems } from "./derive";
13
+ import type { Problem } from "./derive";
14
+ import { protoProblems } from "./proto-problems";
15
+ import { wireProblems } from "./wire-problems";
16
+
17
+ export function allProblems(catalog: Catalog, index: CatalogIndex): Problem[] {
18
+ const found = [
19
+ ...problems(catalog),
20
+ ...protoProblems(catalog, index),
21
+ ...dataProblems(catalog, index),
22
+ ...wireProblems(catalog, index),
23
+ ];
24
+ return [
25
+ ...found.filter((p) => p.severity === "error"),
26
+ ...found.filter((p) => p.severity === "warning"),
27
+ ];
28
+ }
@@ -1,6 +1,6 @@
1
1
  import type { SetupInfo, SetupPhase, SetupRunStepStatus } from "./setup-info";
2
2
 
3
- const ROOT = "/__portolan";
3
+ const ROOT = `${import.meta.env.BASE_URL}__portolan`;
4
4
  const LOCAL_HEADER = { "Content-Type": "application/json", "X-Portolan-Local": "1" };
5
5
 
6
6
  export interface Detection {
@@ -64,6 +64,27 @@ export interface GeneratedFileDiff {
64
64
  diff: string;
65
65
  }
66
66
 
67
+ export type DeliveryProvider = "github" | "gitlab";
68
+
69
+ export interface DeliveryPresetFile {
70
+ path: string;
71
+ status: "added" | "changed" | "unchanged" | "conflict";
72
+ diff: string;
73
+ message?: string;
74
+ }
75
+
76
+ export interface DeliveryPreset {
77
+ provider: DeliveryProvider;
78
+ detectedProvider: DeliveryProvider | null;
79
+ remote: string | null;
80
+ repository: string;
81
+ defaultBranch: string;
82
+ status: "available" | "installed" | "update" | "conflict";
83
+ revision: string;
84
+ features: string[];
85
+ files: DeliveryPresetFile[];
86
+ }
87
+
67
88
  export interface ProjectTrialFact {
68
89
  key: string;
69
90
  label: string;
@@ -142,6 +163,19 @@ export async function localStatus(): Promise<{ local: true; workspace: string; s
142
163
  return json("/status");
143
164
  }
144
165
 
166
+ export async function previewDeliveryPreset(provider?: DeliveryProvider): Promise<DeliveryPreset> {
167
+ const query = provider ? `?provider=${encodeURIComponent(provider)}` : "";
168
+ return json(`/delivery-presets${query}`);
169
+ }
170
+
171
+ export async function installDeliveryPreset(provider: DeliveryProvider, revision: string): Promise<DeliveryPreset & { written: string[] }> {
172
+ return json("/delivery-presets/install", {
173
+ method: "POST",
174
+ headers: LOCAL_HEADER,
175
+ body: JSON.stringify({ provider, revision }),
176
+ });
177
+ }
178
+
145
179
  export async function discover(path: string): Promise<Discovery> {
146
180
  return json("/discover", { method: "POST", headers: LOCAL_HEADER, body: JSON.stringify({ path }) });
147
181
  }
@@ -16,6 +16,7 @@ describe("publicSetupFrom", () => {
16
16
  },
17
17
  { name: "docs", wasm: { url: "https://example.com/docs.wasm" } },
18
18
  { name: "unused", process: { command: "unused" } },
19
+ { name: "vendor", host: "fetch-git" },
19
20
  ],
20
21
  extract: [
21
22
  {
@@ -58,6 +59,13 @@ describe("publicSetupFrom", () => {
58
59
  stepCount: 0,
59
60
  projectIds: [],
60
61
  },
62
+ {
63
+ name: "vendor",
64
+ runtime: "host",
65
+ phases: [],
66
+ stepCount: 0,
67
+ projectIds: [],
68
+ },
61
69
  ]);
62
70
  expect(JSON.stringify(setup)).not.toContain("secret");
63
71
  expect(JSON.stringify(setup)).not.toContain("command");
@@ -32,7 +32,7 @@ export interface SetupStep {
32
32
 
33
33
  export interface SetupPlugin {
34
34
  name: string;
35
- runtime: "wasm" | "process";
35
+ runtime: "wasm" | "process" | "host";
36
36
  phases: SetupPhase[];
37
37
  stepCount: number;
38
38
  projectIds: string[];
@@ -82,6 +82,7 @@ interface ManifestPlugin {
82
82
  name?: unknown;
83
83
  wasm?: unknown;
84
84
  process?: unknown;
85
+ host?: unknown;
85
86
  }
86
87
 
87
88
  interface ManifestStep {
@@ -345,7 +346,7 @@ function pluginFrom(
345
346
  // Settings prints this as a promise about filesystem, network and
346
347
  // environment access, so a `wasm` that is not a module - null, a bare
347
348
  // true, a half-written object - answers "process", which promises nothing.
348
- runtime: typeof record(item.wasm)["url"] === "string" ? "wasm" : "process",
349
+ runtime: typeof record(item.wasm)["url"] === "string" ? "wasm" : typeof item.host === "string" ? "host" : "process",
349
350
  phases: PHASES.filter((phase) => own.some((step) => step.phase === phase)),
350
351
  stepCount: own.length,
351
352
  projectIds: [
@@ -1,6 +1,7 @@
1
1
  import type { RemoteSourceLocation, SourceLocation } from "./source-link";
2
2
 
3
3
  const MAX_SOURCE_BYTES = 1024 * 1024;
4
+ const LOCAL_SOURCE_URL = `${import.meta.env.BASE_URL}__portolan/source`;
4
5
 
5
6
  export type SourceFile = {
6
7
  content: string;
@@ -145,7 +146,7 @@ async function loadRemote(
145
146
  async function loadLocal(
146
147
  location: Extract<SourceLocation, { kind: "local" }>,
147
148
  ): Promise<SourceFile> {
148
- const response = await fetch("/__portolan/source", {
149
+ const response = await fetch(LOCAL_SOURCE_URL, {
149
150
  method: "POST",
150
151
  headers: { "Content-Type": "application/json", "X-Portolan-Local": "1" },
151
152
  body: JSON.stringify({ path: location.path }),