@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.
- package/README.md +69 -205
- package/cli/init.mjs +285 -0
- package/cli/init.test.mjs +158 -0
- package/cli/portolan.mjs +37 -73
- package/index.html +19 -0
- package/package.json +5 -10
- package/plugins/README.md +66 -27
- package/plugins/portolan-go.wasm +0 -0
- package/portolan.json +52 -73
- package/public/landing/cats/bug-hunter.webp +0 -0
- package/public/landing/cats/compass-nap.webp +0 -0
- package/public/landing/cats/diagrammer.webp +0 -0
- package/public/landing/cats/docs-reader.webp +0 -0
- package/public/landing/cats/laptop.webp +0 -0
- package/public/landing/cats/map-inspector.webp +0 -0
- package/public/landing/cats/server-break.webp +0 -0
- package/public/landing/cats/star-mapper.webp +0 -0
- package/public/landing/cats/system-builder.webp +0 -0
- package/public/landing/cats/thread-tangle.webp +0 -0
- package/public/og.png +0 -0
- package/public/readme-header.webp +0 -0
- package/schema/portolan.schema.json +180 -0
- package/scripts/builtin-plugins.mjs +14 -0
- package/scripts/delivery-presets.mjs +353 -0
- package/scripts/gen.mjs +37 -4
- package/scripts/history.mjs +126 -0
- package/scripts/history.test.mjs +110 -0
- package/scripts/host-plugins/fetch-bsr.mjs +351 -0
- package/{plugins/fetch-bsr/options.schema.json → scripts/host-plugins/fetch-bsr.options.json} +4 -0
- package/scripts/host-plugins/fetch-bsr.test.mjs +212 -0
- package/scripts/host-plugins/fetch-csr.mjs +386 -0
- package/scripts/host-plugins/fetch-csr.test.mjs +178 -0
- package/scripts/host-plugins/fetch-git.mjs +379 -0
- package/scripts/host-plugins/fetch-git.test.mjs +210 -0
- package/scripts/local-api.mjs +22 -4
- package/scripts/local-api.test.mjs +84 -2
- package/scripts/manifest.mjs +3 -2
- package/scripts/package-smoke.mjs +6 -2
- package/scripts/plugin-host.mjs +77 -18
- package/scripts/plugin-host.test.mjs +50 -1
- package/scripts/plugin-wasm-worker.mjs +6 -3
- package/scripts/run-builtin.mjs +6 -26
- package/scripts/schema.mjs +6 -1
- package/src/app/App.tsx +24 -429
- package/src/app/Breadcrumbs.test.ts +7 -0
- package/src/app/Breadcrumbs.tsx +15 -2
- package/src/app/CatalogApp.tsx +436 -0
- package/src/app/Sidebar.tsx +0 -1
- package/src/chat/ChatPanel.tsx +98 -41
- package/src/chat/Composer.tsx +4 -2
- package/src/chat/Conversation.tsx +79 -15
- package/src/chat/Header.tsx +43 -9
- package/src/chat/Starter.tsx +64 -3
- package/src/chat/Steps.tsx +1 -1
- package/src/chat/flags.test.ts +51 -14
- package/src/chat/flags.ts +8 -2
- package/src/chat/page-context.test.ts +51 -0
- package/src/chat/page-context.ts +128 -0
- package/src/chat/prompt.test.ts +49 -1
- package/src/chat/prompt.ts +42 -1
- package/src/chat/tools.ts +7 -2
- package/src/chat/transport.ts +16 -4
- package/src/components/ProblemRow.tsx +182 -0
- package/src/data.ts +20 -5
- package/src/er/ErCanvas.tsx +1 -1
- package/src/graph/DependencyGraph.tsx +10 -1
- package/src/graph/FocusedEventGraph.tsx +1 -1
- package/src/graph/GraphToolbar.tsx +2 -4
- package/src/index.css +220 -2
- package/src/landing/DraggableReveal.tsx +168 -0
- package/src/landing/EstateGraph.tsx +28 -0
- package/src/landing/FlowPlayback.tsx +282 -0
- package/src/landing/HeroMap.tsx +128 -0
- package/src/landing/LandingPage.tsx +757 -0
- package/src/landing/ProductFrame.tsx +68 -0
- package/src/landing/ProductTour.tsx +371 -0
- package/src/landing/catalog.ts +25 -0
- package/src/landing/motion.tsx +52 -0
- package/src/lib/all-problems.ts +28 -0
- package/src/lib/local-api.ts +35 -1
- package/src/lib/setup-info.test.ts +8 -0
- package/src/lib/setup-info.ts +3 -2
- package/src/lib/source-code.ts +2 -1
- package/src/likec4/C4View.tsx +5 -0
- package/src/likec4/InteractiveView.tsx +5 -1
- package/src/map/ContextMapGraph.tsx +47 -20
- package/src/pages/ContextMap.tsx +5 -1
- package/src/pages/FlowDetail.tsx +24 -7
- package/src/pages/Overview.tsx +68 -12
- package/src/pages/Problems.tsx +6 -193
- package/src/pages/Settings.tsx +133 -108
- package/src/pages/settings/DeliverySettings.tsx +229 -0
- package/src/pages/settings/PreferencesSettings.tsx +101 -0
- package/src/routes.test.ts +20 -0
- package/src/routes.ts +13 -1
- package/vite.config.ts +12 -8
- package/catalog/enum_test.go +0 -46
- package/catalog/model.go +0 -932
- package/catalog/roundtrip_test.go +0 -185
- package/catalog/via_test.go +0 -38
- package/go.mod +0 -14
- package/go.sum +0 -14
- package/internal/commands/cargo.go +0 -154
- package/internal/commands/commands.go +0 -143
- package/internal/commands/commands_test.go +0 -323
- package/internal/commands/gradle.go +0 -77
- package/internal/commands/justfile.go +0 -82
- package/internal/commands/makefile.go +0 -106
- package/internal/commands/maven.go +0 -93
- package/internal/commands/packagejson.go +0 -159
- package/internal/commands/pyproject.go +0 -169
- package/internal/commands/taskfile.go +0 -101
- package/internal/commands/testdata/estate/.cargo/config.toml +0 -7
- package/internal/commands/testdata/estate/Makefile +0 -33
- package/internal/commands/testdata/estate/Taskfile.yml +0 -21
- package/internal/commands/testdata/estate/build.gradle.kts +0 -21
- package/internal/commands/testdata/estate/justfile +0 -20
- package/internal/commands/testdata/estate/package.json +0 -13
- package/internal/commands/testdata/estate/pom.xml +0 -29
- package/internal/commands/testdata/estate/pyproject.toml +0 -27
- package/internal/commands/testdata/estate/xtask/src/main.rs +0 -27
- package/internal/commands/testdata/golden/commands.json +0 -305
- package/internal/gohttp/analyze.go +0 -2500
- package/internal/gohttp/endpoints.go +0 -1067
- package/internal/gohttp/roots.go +0 -320
- package/internal/gohttp/typed.go +0 -143
- package/internal/goscan/constants.go +0 -85
- package/internal/goscan/goscan_test.go +0 -227
- package/internal/goscan/names.go +0 -52
- package/internal/goscan/parse_test.go +0 -11
- package/internal/goscan/source.go +0 -36
- package/internal/goscan/tree.go +0 -155
- package/internal/goscan/types.go +0 -99
- package/internal/wsdl/ids.go +0 -127
- package/internal/wsdl/ids_test.go +0 -21
- package/internal/wsdl/model.go +0 -62
- package/internal/wsdl/parse.go +0 -920
- package/internal/wsdl/parse_test.go +0 -133
- package/plugin/describe.go +0 -107
- package/plugin/describe_test.go +0 -114
- package/plugin/protocol.go +0 -117
- package/plugin/schematest/schematest.go +0 -126
- package/plugins/extract-adr/describe.go +0 -19
- package/plugins/extract-adr/describe_test.go +0 -11
- package/plugins/extract-adr/extract.go +0 -153
- package/plugins/extract-adr/extract_test.go +0 -350
- package/plugins/extract-adr/history.go +0 -99
- package/plugins/extract-adr/main.go +0 -65
- package/plugins/extract-adr/parse.go +0 -642
- package/plugins/extract-adr/parse_test.go +0 -405
- package/plugins/extract-asyncapi/describe.go +0 -19
- package/plugins/extract-asyncapi/describe_test.go +0 -11
- package/plugins/extract-asyncapi/extract.go +0 -315
- package/plugins/extract-asyncapi/extract_test.go +0 -197
- package/plugins/extract-asyncapi/main.go +0 -48
- package/plugins/extract-asyncapi/spec.go +0 -151
- package/plugins/extract-commands/describe.go +0 -19
- package/plugins/extract-commands/describe_test.go +0 -11
- package/plugins/extract-commands/extract.go +0 -79
- package/plugins/extract-commands/extract_test.go +0 -92
- package/plugins/extract-commands/main.go +0 -55
- package/plugins/extract-csr/avro.go +0 -258
- package/plugins/extract-csr/describe.go +0 -19
- package/plugins/extract-csr/describe_test.go +0 -11
- package/plugins/extract-csr/extract.go +0 -338
- package/plugins/extract-csr/extract_test.go +0 -374
- package/plugins/extract-csr/jsonschema.go +0 -343
- package/plugins/extract-csr/lock.go +0 -25
- package/plugins/extract-csr/main.go +0 -84
- package/plugins/extract-csr/subject.go +0 -102
- package/plugins/extract-flows/describe.go +0 -19
- package/plugins/extract-flows/describe_test.go +0 -11
- package/plugins/extract-flows/extract.go +0 -91
- package/plugins/extract-flows/main.go +0 -45
- package/plugins/extract-flows/parse.go +0 -593
- package/plugins/extract-flows/parse_test.go +0 -204
- package/plugins/extract-glossary/describe.go +0 -19
- package/plugins/extract-glossary/describe_test.go +0 -11
- package/plugins/extract-glossary/extract.go +0 -115
- package/plugins/extract-glossary/extract_test.go +0 -220
- package/plugins/extract-glossary/main.go +0 -59
- package/plugins/extract-glossary/parse.go +0 -214
- package/plugins/extract-glossary/parse_test.go +0 -203
- package/plugins/extract-go/aggregate.go +0 -214
- package/plugins/extract-go/client.go +0 -409
- package/plugins/extract-go/client_test.go +0 -266
- package/plugins/extract-go/describe.go +0 -19
- package/plugins/extract-go/describe_test.go +0 -11
- package/plugins/extract-go/enum.go +0 -195
- package/plugins/extract-go/enum_test.go +0 -82
- package/plugins/extract-go/event.go +0 -99
- package/plugins/extract-go/extract.go +0 -191
- package/plugins/extract-go/extract_test.go +0 -261
- package/plugins/extract-go/flow.go +0 -1441
- package/plugins/extract-go/flow_test.go +0 -609
- package/plugins/extract-go/httpclient.go +0 -174
- package/plugins/extract-go/httpclient_test.go +0 -296
- package/plugins/extract-go/ids.go +0 -92
- package/plugins/extract-go/layout.go +0 -225
- package/plugins/extract-go/layout_test.go +0 -125
- package/plugins/extract-go/lifecycle.go +0 -324
- package/plugins/extract-go/lifecycle_test.go +0 -108
- package/plugins/extract-go/main.go +0 -79
- package/plugins/extract-go/operation.go +0 -157
- package/plugins/extract-go/source.go +0 -316
- package/plugins/extract-go/transport.go +0 -321
- package/plugins/extract-go/transport_test.go +0 -145
- package/plugins/extract-go/wiring.go +0 -434
- package/plugins/extract-go-nats/describe.go +0 -19
- package/plugins/extract-go-nats/describe_test.go +0 -11
- package/plugins/extract-go-nats/extract.go +0 -177
- package/plugins/extract-go-nats/extract_test.go +0 -389
- package/plugins/extract-go-nats/index.go +0 -379
- package/plugins/extract-go-nats/main.go +0 -42
- package/plugins/extract-go-nats/resolve.go +0 -161
- package/plugins/extract-go-nats/sites.go +0 -224
- package/plugins/extract-graphql/describe.go +0 -19
- package/plugins/extract-graphql/describe_test.go +0 -11
- package/plugins/extract-graphql/extract.go +0 -433
- package/plugins/extract-graphql/extract_test.go +0 -256
- package/plugins/extract-graphql/ids.go +0 -49
- package/plugins/extract-graphql/lex.go +0 -237
- package/plugins/extract-graphql/main.go +0 -51
- package/plugins/extract-graphql/parse.go +0 -621
- package/plugins/extract-graphql/parse_test.go +0 -122
- package/plugins/extract-http-clients/describe.go +0 -19
- package/plugins/extract-http-clients/describe_test.go +0 -11
- package/plugins/extract-http-clients/extract.go +0 -705
- package/plugins/extract-http-clients/extract_test.go +0 -1263
- package/plugins/extract-http-clients/main.go +0 -42
- package/plugins/extract-openapi/describe.go +0 -19
- package/plugins/extract-openapi/describe_test.go +0 -11
- package/plugins/extract-openapi/discover.go +0 -243
- package/plugins/extract-openapi/extract.go +0 -526
- package/plugins/extract-openapi/extract_test.go +0 -545
- package/plugins/extract-openapi/main.go +0 -75
- package/plugins/extract-openapi/spec.go +0 -350
- package/plugins/extract-project/describe.go +0 -19
- package/plugins/extract-project/describe_test.go +0 -11
- package/plugins/extract-project/extract.go +0 -221
- package/plugins/extract-project/extract_test.go +0 -109
- package/plugins/extract-project/main.go +0 -41
- package/plugins/extract-proto/ast.go +0 -125
- package/plugins/extract-proto/consumes.go +0 -77
- package/plugins/extract-proto/describe.go +0 -19
- package/plugins/extract-proto/extract.go +0 -293
- package/plugins/extract-proto/extract_test.go +0 -459
- package/plugins/extract-proto/ids.go +0 -89
- package/plugins/extract-proto/ids_test.go +0 -57
- package/plugins/extract-proto/lex.go +0 -285
- package/plugins/extract-proto/main.go +0 -100
- package/plugins/extract-proto/module.go +0 -120
- package/plugins/extract-proto/parse.go +0 -720
- package/plugins/extract-proto/parse_test.go +0 -307
- package/plugins/extract-proto/provides.go +0 -236
- package/plugins/extract-proto/resolve.go +0 -222
- package/plugins/extract-proto/resolve_test.go +0 -119
- package/plugins/extract-redis/describe.go +0 -19
- package/plugins/extract-redis/describe_test.go +0 -11
- package/plugins/extract-redis/extract.go +0 -183
- package/plugins/extract-redis/extract_test.go +0 -168
- package/plugins/extract-redis/keyspaces.go +0 -469
- package/plugins/extract-redis/main.go +0 -44
- package/plugins/extract-river/describe.go +0 -19
- package/plugins/extract-river/describe_test.go +0 -11
- package/plugins/extract-river/extract.go +0 -507
- package/plugins/extract-river/extract_test.go +0 -132
- package/plugins/extract-river/main.go +0 -42
- package/plugins/extract-sql/ddl.go +0 -893
- package/plugins/extract-sql/ddl_test.go +0 -401
- package/plugins/extract-sql/describe.go +0 -19
- package/plugins/extract-sql/describe_test.go +0 -11
- package/plugins/extract-sql/layout.go +0 -221
- package/plugins/extract-sql/layout_test.go +0 -55
- package/plugins/extract-sql/lineage.go +0 -107
- package/plugins/extract-sql/main.go +0 -141
- package/plugins/extract-sql/maps.go +0 -564
- package/plugins/extract-sql/maps_java.go +0 -117
- package/plugins/extract-sql/maps_rust.go +0 -333
- package/plugins/extract-sql/maps_rust_test.go +0 -70
- package/plugins/extract-sql/maps_test.go +0 -204
- package/plugins/extract-sql/maps_ts.go +0 -398
- package/plugins/extract-sql/maps_ts_test.go +0 -136
- package/plugins/extract-sql/projection.go +0 -70
- package/plugins/extract-sql/projection_test.go +0 -50
- package/plugins/extract-sql/store.go +0 -420
- package/plugins/extract-sql/store_test.go +0 -233
- package/plugins/extract-sql/view.go +0 -295
- package/plugins/extract-watermill/describe.go +0 -19
- package/plugins/extract-watermill/describe_test.go +0 -11
- package/plugins/extract-watermill/extract.go +0 -1228
- package/plugins/extract-watermill/extract_test.go +0 -234
- package/plugins/extract-watermill/main.go +0 -41
- package/plugins/extract-wsdl/describe.go +0 -19
- package/plugins/extract-wsdl/describe_test.go +0 -11
- package/plugins/extract-wsdl/extract.go +0 -148
- package/plugins/extract-wsdl/extract_test.go +0 -45
- package/plugins/extract-wsdl/main.go +0 -53
- package/plugins/fetch-bsr/auth.go +0 -114
- package/plugins/fetch-bsr/bsr.go +0 -240
- package/plugins/fetch-bsr/cache.go +0 -66
- package/plugins/fetch-bsr/describe.go +0 -19
- package/plugins/fetch-bsr/fetch.go +0 -192
- package/plugins/fetch-bsr/fetch_test.go +0 -465
- package/plugins/fetch-bsr/lock.go +0 -71
- package/plugins/fetch-bsr/main.go +0 -117
- package/plugins/fetch-csr/auth.go +0 -95
- package/plugins/fetch-csr/cache.go +0 -69
- package/plugins/fetch-csr/describe.go +0 -19
- package/plugins/fetch-csr/describe_test.go +0 -11
- package/plugins/fetch-csr/fetch.go +0 -254
- package/plugins/fetch-csr/fetch_test.go +0 -494
- package/plugins/fetch-csr/lock.go +0 -95
- package/plugins/fetch-csr/main.go +0 -122
- package/plugins/fetch-csr/registry.go +0 -209
- package/plugins/fetch-git/cache.go +0 -65
- package/plugins/fetch-git/describe.go +0 -19
- package/plugins/fetch-git/describe_test.go +0 -11
- package/plugins/fetch-git/fetch.go +0 -181
- package/plugins/fetch-git/fetch_test.go +0 -332
- package/plugins/fetch-git/git.go +0 -169
- package/plugins/fetch-git/lock.go +0 -72
- package/plugins/fetch-git/main.go +0 -127
- package/plugins/fetch-git/offline.go +0 -39
- package/plugins/fetch-git/pin.go +0 -90
- package/plugins/fetch-git/pin_test.go +0 -74
- package/plugins/gen-backstage/describe.go +0 -17
- package/plugins/gen-backstage/main.go +0 -22
- package/plugins/gen-backstage/plugin.go +0 -473
- package/plugins/gen-backstage/plugin_test.go +0 -145
- package/plugins/gen-backstage.wasm +0 -0
- package/plugins/gen-markdown/adr.go +0 -162
- package/plugins/gen-markdown/aggregate.go +0 -362
- package/plugins/gen-markdown/canonical.go +0 -93
- package/plugins/gen-markdown/context.go +0 -90
- package/plugins/gen-markdown/coverage_test.go +0 -89
- package/plugins/gen-markdown/describe.go +0 -19
- package/plugins/gen-markdown/describe_test.go +0 -11
- package/plugins/gen-markdown/external.go +0 -71
- package/plugins/gen-markdown/flow.go +0 -277
- package/plugins/gen-markdown/glossary.go +0 -76
- package/plugins/gen-markdown/glossary_test.go +0 -148
- package/plugins/gen-markdown/llms.go +0 -302
- package/plugins/gen-markdown/main.go +0 -22
- package/plugins/gen-markdown/markdown.go +0 -254
- package/plugins/gen-markdown/markdown_test.go +0 -100
- package/plugins/gen-markdown/module.go +0 -107
- package/plugins/gen-markdown/plugin.go +0 -39
- package/plugins/gen-markdown/quality_test.go +0 -176
- package/plugins/gen-markdown/redis_test.go +0 -38
- package/plugins/gen-markdown/render.go +0 -344
- package/plugins/gen-markdown/render_test.go +0 -186
- package/plugins/gen-markdown/service.go +0 -456
- package/plugins/gen-markdown/source.go +0 -177
- package/plugins/gen-markdown/store.go +0 -201
- package/plugins/gen-markdown.wasm +0 -0
- package/plugins/gen-mermaid/describe.go +0 -17
- package/plugins/gen-mermaid/main.go +0 -22
- package/plugins/gen-mermaid/plugin.go +0 -104
- package/plugins/gen-mermaid/plugin_test.go +0 -33
- package/plugins/gen-mermaid.wasm +0 -0
- package/plugins/openapi/ids.go +0 -261
- package/plugins/openapi/ids_test.go +0 -98
- package/plugins/verify-codeowners/describe.go +0 -19
- package/plugins/verify-codeowners/describe_test.go +0 -11
- package/plugins/verify-codeowners/main.go +0 -75
- package/plugins/verify-codeowners/match.go +0 -85
- package/plugins/verify-codeowners/match_test.go +0 -47
- package/plugins/verify-codeowners/owners.go +0 -164
- package/plugins/verify-codeowners/owners_test.go +0 -225
- package/plugins/verify-codeowners/parse.go +0 -90
- package/plugins/verify-codeowners/parse_test.go +0 -62
- package/plugins/verify-otel/describe.go +0 -19
- package/plugins/verify-otel/describe_test.go +0 -11
- package/plugins/verify-otel/main.go +0 -53
- package/plugins/verify-otel/match.go +0 -336
- package/plugins/verify-otel/otlp.go +0 -200
- package/plugins/verify-otel/verify.go +0 -734
- package/plugins/verify-otel/verify_test.go +0 -460
- /package/{plugins/fetch-csr/options.schema.json → scripts/host-plugins/fetch-csr.options.json} +0 -0
- /package/{plugins/fetch-git/options.schema.json → scripts/host-plugins/fetch-git.options.json} +0 -0
package/src/likec4/C4View.tsx
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { useCallback } from "react";
|
|
2
|
+
import type { ViewPadding } from "likec4/react";
|
|
2
3
|
import { InteractiveView } from "./InteractiveView";
|
|
3
4
|
import { catalogIdOf } from "./mapping";
|
|
4
5
|
import { fqn } from "./ids";
|
|
@@ -14,11 +15,14 @@ export function C4View({
|
|
|
14
15
|
viewId,
|
|
15
16
|
height = 320,
|
|
16
17
|
controls = false,
|
|
18
|
+
fitViewPadding,
|
|
17
19
|
}: {
|
|
18
20
|
viewId: string;
|
|
19
21
|
height?: number | string;
|
|
20
22
|
/** Zoom controls, useful when a whole estate has to fit on a phone. */
|
|
21
23
|
controls?: boolean;
|
|
24
|
+
/** Space around the fitted model; overview uses a tighter product frame. */
|
|
25
|
+
fitViewPadding?: ViewPadding;
|
|
22
26
|
}) {
|
|
23
27
|
const selection = useSelectionStore((s) => s.selection);
|
|
24
28
|
const source = useSelectionStore((s) => s.source);
|
|
@@ -46,6 +50,7 @@ export function C4View({
|
|
|
46
50
|
<InteractiveView
|
|
47
51
|
viewId={viewId}
|
|
48
52
|
controls={controls}
|
|
53
|
+
fitViewPadding={fitViewPadding}
|
|
49
54
|
highlightNodes={marked}
|
|
50
55
|
onNode={onNode}
|
|
51
56
|
onCanvas={onCanvas}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useCallback, useMemo, useRef } from "react";
|
|
2
2
|
import type { ReactNode } from "react";
|
|
3
|
-
import type { DiagramApi } from "likec4/react";
|
|
3
|
+
import type { DiagramApi, ViewPadding } from "likec4/react";
|
|
4
4
|
import { ReactLikeC4, isLikeC4ViewId } from "./generated";
|
|
5
5
|
import { useTheme } from "../app/theme";
|
|
6
6
|
import { Ident } from "../components/Ident";
|
|
@@ -12,6 +12,8 @@ export interface InteractiveViewProps {
|
|
|
12
12
|
/** Sequence for flows, diagram for element views. */
|
|
13
13
|
variant?: "diagram" | "sequence";
|
|
14
14
|
controls?: boolean;
|
|
15
|
+
/** Space around an initially fitted diagram. */
|
|
16
|
+
fitViewPadding?: ViewPadding;
|
|
15
17
|
walkthrough?: boolean;
|
|
16
18
|
/** LikeC4 element ids to mark; everything else on the canvas is dimmed. */
|
|
17
19
|
highlightNodes?: readonly string[];
|
|
@@ -71,6 +73,7 @@ export function InteractiveView({
|
|
|
71
73
|
viewId,
|
|
72
74
|
variant = "diagram",
|
|
73
75
|
controls = false,
|
|
76
|
+
fitViewPadding,
|
|
74
77
|
walkthrough = false,
|
|
75
78
|
highlightNodes = [],
|
|
76
79
|
highlightEdges = [],
|
|
@@ -122,6 +125,7 @@ export function InteractiveView({
|
|
|
122
125
|
background="dots"
|
|
123
126
|
controls={controls}
|
|
124
127
|
fitView
|
|
128
|
+
fitViewPadding={fitViewPadding}
|
|
125
129
|
pannable
|
|
126
130
|
zoomable
|
|
127
131
|
enableDynamicViewWalkthrough={walkthrough}
|
|
@@ -61,13 +61,33 @@ interface Drawn {
|
|
|
61
61
|
ready: boolean;
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
+
export interface ContextMapGraphProps {
|
|
65
|
+
catalog: Catalog;
|
|
66
|
+
relations: readonly ContextRelation[];
|
|
67
|
+
/**
|
|
68
|
+
* Whether the wheel zooms the map. On the map's own page it does; on a page
|
|
69
|
+
* the map is embedded in, the wheel belongs to the page and the map is
|
|
70
|
+
* zoomed by pinch or by the fit control instead.
|
|
71
|
+
*/
|
|
72
|
+
zoomOnScroll?: boolean;
|
|
73
|
+
/** Tighter geometry for small embedded canvases such as the landing hero. */
|
|
74
|
+
compactLayout?: boolean;
|
|
75
|
+
/** The full map exports files; embedded previews keep the canvas focused. */
|
|
76
|
+
showExport?: boolean;
|
|
77
|
+
/** Embedded previews keep the surrounding estate readable while touring it. */
|
|
78
|
+
inactiveEdgeOpacity?: number;
|
|
79
|
+
inactiveNodeOpacity?: number;
|
|
80
|
+
}
|
|
81
|
+
|
|
64
82
|
export function ContextMapGraph({
|
|
65
83
|
catalog,
|
|
66
84
|
relations,
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
85
|
+
zoomOnScroll = true,
|
|
86
|
+
compactLayout = false,
|
|
87
|
+
showExport = true,
|
|
88
|
+
inactiveEdgeOpacity = DIM,
|
|
89
|
+
inactiveNodeOpacity = 0.55,
|
|
90
|
+
}: ContextMapGraphProps) {
|
|
71
91
|
const navigate = useNavigate();
|
|
72
92
|
const selectionId = useSelectionStore((s) => s.selection?.id ?? null);
|
|
73
93
|
const select = useSelectionStore((s) => s.select);
|
|
@@ -126,8 +146,8 @@ export function ContextMapGraph({
|
|
|
126
146
|
// Far enough apart that a pattern name fits in the gap: "customer /
|
|
127
147
|
// supplier" is 110px of mono, and a label wider than the space between
|
|
128
148
|
// two boxes is a label lying across one of them.
|
|
129
|
-
edgeLength: 300,
|
|
130
|
-
nodeSpacing: 130,
|
|
149
|
+
edgeLength: compactLayout ? 210 : 300,
|
|
150
|
+
nodeSpacing: compactLayout ? 80 : 130,
|
|
131
151
|
});
|
|
132
152
|
if (cancelled) return;
|
|
133
153
|
|
|
@@ -185,7 +205,7 @@ export function ContextMapGraph({
|
|
|
185
205
|
return () => {
|
|
186
206
|
cancelled = true;
|
|
187
207
|
};
|
|
188
|
-
}, [catalog, lines]);
|
|
208
|
+
}, [catalog, compactLayout, lines]);
|
|
189
209
|
|
|
190
210
|
const fitKey = drawn.ready
|
|
191
211
|
? `fit-${drawn.nodes.length}-${drawn.edges.length}`
|
|
@@ -201,11 +221,14 @@ export function ContextMapGraph({
|
|
|
201
221
|
// line and its dimmed label can never disagree.
|
|
202
222
|
return {
|
|
203
223
|
...edge,
|
|
204
|
-
style: {
|
|
224
|
+
style: {
|
|
225
|
+
...edge.style,
|
|
226
|
+
opacity: lit ? 1 : inactiveEdgeOpacity,
|
|
227
|
+
},
|
|
205
228
|
zIndex: lit ? 10 : 0,
|
|
206
229
|
};
|
|
207
230
|
});
|
|
208
|
-
}, [drawn.edges, selectionId]);
|
|
231
|
+
}, [drawn.edges, inactiveEdgeOpacity, selectionId]);
|
|
209
232
|
|
|
210
233
|
const shownNodes = useMemo(
|
|
211
234
|
() =>
|
|
@@ -214,10 +237,13 @@ export function ContextMapGraph({
|
|
|
214
237
|
selected: node.id === selectionId,
|
|
215
238
|
style: {
|
|
216
239
|
...node.style,
|
|
217
|
-
opacity:
|
|
240
|
+
opacity:
|
|
241
|
+
selectionId && selectionId !== node.id
|
|
242
|
+
? inactiveNodeOpacity
|
|
243
|
+
: 1,
|
|
218
244
|
},
|
|
219
245
|
})),
|
|
220
|
-
[drawn.nodes, selectionId],
|
|
246
|
+
[drawn.nodes, inactiveNodeOpacity, selectionId],
|
|
221
247
|
);
|
|
222
248
|
|
|
223
249
|
const onNodeClick = useCallback(
|
|
@@ -231,7 +257,7 @@ export function ContextMapGraph({
|
|
|
231
257
|
);
|
|
232
258
|
|
|
233
259
|
return (
|
|
234
|
-
<div className="relative h-full w-full">
|
|
260
|
+
<div className="canvas-motion relative h-full w-full">
|
|
235
261
|
{drawn.ready ? null : <DiagramSkeleton />}
|
|
236
262
|
<ReactFlow
|
|
237
263
|
nodes={shownNodes}
|
|
@@ -246,7 +272,9 @@ export function ContextMapGraph({
|
|
|
246
272
|
elementsSelectable
|
|
247
273
|
proOptions={{ hideAttribution: true }}
|
|
248
274
|
fitView
|
|
249
|
-
fitViewOptions={{ padding: 0.16 }}
|
|
275
|
+
fitViewOptions={{ padding: compactLayout ? 0.06 : 0.16 }}
|
|
276
|
+
zoomOnScroll={zoomOnScroll}
|
|
277
|
+
preventScrolling={zoomOnScroll}
|
|
250
278
|
minZoom={0.2}
|
|
251
279
|
maxZoom={1.6}
|
|
252
280
|
key={fitKey}
|
|
@@ -254,18 +282,17 @@ export function ContextMapGraph({
|
|
|
254
282
|
<Background gap={20} size={2} />
|
|
255
283
|
{/* The map has no modes to switch, so its only control is the way
|
|
256
284
|
out: the drawing as a file, for the page that explains it. */}
|
|
257
|
-
|
|
258
|
-
<
|
|
259
|
-
|
|
285
|
+
{showExport ? (
|
|
286
|
+
<Panel position="top-right">
|
|
287
|
+
<ExportSeg name="context-map" />
|
|
288
|
+
</Panel>
|
|
289
|
+
) : null}
|
|
260
290
|
</ReactFlow>
|
|
261
291
|
</div>
|
|
262
292
|
);
|
|
263
293
|
}
|
|
264
294
|
|
|
265
|
-
export function ContextMapPane(props: {
|
|
266
|
-
catalog: Catalog;
|
|
267
|
-
relations: readonly ContextRelation[];
|
|
268
|
-
}) {
|
|
295
|
+
export function ContextMapPane(props: ContextMapGraphProps) {
|
|
269
296
|
return (
|
|
270
297
|
<ReactFlowProvider>
|
|
271
298
|
<ContextMapGraph {...props} />
|
package/src/pages/ContextMap.tsx
CHANGED
|
@@ -495,7 +495,11 @@ export function ContextMap() {
|
|
|
495
495
|
Map
|
|
496
496
|
</SectionTitle>
|
|
497
497
|
<div className="h-[340px] overflow-hidden rounded-card border border-line">
|
|
498
|
-
<ContextMapPane
|
|
498
|
+
<ContextMapPane
|
|
499
|
+
catalog={catalog}
|
|
500
|
+
relations={relations}
|
|
501
|
+
zoomOnScroll={false}
|
|
502
|
+
/>
|
|
499
503
|
</div>
|
|
500
504
|
</section>
|
|
501
505
|
|
package/src/pages/FlowDetail.tsx
CHANGED
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
useState,
|
|
9
9
|
} from "react";
|
|
10
10
|
import { useParams } from "react-router";
|
|
11
|
-
import { ChevronLeft, ChevronRight } from "lucide-react";
|
|
11
|
+
import { ChevronLeft, ChevronRight, MessageSquare } from "lucide-react";
|
|
12
12
|
import { saveCanvasImage } from "../lib/export-canvas";
|
|
13
13
|
import { allRepos, flowContexts, walkSteps } from "../catalog";
|
|
14
14
|
import type { Flow, Status, Step } from "../catalog";
|
|
@@ -55,6 +55,8 @@ import { ContextPill } from "../components/primitives";
|
|
|
55
55
|
import { WhatLinksHere } from "../components/WhatLinksHere";
|
|
56
56
|
import { FlowTrigger } from "../components/FlowTrigger";
|
|
57
57
|
import { NotFound } from "./NotFound";
|
|
58
|
+
import { useChatAvailable } from "../chat/prefs";
|
|
59
|
+
import { useChatUi } from "../chat/store";
|
|
58
60
|
|
|
59
61
|
/**
|
|
60
62
|
* The summary, clamped to two lines.
|
|
@@ -117,6 +119,7 @@ export function FlowDetail() {
|
|
|
117
119
|
const [statusFilter, setStatusFilter] = useState<Status | null>(null);
|
|
118
120
|
const [exporting, setExporting] = useState(false);
|
|
119
121
|
const say = useToastStore((s) => s.say);
|
|
122
|
+
const chatAvailable = useChatAvailable();
|
|
120
123
|
/** Empty means every branch at once — the union, not a run. */
|
|
121
124
|
const [pathId, setPathId] = useState("");
|
|
122
125
|
/** Chapters the reader has folded away. Empty means the whole flow is open. */
|
|
@@ -441,12 +444,23 @@ export function FlowDetail() {
|
|
|
441
444
|
{/* A flow belongs to no single context, so the wash takes the first one
|
|
442
445
|
it crosses - the context it starts in. */}
|
|
443
446
|
<div aria-hidden className="hero-wash" style={ctxStyle(contexts[0])} />
|
|
444
|
-
<div className="flex flex-wrap items-
|
|
445
|
-
<h1 className="text-
|
|
447
|
+
<div className="flex flex-wrap items-center gap-x-3 gap-y-2">
|
|
448
|
+
<h1 className="text-lg font-semibold" title={flow.name}>
|
|
446
449
|
{flow.name}
|
|
447
450
|
</h1>
|
|
448
451
|
<Ident value={flow.id} className="text-muted" />
|
|
449
452
|
<div className="ml-auto flex items-center gap-2">
|
|
453
|
+
{chatAvailable ? (
|
|
454
|
+
<button
|
|
455
|
+
type="button"
|
|
456
|
+
onClick={() => useChatUi.getState().setOpen(true)}
|
|
457
|
+
className="product-primary"
|
|
458
|
+
title={`Ask the catalog about ${flow.name}`}
|
|
459
|
+
>
|
|
460
|
+
<MessageSquare size={14} aria-hidden />
|
|
461
|
+
ask this flow
|
|
462
|
+
</button>
|
|
463
|
+
) : null}
|
|
450
464
|
<PinButton kind="flow" id={flow.id} label={flow.name} />
|
|
451
465
|
</div>
|
|
452
466
|
</div>
|
|
@@ -465,7 +479,8 @@ export function FlowDetail() {
|
|
|
465
479
|
What the row does NOT carry is anything about the picture: the view
|
|
466
480
|
id and the filter's own count are facts about what is on the canvas
|
|
467
481
|
right now, and they live with the controls that changed them. */}
|
|
468
|
-
<div className="mt-
|
|
482
|
+
<div className="mt-2 flex flex-wrap items-center gap-x-2.5 gap-y-1.5 rounded-control border border-line bg-surface px-2.5 py-1.5 shadow-xs">
|
|
483
|
+
<span className="label shrink-0 text-faint">evidence</span>
|
|
469
484
|
{flow.trigger ? <FlowTrigger trigger={flow.trigger} /> : null}
|
|
470
485
|
{flow.includes && flow.includes.length > 0 ? (
|
|
471
486
|
<span
|
|
@@ -501,9 +516,11 @@ export function FlowDetail() {
|
|
|
501
516
|
variant="line"
|
|
502
517
|
className="mt-0"
|
|
503
518
|
/>
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
519
|
+
<div className="flex flex-wrap items-center gap-1.5 sm:ml-auto">
|
|
520
|
+
{contexts.map((c) => (
|
|
521
|
+
<ContextPill key={c} id={c} name={contextName(c)} />
|
|
522
|
+
))}
|
|
523
|
+
</div>
|
|
507
524
|
</div>
|
|
508
525
|
|
|
509
526
|
<FlowToolbar
|
package/src/pages/Overview.tsx
CHANGED
|
@@ -3,11 +3,9 @@ import { useState } from "react";
|
|
|
3
3
|
import { Link } from "react-router";
|
|
4
4
|
import { AlertTriangle } from "lucide-react";
|
|
5
5
|
import { activeCatalogProfile, CATALOG_PATH, catalog, index } from "../data";
|
|
6
|
-
import { contextOwners, contextStats,
|
|
6
|
+
import { contextOwners, contextStats, widestFlows } from "../lib/derive";
|
|
7
7
|
import type { ContextOwner } from "../lib/derive";
|
|
8
|
-
import {
|
|
9
|
-
import { protoProblems } from "../lib/proto-problems";
|
|
10
|
-
import { wireProblems } from "../lib/wire-problems";
|
|
8
|
+
import { allProblems } from "../lib/all-problems";
|
|
11
9
|
import { ctxStyle } from "../lib/context-color";
|
|
12
10
|
import { middleTruncate, plural } from "../lib/format";
|
|
13
11
|
import { useCountUp, staggerStyle } from "../lib/motion";
|
|
@@ -19,6 +17,7 @@ import { profileContainersViewId, profileLandscapeViewId } from "../likec4/ids";
|
|
|
19
17
|
import { LevelSwitch } from "../likec4/levels";
|
|
20
18
|
import type { C4Level } from "../likec4/levels";
|
|
21
19
|
import { CatalogStamp } from "../components/CatalogStamp";
|
|
20
|
+
import { ProblemRow } from "../components/ProblemRow";
|
|
22
21
|
import { RowActions } from "../components/RowActions";
|
|
23
22
|
import { MachineDocs } from "../components/MachineDocs";
|
|
24
23
|
import {
|
|
@@ -26,6 +25,9 @@ import {
|
|
|
26
25
|
ContextPill,
|
|
27
26
|
} from "../components/primitives";
|
|
28
27
|
|
|
28
|
+
/** How many problems the overview lists before pointing at the page that lists them all. */
|
|
29
|
+
const OVERVIEW_PROBLEMS = 4;
|
|
30
|
+
|
|
29
31
|
/**
|
|
30
32
|
* A measurement that arrives rather than appears - 200ms, linear, once - and
|
|
31
33
|
* then takes you to what it measured. A number that counts something the app
|
|
@@ -99,12 +101,9 @@ export function Overview() {
|
|
|
99
101
|
(count, context) => count + context.services.length,
|
|
100
102
|
0,
|
|
101
103
|
);
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
...dataProblems(catalog, index),
|
|
106
|
-
...wireProblems(catalog, index),
|
|
107
|
-
].length;
|
|
104
|
+
const issues = allProblems(catalog, index);
|
|
105
|
+
const issueCount = issues.length;
|
|
106
|
+
const errorCount = issues.filter((p) => p.severity === "error").length;
|
|
108
107
|
|
|
109
108
|
return (
|
|
110
109
|
<div className="h-full overflow-y-auto p-gutter">
|
|
@@ -144,7 +143,7 @@ export function Overview() {
|
|
|
144
143
|
<HealthMetric
|
|
145
144
|
value={issueCount}
|
|
146
145
|
unit="problem"
|
|
147
|
-
to={paths.problems()}
|
|
146
|
+
to={issueCount > 0 ? `#${OVERVIEW_ANCHOR.problems}` : paths.problems()}
|
|
148
147
|
problem
|
|
149
148
|
/>
|
|
150
149
|
</div>
|
|
@@ -178,8 +177,9 @@ export function Overview() {
|
|
|
178
177
|
? profileContainersViewId(activeCatalogProfile.id)
|
|
179
178
|
: profileLandscapeViewId(activeCatalogProfile.id)
|
|
180
179
|
}
|
|
181
|
-
height={phone ?
|
|
180
|
+
height={phone ? 320 : level === 2 ? 660 : 580}
|
|
182
181
|
controls={phone}
|
|
182
|
+
fitViewPadding={{ x: 8, y: 8 }}
|
|
183
183
|
/>
|
|
184
184
|
</section>
|
|
185
185
|
|
|
@@ -338,6 +338,62 @@ export function Overview() {
|
|
|
338
338
|
</div>
|
|
339
339
|
</section>
|
|
340
340
|
|
|
341
|
+
{/* The first few problems, errors first, on the page a reader lands on.
|
|
342
|
+
The count in the health row says how many; this says which, so
|
|
343
|
+
"13 problems" is a list to start on rather than a number to worry
|
|
344
|
+
about. Nothing is shown for a clean estate - the metric already
|
|
345
|
+
says zero, and a heading over an empty list is a dead end. */}
|
|
346
|
+
{issueCount > 0 ? (
|
|
347
|
+
<section id={OVERVIEW_ANCHOR.problems} className="mt-section">
|
|
348
|
+
<SectionTitle
|
|
349
|
+
anchor={OVERVIEW_ANCHOR.problems}
|
|
350
|
+
right={
|
|
351
|
+
<span className="flex items-center gap-2">
|
|
352
|
+
<span className="section-aside">
|
|
353
|
+
<span className="text-unresolved">
|
|
354
|
+
{errorCount} {plural(errorCount, "error")}
|
|
355
|
+
</span>
|
|
356
|
+
{issueCount - errorCount > 0 ? (
|
|
357
|
+
<>
|
|
358
|
+
{" · "}
|
|
359
|
+
<span className="text-declared">
|
|
360
|
+
{issueCount - errorCount}{" "}
|
|
361
|
+
{plural(issueCount - errorCount, "warning")}
|
|
362
|
+
</span>
|
|
363
|
+
</>
|
|
364
|
+
) : null}
|
|
365
|
+
</span>
|
|
366
|
+
<Link
|
|
367
|
+
to={paths.problems()}
|
|
368
|
+
className="rounded-control px-1 text-accent hover:underline"
|
|
369
|
+
>
|
|
370
|
+
all problems →
|
|
371
|
+
</Link>
|
|
372
|
+
</span>
|
|
373
|
+
}
|
|
374
|
+
>
|
|
375
|
+
Problems
|
|
376
|
+
</SectionTitle>
|
|
377
|
+
<div className="flex max-w-table flex-col gap-2" data-nav-list>
|
|
378
|
+
{issues.slice(0, OVERVIEW_PROBLEMS).map((problem, i) => (
|
|
379
|
+
<ProblemRow
|
|
380
|
+
key={`${problem.kind}:${problem.id}:${problem.peer}`}
|
|
381
|
+
problem={problem}
|
|
382
|
+
index={i}
|
|
383
|
+
/>
|
|
384
|
+
))}
|
|
385
|
+
{issueCount > OVERVIEW_PROBLEMS ? (
|
|
386
|
+
<Link
|
|
387
|
+
to={paths.problems()}
|
|
388
|
+
className="mono self-start rounded-control px-1 text-muted hover:text-ink"
|
|
389
|
+
>
|
|
390
|
+
{issueCount - OVERVIEW_PROBLEMS} more →
|
|
391
|
+
</Link>
|
|
392
|
+
) : null}
|
|
393
|
+
</div>
|
|
394
|
+
</section>
|
|
395
|
+
) : null}
|
|
396
|
+
|
|
341
397
|
<MachineDocs className="mt-section pb-section" />
|
|
342
398
|
</div>
|
|
343
399
|
);
|
package/src/pages/Problems.tsx
CHANGED
|
@@ -6,62 +6,14 @@
|
|
|
6
6
|
|
|
7
7
|
import { useDocumentTitle } from "../app/title";
|
|
8
8
|
import { useMemo, useState } from "react";
|
|
9
|
-
import {
|
|
9
|
+
import { useSearchParams } from "react-router";
|
|
10
10
|
import { catalog, index } from "../data";
|
|
11
|
-
import { edgeCount
|
|
12
|
-
import
|
|
13
|
-
import {
|
|
14
|
-
import { protoProblems } from "../lib/proto-problems";
|
|
15
|
-
import { wireProblems } from "../lib/wire-problems";
|
|
16
|
-
import { contextVar, ctxStyle } from "../lib/context-color";
|
|
11
|
+
import { edgeCount } from "../lib/derive";
|
|
12
|
+
import { allProblems } from "../lib/all-problems";
|
|
13
|
+
import { contextVar } from "../lib/context-color";
|
|
17
14
|
import { absoluteTime, plural, relativeTime } from "../lib/format";
|
|
18
|
-
import { staggerStyle } from "../lib/motion";
|
|
19
|
-
import { KindIcon } from "../components/kind";
|
|
20
|
-
import { Ident } from "../components/Ident";
|
|
21
15
|
import { SectionTitle } from "../components/PageHeader";
|
|
22
|
-
import {
|
|
23
|
-
eventPath,
|
|
24
|
-
servicePath,
|
|
25
|
-
storePath,
|
|
26
|
-
tablePath,
|
|
27
|
-
viewPath,
|
|
28
|
-
} from "../routes";
|
|
29
|
-
|
|
30
|
-
/** The icon a problem row carries: what the near end of the edge IS. */
|
|
31
|
-
const KIND_OF: Record<Problem["kind"], "service" | "event" | "table"> = {
|
|
32
|
-
rpc: "service",
|
|
33
|
-
consumer: "event",
|
|
34
|
-
"cross-service-fk": "table",
|
|
35
|
-
"cross-service-lineage": "table",
|
|
36
|
-
"shared-store": "table",
|
|
37
|
-
"persistence-drift": "table",
|
|
38
|
-
"column-type": "table",
|
|
39
|
-
"outbox-payload": "table",
|
|
40
|
-
"proto-missing": "service",
|
|
41
|
-
"shared-channel": "event",
|
|
42
|
-
"channel-undeclared": "event",
|
|
43
|
-
"channel-unpublished": "service",
|
|
44
|
-
"subscription-unresolved": "service",
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
const KIND_NOTE: Record<Problem["kind"], string> = {
|
|
48
|
-
rpc: "the provider of this call is not in the catalog",
|
|
49
|
-
consumer: "this consumer of the event is not in the catalog",
|
|
50
|
-
"cross-service-fk": "foreign key across a service boundary",
|
|
51
|
-
"cross-service-lineage": "a value copied from another service's schema",
|
|
52
|
-
"shared-store": "a second service writes this database",
|
|
53
|
-
"persistence-drift": "this table no longer carries the aggregate it claims",
|
|
54
|
-
"column-type": "column type and domain type disagree",
|
|
55
|
-
"outbox-payload": "an outbox with no payload column",
|
|
56
|
-
"proto-missing":
|
|
57
|
-
"the provider is in the catalog but answers on no such method",
|
|
58
|
-
"shared-channel": "a second service publishes on this channel",
|
|
59
|
-
"channel-undeclared":
|
|
60
|
-
"this event goes out on a channel the service does not declare",
|
|
61
|
-
"channel-unpublished": "a declared channel no event of this service names",
|
|
62
|
-
"subscription-unresolved":
|
|
63
|
-
"nothing in the catalog publishes what this service listens for",
|
|
64
|
-
};
|
|
16
|
+
import { ProblemRow } from "../components/ProblemRow";
|
|
65
17
|
|
|
66
18
|
export function Problems() {
|
|
67
19
|
useDocumentTitle("Problems");
|
|
@@ -81,18 +33,7 @@ export function Problems() {
|
|
|
81
33
|
// Unresolved edges first, then everything the schema disagrees with. Within
|
|
82
34
|
// each, errors before warnings: a boundary leak is not the same kind of news
|
|
83
35
|
// as a column whose type has drifted, and mixing them buries the first.
|
|
84
|
-
const all = useMemo(() =>
|
|
85
|
-
const found = [
|
|
86
|
-
...problems(catalog),
|
|
87
|
-
...protoProblems(catalog, index),
|
|
88
|
-
...dataProblems(catalog, index),
|
|
89
|
-
...wireProblems(catalog, index),
|
|
90
|
-
];
|
|
91
|
-
return [
|
|
92
|
-
...found.filter((p) => p.severity === "error"),
|
|
93
|
-
...found.filter((p) => p.severity === "warning"),
|
|
94
|
-
];
|
|
95
|
-
}, []);
|
|
36
|
+
const all = useMemo(() => allProblems(catalog, index), []);
|
|
96
37
|
// How many edges there were to resolve at all. Zero problems out of zero
|
|
97
38
|
// edges is not a clean bill of health - nothing crossed a boundary, so
|
|
98
39
|
// nothing was checked, and saying "every edge resolved" there is a green
|
|
@@ -230,131 +171,3 @@ function ClearSkies({ checked }: { checked: number }) {
|
|
|
230
171
|
);
|
|
231
172
|
}
|
|
232
173
|
|
|
233
|
-
/** Where the near end of a problem lives, by what kind of edge it is. */
|
|
234
|
-
function nearPath(problem: Problem): string | null {
|
|
235
|
-
switch (problem.kind) {
|
|
236
|
-
case "rpc":
|
|
237
|
-
// The near end is the CALLING service either way. `rpc` is a call whose
|
|
238
|
-
// peer is nobody; this is a call whose peer is known and whose method is
|
|
239
|
-
// not - and in both the thing to go and look at is the caller.
|
|
240
|
-
case "proto-missing":
|
|
241
|
-
return servicePath(problem.service);
|
|
242
|
-
case "consumer":
|
|
243
|
-
case "channel-undeclared":
|
|
244
|
-
return eventPath(problem.id);
|
|
245
|
-
// The near end is an event when the service has one on the channel, and
|
|
246
|
-
// the service itself when the claim comes from its document alone.
|
|
247
|
-
case "shared-channel":
|
|
248
|
-
return eventPath(problem.id) ?? servicePath(problem.service);
|
|
249
|
-
case "channel-unpublished":
|
|
250
|
-
case "subscription-unresolved":
|
|
251
|
-
return servicePath(problem.service);
|
|
252
|
-
case "shared-store":
|
|
253
|
-
case "persistence-drift":
|
|
254
|
-
case "outbox-payload":
|
|
255
|
-
return tablePath(problem.id);
|
|
256
|
-
// The id is "<relation>.<column>", so the relation is its own id minus the
|
|
257
|
-
// last segment — and the relation is what the canvas can actually show.
|
|
258
|
-
case "cross-service-fk":
|
|
259
|
-
case "column-type":
|
|
260
|
-
return relationPath(problem.id.split(".").slice(0, -1).join("."));
|
|
261
|
-
// Lineage is stated on a column, except when a view states it whole, so
|
|
262
|
-
// the id is one or the other and both are looked up the same way.
|
|
263
|
-
case "cross-service-lineage":
|
|
264
|
-
return (
|
|
265
|
-
relationPath(problem.id) ??
|
|
266
|
-
relationPath(problem.id.split(".").slice(0, -1).join("."))
|
|
267
|
-
);
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
/** A table or a view, whichever the id turns out to name. */
|
|
272
|
-
function relationPath(id: string): string | null {
|
|
273
|
-
return tablePath(id) ?? viewPath(id);
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
/** Where the far end lives, when the catalog knows it. */
|
|
277
|
-
function peerPath(problem: Problem): string | null {
|
|
278
|
-
switch (problem.kind) {
|
|
279
|
-
case "cross-service-fk":
|
|
280
|
-
return tablePath(problem.peer);
|
|
281
|
-
case "cross-service-lineage":
|
|
282
|
-
return (
|
|
283
|
-
relationPath(problem.peer) ??
|
|
284
|
-
relationPath(problem.peer.split(".").slice(0, -1).join("."))
|
|
285
|
-
);
|
|
286
|
-
case "shared-store":
|
|
287
|
-
case "shared-channel":
|
|
288
|
-
return servicePath(problem.peer);
|
|
289
|
-
case "outbox-payload":
|
|
290
|
-
return storePath(problem.peer);
|
|
291
|
-
default:
|
|
292
|
-
return null;
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
function ProblemRow({ problem, index }: { problem: Problem; index: number }) {
|
|
297
|
-
const near = nearPath(problem);
|
|
298
|
-
const peerTo = peerPath(problem);
|
|
299
|
-
const tone =
|
|
300
|
-
problem.severity === "error"
|
|
301
|
-
? "var(--status-unresolved)"
|
|
302
|
-
: "var(--status-declared)";
|
|
303
|
-
|
|
304
|
-
return (
|
|
305
|
-
<div
|
|
306
|
-
className="stagger-in flex flex-wrap items-baseline gap-x-3 gap-y-1 rounded-control border px-3 py-2"
|
|
307
|
-
style={{ ...staggerStyle(index), borderColor: tone }}
|
|
308
|
-
>
|
|
309
|
-
<KindIcon kind={KIND_OF[problem.kind]} />
|
|
310
|
-
{near ? (
|
|
311
|
-
<Link
|
|
312
|
-
to={near}
|
|
313
|
-
data-nav-item
|
|
314
|
-
className="mono rounded-control text-accent hover:underline"
|
|
315
|
-
title={problem.id}
|
|
316
|
-
>
|
|
317
|
-
{problem.id}
|
|
318
|
-
</Link>
|
|
319
|
-
) : (
|
|
320
|
-
<Ident value={problem.id} />
|
|
321
|
-
)}
|
|
322
|
-
<span aria-hidden className="text-muted">
|
|
323
|
-
→
|
|
324
|
-
</span>
|
|
325
|
-
{peerTo ? (
|
|
326
|
-
<Link
|
|
327
|
-
to={peerTo}
|
|
328
|
-
className="mono rounded-control hover:underline"
|
|
329
|
-
style={{ color: tone }}
|
|
330
|
-
title={problem.peer}
|
|
331
|
-
>
|
|
332
|
-
{problem.peer}
|
|
333
|
-
</Link>
|
|
334
|
-
) : (
|
|
335
|
-
<Ident
|
|
336
|
-
value={problem.peer}
|
|
337
|
-
className={
|
|
338
|
-
problem.severity === "error" ? "text-unresolved" : "text-declared"
|
|
339
|
-
}
|
|
340
|
-
title={`${problem.peer} — ${KIND_NOTE[problem.kind]}. Click to copy.`}
|
|
341
|
-
/>
|
|
342
|
-
)}
|
|
343
|
-
<span className="chip ctx" style={ctxStyle(problem.context)}>
|
|
344
|
-
<span aria-hidden className="dot" />
|
|
345
|
-
{problem.context}
|
|
346
|
-
</span>
|
|
347
|
-
<span className="mono ml-auto text-muted">{KIND_NOTE[problem.kind]}</span>
|
|
348
|
-
{problem.note ? (
|
|
349
|
-
<p className="w-full border-l-2 pl-2 border-line-strong text-muted">
|
|
350
|
-
{problem.note}
|
|
351
|
-
</p>
|
|
352
|
-
) : null}
|
|
353
|
-
{problem.source ? (
|
|
354
|
-
<div className="mono w-full text-muted">
|
|
355
|
-
<Ident value={problem.source} />
|
|
356
|
-
</div>
|
|
357
|
-
) : null}
|
|
358
|
-
</div>
|
|
359
|
-
);
|
|
360
|
-
}
|