@shortlink-org/portolan 0.2.0 → 0.2.2
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/cli/init.mjs +14 -6
- package/cli/init.test.mjs +8 -1
- package/cli/portolan.mjs +56 -7
- package/cli/portolan.test.mjs +47 -0
- package/package.json +7 -6
- package/plugins/extract-django/README.md +1 -1
- package/plugins/extract-django/extract_test.py +1 -1
- package/plugins/extract-django/openapi.py +4 -14
- package/plugins/extract-django/openapi_test.py +4 -4
- package/plugins/extract-go/options.schema.json +5 -0
- package/plugins/extract-project/options.schema.json +21 -0
- package/plugins/extract-ts/README.md +1 -1
- package/plugins/extract-ts/clients.ts +1 -1
- package/plugins/extract-ts/extract.test.ts +2 -2
- package/plugins/extract-ts/graphql.test.ts +2 -2
- package/plugins/extract-ts/openapi.test.ts +6 -7
- package/plugins/extract-ts/openapi.ts +3 -10
- package/plugins/portolan-go.wasm +0 -0
- package/public/404-cat-v1.webp +0 -0
- package/public/cat-about-v1.webp +0 -0
- package/public/cat-clear-skies-v1.webp +0 -0
- package/public/cat-no-changes-v1.webp +0 -0
- package/public/cat-onboarding-v1.webp +0 -0
- package/public/cat-trial-success-v1.webp +0 -0
- package/schema/portolan.schema.json +61 -0
- package/scripts/catalog-sources.mjs +12 -1
- package/scripts/catalog-sources.test.mjs +35 -0
- package/scripts/gen-likec4.mjs +242 -85
- package/scripts/gen-likec4.test.mjs +168 -14
- package/scripts/gen.mjs +5 -3
- package/scripts/host-plugins/fetch-git.mjs +22 -12
- package/scripts/host-plugins/fetch-git.test.mjs +25 -5
- package/scripts/local-api.mjs +368 -34
- package/scripts/local-api.test.mjs +197 -2
- package/scripts/package-smoke.mjs +19 -2
- package/scripts/plugin-host.mjs +14 -3
- package/scripts/plugin-host.test.mjs +8 -0
- package/scripts/schema.mjs +7 -0
- package/src/app/CatalogApp.tsx +19 -4
- package/src/app/CommandPalette.tsx +7 -2
- package/src/app/toast.tsx +11 -7
- package/src/catalog.test.ts +33 -0
- package/src/catalog.ts +190 -8
- package/src/components/CatIllustration.tsx +30 -0
- package/src/components/CommitLink.tsx +154 -0
- package/src/components/EditorLink.tsx +15 -8
- package/src/components/GrpcMethodReference.tsx +93 -0
- package/src/components/Markdown.tsx +17 -0
- package/src/components/MethodRows.tsx +8 -1
- package/src/components/PageHeader.test.ts +24 -0
- package/src/components/PageHeader.tsx +34 -0
- package/src/components/ShapeRows.tsx +12 -0
- package/src/components/SourceDoc.tsx +27 -12
- package/src/components/SourcePreview.tsx +124 -36
- package/src/enrich.test.ts +209 -2
- package/src/enrich.ts +284 -15
- package/src/er/RedisSchema.tsx +86 -5
- package/src/flow/FlowTable.tsx +19 -4
- package/src/flow/StepDetail.tsx +461 -101
- package/src/flow/StepRail.tsx +36 -13
- package/src/flow/answers-response.test.ts +100 -0
- package/src/flow/answers.ts +11 -8
- package/src/flow/chapters.ts +2 -1
- package/src/flow/labels.ts +10 -0
- package/src/flow/mermaid.test.ts +87 -3
- package/src/flow/mermaid.ts +26 -14
- package/src/index.css +132 -0
- package/src/landing/LandingPage.tsx +12 -8
- package/src/lib/api.test.ts +4 -6
- package/src/lib/build-info.test.ts +29 -0
- package/src/lib/build-info.ts +21 -0
- package/src/lib/data-model.ts +14 -0
- package/src/lib/flow-tree.ts +1 -1
- package/src/lib/github-catalog.test.ts +51 -0
- package/src/lib/github-catalog.ts +69 -0
- package/src/lib/grpc-reference.test.ts +72 -0
- package/src/lib/grpc-reference.ts +117 -0
- package/src/lib/local-api.ts +17 -4
- package/src/lib/product.ts +7 -0
- package/src/lib/queries.ts +16 -1
- package/src/lib/readme-assets.test.ts +19 -0
- package/src/lib/readme-assets.ts +34 -0
- package/src/lib/setup-info.test.ts +7 -0
- package/src/lib/setup-info.ts +5 -0
- package/src/lib/source-link.test.ts +106 -18
- package/src/lib/source-link.ts +22 -6
- package/src/merge.test.ts +55 -0
- package/src/merge.ts +30 -0
- package/src/pages/AdrDetail.tsx +3 -2
- package/src/pages/AdrIndex.tsx +10 -7
- package/src/pages/AggregatePage.tsx +34 -4
- package/src/pages/BlockPage.tsx +39 -32
- package/src/pages/Changes.tsx +7 -9
- package/src/pages/ContextMap.tsx +11 -2
- package/src/pages/ContextPage.tsx +11 -4
- package/src/pages/FlowIndex.tsx +16 -7
- package/src/pages/GraphPage.tsx +21 -7
- package/src/pages/Language.tsx +9 -9
- package/src/pages/NotFound.tsx +58 -15
- package/src/pages/Overview.tsx +6 -0
- package/src/pages/Problems.tsx +10 -8
- package/src/pages/RegistryIndex.tsx +10 -8
- package/src/pages/ServicePage.tsx +1 -1
- package/src/pages/Settings.tsx +382 -44
- package/src/routes.test.ts +2 -0
- package/src/routes.ts +2 -1
- package/src/selection/DetailPanel.tsx +213 -113
- package/src/testing/setup.ts +14 -0
- package/vite.config.ts +5 -1
package/src/pages/ContextMap.tsx
CHANGED
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
// whose whole claim is that it does not hold opinions.
|
|
13
13
|
|
|
14
14
|
import { useDocumentTitle } from "../app/title";
|
|
15
|
-
import { useMemo } from "react";
|
|
16
|
-
import { Link } from "react-router";
|
|
15
|
+
import { useMemo, useState } from "react";
|
|
16
|
+
import { Link, useSearchParams } from "react-router";
|
|
17
17
|
import { ArrowRight, ArrowLeftRight, Boxes, Minus } from "lucide-react";
|
|
18
18
|
import { catalog } from "../data";
|
|
19
19
|
import { plural } from "../lib/format";
|
|
@@ -437,6 +437,8 @@ function SeparateWays({
|
|
|
437
437
|
|
|
438
438
|
export function ContextMap() {
|
|
439
439
|
useDocumentTitle("Context map");
|
|
440
|
+
const [search, setSearch] = useSearchParams();
|
|
441
|
+
const [tour, setTour] = useState(() => search.get("tour") === "1");
|
|
440
442
|
const relations = useMemo(() => contextMap(catalog), []);
|
|
441
443
|
const joined = relations.filter(
|
|
442
444
|
(r) => r.dependencies.length > 0 || r.shared.length > 0,
|
|
@@ -444,6 +446,12 @@ export function ContextMap() {
|
|
|
444
446
|
const apart = relations.filter((r) => !joined.includes(r));
|
|
445
447
|
const wired = joined.length;
|
|
446
448
|
|
|
449
|
+
if (catalog.contexts.length === 0) {
|
|
450
|
+
return (
|
|
451
|
+
<div className="h-full overflow-y-auto p-gutter"><div className="mx-auto mt-[12vh] max-w-2xl rounded-card border border-accent bg-canvas p-6 shadow-xs"><div className="label mb-2">context map</div><h1 className="text-xl font-semibold text-ink">Your architecture will appear here</h1><p className="mt-2 text-muted">Connect one project first. Portolan will turn detected components, APIs and dependencies into the first map without asking you to draw it by hand.</p><div className="mt-5 flex flex-wrap gap-2"><Link to={paths.settingsProjects()} className="product-primary">Connect your first project</Link><Link to={paths.settings()} className="tbtn">See setup steps</Link></div></div></div>
|
|
452
|
+
);
|
|
453
|
+
}
|
|
454
|
+
|
|
447
455
|
return (
|
|
448
456
|
<div className="h-full overflow-y-auto">
|
|
449
457
|
<div className="flex shrink-0 flex-wrap items-center gap-x-3 gap-y-2 border-b px-gutter py-3 border-line">
|
|
@@ -482,6 +490,7 @@ export function ContextMap() {
|
|
|
482
490
|
|
|
483
491
|
<div className="flex gap-section p-gutter">
|
|
484
492
|
<div className="min-w-0 flex-1">
|
|
493
|
+
{tour ? <section className="mb-section overflow-hidden rounded-card border border-accent bg-canvas shadow-xs"><div className="flex items-start justify-between gap-3 border-b border-line bg-surface px-card py-3"><div><div className="font-semibold text-ink">Your first map</div><p className="mt-1 text-muted">Three things are enough to start exploring.</p></div><button type="button" className="tbtn" onClick={() => { setTour(false); search.delete("tour"); setSearch(search, { replace: true }); try { localStorage.setItem("portolan.onboarding.map-tour.v1", "seen"); } catch {} }}>Got it</button></div><ol className="grid md:grid-cols-3"><li className="border-b border-line p-card md:border-r md:border-b-0"><span className="label text-accent">1 · contexts</span><p className="mt-1 text-muted">Each large boundary owns a language and responsibility.</p></li><li className="border-b border-line p-card md:border-r md:border-b-0"><span className="label text-accent">2 · components</span><p className="mt-1 text-muted">Click a context to highlight it; double-click to open its components.</p></li><li className="p-card"><span className="label text-accent">3 · evidence</span><p className="mt-1 text-muted">Relations below explain which calls and events created each connection.</p></li></ol></section> : null}
|
|
485
494
|
<section id={MAP_ANCHOR.model}>
|
|
486
495
|
<SectionTitle
|
|
487
496
|
anchor={MAP_ANCHOR.model}
|
|
@@ -144,22 +144,29 @@ export function ContextPage() {
|
|
|
144
144
|
className="card card-tagged stagger-in"
|
|
145
145
|
style={{ ...staggerStyle(i), ...ctxStyle(context.id) }}
|
|
146
146
|
>
|
|
147
|
-
<div className="flex items-baseline gap-2">
|
|
147
|
+
<div className="flex min-w-0 items-baseline gap-2">
|
|
148
148
|
<Link
|
|
149
149
|
to={paths.service(context.id, service.slug)}
|
|
150
150
|
data-nav-item
|
|
151
|
-
className="card-link rounded-control font-semibold"
|
|
151
|
+
className="card-link min-w-0 truncate rounded-control font-semibold"
|
|
152
152
|
title={service.name}
|
|
153
153
|
>
|
|
154
154
|
{service.name}
|
|
155
155
|
</Link>
|
|
156
|
-
<Ident value={service.id} className="text-muted" />
|
|
157
156
|
<RowActions
|
|
158
157
|
copy={service.id}
|
|
159
158
|
reveal={service.id}
|
|
160
159
|
label={service.name}
|
|
161
160
|
/>
|
|
162
|
-
|
|
161
|
+
</div>
|
|
162
|
+
{/* The id and kind get their own metadata row. Keeping the
|
|
163
|
+
kind after the hidden row actions made it overflow the
|
|
164
|
+
card before those actions were even visible. */}
|
|
165
|
+
<div className="mt-1.5 flex min-w-0 items-center justify-between gap-2">
|
|
166
|
+
<span className="min-w-0 overflow-hidden">
|
|
167
|
+
<Ident value={service.id} className="block truncate text-muted" />
|
|
168
|
+
</span>
|
|
169
|
+
<span className="chip shrink-0">{componentKind(service)}</span>
|
|
163
170
|
</div>
|
|
164
171
|
{/* Each number is a link into the part of the service page
|
|
165
172
|
that lists what it counted. */}
|
package/src/pages/FlowIndex.tsx
CHANGED
|
@@ -2,7 +2,7 @@ import { useDocumentTitle } from "../app/title";
|
|
|
2
2
|
import { useMemo, useState } from "react";
|
|
3
3
|
import { Link, useSearchParams } from "react-router";
|
|
4
4
|
import { ArrowUpDown, X } from "lucide-react";
|
|
5
|
-
import {
|
|
5
|
+
import { catalog } from "../data";
|
|
6
6
|
import { allRepos, flowContexts, walkSteps } from "../catalog";
|
|
7
7
|
import { FLOW_HEALTH_NOTE, flowHealth, flowOwner, statusCounts } from "../lib/flow-tree";
|
|
8
8
|
import type { FlowHealth } from "../lib/flow-tree";
|
|
@@ -15,9 +15,10 @@ import { middleTruncate } from "../lib/format";
|
|
|
15
15
|
import { staggerStyle } from "../lib/motion";
|
|
16
16
|
import { Ident } from "../components/Ident";
|
|
17
17
|
import { RowActions } from "../components/RowActions";
|
|
18
|
-
import {
|
|
18
|
+
import { CapabilityEmpty, Empty } from "../components/PageHeader";
|
|
19
19
|
import { ContextPill } from "../components/primitives";
|
|
20
20
|
import { FlowTrigger } from "../components/FlowTrigger";
|
|
21
|
+
import { paths } from "../routes";
|
|
21
22
|
|
|
22
23
|
type Sort = "contexts" | "name" | "steps" | "health";
|
|
23
24
|
|
|
@@ -189,11 +190,19 @@ export function FlowIndex() {
|
|
|
189
190
|
|
|
190
191
|
{bare ? (
|
|
191
192
|
<div className="mt-section">
|
|
192
|
-
<
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
193
|
+
<CapabilityEmpty
|
|
194
|
+
title={catalog.contexts.length === 0 ? "Connect a project before tracing flows" : "No end-to-end flows yet"}
|
|
195
|
+
signal="Signals: integration tests, message handlers or authored flows[] catalog fragments."
|
|
196
|
+
actions={<>
|
|
197
|
+
<Link className="product-primary" to={catalog.contexts.length === 0 ? paths.settingsProjects() : paths.settingsPipeline()}>{catalog.contexts.length === 0 ? "Connect a project" : "Review extraction"}</Link>
|
|
198
|
+
{catalog.contexts.length > 0 ? <Link className="tbtn" to={paths.map()}>Open context map</Link> : null}
|
|
199
|
+
</>}
|
|
200
|
+
>
|
|
201
|
+
{catalog.contexts.length === 0
|
|
202
|
+
? "Portolan needs a project before it can reconstruct a journey through the architecture."
|
|
203
|
+
: "A flow is one run across the estate. Portolan can reconstruct it from integration evidence or read an authored flow from the catalog."
|
|
204
|
+
}
|
|
205
|
+
</CapabilityEmpty>
|
|
197
206
|
</div>
|
|
198
207
|
) : rows.length === 0 ? (
|
|
199
208
|
<div className="mt-section">
|
package/src/pages/GraphPage.tsx
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { useDocumentTitle } from "../app/title";
|
|
2
2
|
import { useMemo, useState } from "react";
|
|
3
|
+
import { Link } from "react-router";
|
|
3
4
|
import { catalog } from "../data";
|
|
4
5
|
import { plural } from "../lib/format";
|
|
5
|
-
import { Empty } from "../components/PageHeader";
|
|
6
|
+
import { CapabilityEmpty, Empty } from "../components/PageHeader";
|
|
6
7
|
import {
|
|
7
8
|
bundles,
|
|
8
9
|
edgeCount,
|
|
@@ -14,6 +15,7 @@ import { statusColor, statusDash } from "../graph/theme";
|
|
|
14
15
|
import { DependencyGraphPane } from "../graph/DependencyGraph";
|
|
15
16
|
import type { GraphMode } from "../graph/dependency-layout";
|
|
16
17
|
import type { Status } from "../catalog";
|
|
18
|
+
import { paths } from "../routes";
|
|
17
19
|
|
|
18
20
|
const STATUSES: { status: Status; note: string }[] = [
|
|
19
21
|
{ status: "verified", note: "consumption observed" },
|
|
@@ -183,8 +185,8 @@ export function GraphPage() {
|
|
|
183
185
|
<div className="min-h-0 flex-1">
|
|
184
186
|
{nothing ? (
|
|
185
187
|
<div className="p-gutter">
|
|
186
|
-
|
|
187
|
-
|
|
188
|
+
{filtered ? (
|
|
189
|
+
<Empty>
|
|
188
190
|
<>
|
|
189
191
|
no event passes these filters.{" "}
|
|
190
192
|
<button
|
|
@@ -198,10 +200,22 @@ export function GraphPage() {
|
|
|
198
200
|
show everything
|
|
199
201
|
</button>
|
|
200
202
|
</>
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
203
|
+
</Empty>
|
|
204
|
+
) : (
|
|
205
|
+
<CapabilityEmpty
|
|
206
|
+
title={catalog.contexts.length === 0 ? "Connect a project before mapping dependencies" : "No dependency graph yet"}
|
|
207
|
+
signal="Signals: domain events, message consumers, AsyncAPI channels and integration contracts."
|
|
208
|
+
actions={<>
|
|
209
|
+
<Link className="product-primary" to={paths.settingsProjects()}>{catalog.contexts.length === 0 ? "Connect a project" : "Review project extraction"}</Link>
|
|
210
|
+
{catalog.contexts.length > 0 ? <Link className="tbtn" to={paths.map()}>Open context map</Link> : null}
|
|
211
|
+
</>}
|
|
212
|
+
>
|
|
213
|
+
{catalog.contexts.length === 0
|
|
214
|
+
? "Portolan needs at least one project before it can discover events and their consumers."
|
|
215
|
+
: "The catalog has components, but no events or consumers to connect yet. Review the selected extractors and the evidence they found."
|
|
216
|
+
}
|
|
217
|
+
</CapabilityEmpty>
|
|
218
|
+
)}
|
|
205
219
|
</div>
|
|
206
220
|
) : (
|
|
207
221
|
<DependencyGraphPane
|
package/src/pages/Language.tsx
CHANGED
|
@@ -13,9 +13,9 @@
|
|
|
13
13
|
import { useDocumentTitle } from "../app/title";
|
|
14
14
|
import { useMemo, useState } from "react";
|
|
15
15
|
import { Link, useSearchParams } from "react-router";
|
|
16
|
-
import {
|
|
16
|
+
import { catalog, index } from "../data";
|
|
17
17
|
import { allTerms } from "../catalog";
|
|
18
|
-
import {
|
|
18
|
+
import { CapabilityEmpty, Empty, SectionTitle } from "../components/PageHeader";
|
|
19
19
|
import { ContextPill } from "../components/primitives";
|
|
20
20
|
import { contextVar } from "../lib/context-color";
|
|
21
21
|
import { plural } from "../lib/format";
|
|
@@ -73,13 +73,13 @@ export function Language() {
|
|
|
73
73
|
<div className="h-full overflow-y-auto p-gutter">
|
|
74
74
|
<h1 className="text-lg font-semibold">Language</h1>
|
|
75
75
|
<div className="mt-section">
|
|
76
|
-
<
|
|
77
|
-
No
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
</
|
|
76
|
+
<CapabilityEmpty
|
|
77
|
+
title="No bounded-context language yet"
|
|
78
|
+
signal="Signal: GLOSSARY.md files discovered beside project documentation → terms[]."
|
|
79
|
+
actions={<Link className="product-primary" to={catalog.contexts.length === 0 ? paths.settingsProjects() : paths.settingsPipeline()}>{catalog.contexts.length === 0 ? "Connect a project" : "Review extraction"}</Link>}
|
|
80
|
+
>
|
|
81
|
+
A glossary records what a word means inside one boundary—and what it does not. Portolan combines those files to reveal shared terms and conflicting meanings.
|
|
82
|
+
</CapabilityEmpty>
|
|
83
83
|
</div>
|
|
84
84
|
</div>
|
|
85
85
|
);
|
package/src/pages/NotFound.tsx
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { House, Search } from "lucide-react";
|
|
2
|
+
import { Link, useLocation } from "react-router";
|
|
2
3
|
import { useDocumentTitle } from "../app/title";
|
|
3
4
|
|
|
4
5
|
/**
|
|
@@ -34,21 +35,63 @@ export function NotFound({ kind, id }: { kind: string; id?: string }) {
|
|
|
34
35
|
);
|
|
35
36
|
}
|
|
36
37
|
|
|
37
|
-
|
|
38
|
-
useDocumentTitle("Not found");
|
|
38
|
+
function LostExplorer() {
|
|
39
39
|
return (
|
|
40
|
-
<div className="
|
|
41
|
-
<
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
40
|
+
<div className="not-found-art">
|
|
41
|
+
<img
|
|
42
|
+
src={`${import.meta.env.BASE_URL}404-cat-v1.webp`}
|
|
43
|
+
alt="An explorer cat studying a folded map with a missing route"
|
|
44
|
+
className="not-found-cat"
|
|
45
|
+
draggable={false}
|
|
46
|
+
/>
|
|
47
|
+
</div>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function NotFoundPage({ onOpenSearch }: { onOpenSearch: () => void }) {
|
|
52
|
+
useDocumentTitle("Route not found");
|
|
53
|
+
const { pathname } = useLocation();
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<div className="not-found-page pane">
|
|
57
|
+
<section className="not-found-state" aria-labelledby="not-found-title">
|
|
58
|
+
<LostExplorer />
|
|
59
|
+
|
|
60
|
+
<div className="not-found-copy">
|
|
61
|
+
<p className="label text-accent">uncharted route</p>
|
|
62
|
+
<h1 id="not-found-title" className="mt-2 text-xl font-semibold">
|
|
63
|
+
Route not found
|
|
64
|
+
</h1>
|
|
65
|
+
<p className="mt-3 text-muted">
|
|
66
|
+
This path runs beyond the current catalog. It may have moved or
|
|
67
|
+
been renamed since the chart was drawn.
|
|
68
|
+
</p>
|
|
69
|
+
|
|
70
|
+
<div className="not-found-request mt-5">
|
|
71
|
+
<span className="mono text-faint">requested</span>
|
|
72
|
+
<code className="mono trunc text-ink" title={pathname}>
|
|
73
|
+
{pathname}
|
|
74
|
+
</code>
|
|
75
|
+
</div>
|
|
76
|
+
|
|
77
|
+
<div className="mt-6 flex flex-wrap items-center gap-2">
|
|
78
|
+
<button
|
|
79
|
+
type="button"
|
|
80
|
+
className="product-primary"
|
|
81
|
+
onClick={onOpenSearch}
|
|
82
|
+
aria-keyshortcuts="Meta+K Control+K"
|
|
83
|
+
>
|
|
84
|
+
<Search size={15} aria-hidden />
|
|
85
|
+
<span>Search catalog</span>
|
|
86
|
+
<kbd className="not-found-key">⌘K</kbd>
|
|
87
|
+
</button>
|
|
88
|
+
<Link to="/" className="tbtn py-1.5 text-ink">
|
|
89
|
+
<House size={15} aria-hidden />
|
|
90
|
+
Back to overview
|
|
91
|
+
</Link>
|
|
92
|
+
</div>
|
|
93
|
+
</div>
|
|
94
|
+
</section>
|
|
52
95
|
</div>
|
|
53
96
|
);
|
|
54
97
|
}
|
package/src/pages/Overview.tsx
CHANGED
|
@@ -105,6 +105,12 @@ export function Overview() {
|
|
|
105
105
|
const issueCount = issues.length;
|
|
106
106
|
const errorCount = issues.filter((p) => p.severity === "error").length;
|
|
107
107
|
|
|
108
|
+
if (catalog.contexts.length === 0) {
|
|
109
|
+
return (
|
|
110
|
+
<div className="h-full overflow-y-auto p-gutter"><div className="mx-auto mt-[12vh] max-w-2xl rounded-card border border-accent bg-canvas p-6 shadow-xs"><div className="label mb-2">welcome to Portolan</div><h1 className="text-xl font-semibold text-ink">Turn your first project into an architecture catalog</h1><p className="mt-2 text-muted">Start with one repository. Portolan detects its components and contracts, lets you review the evidence, then generates a navigable map.</p><div className="mt-5 flex flex-wrap gap-2"><Link to={paths.settingsProjects()} className="product-primary">Connect your first project</Link><Link to={paths.settings()} className="tbtn">Open setup guide</Link></div><div className="mono mt-5 border-t border-line pt-4 text-muted">source → context → evidence → preview → map</div></div></div>
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
108
114
|
return (
|
|
109
115
|
<div className="h-full overflow-y-auto p-gutter">
|
|
110
116
|
<div className="flex flex-col items-start gap-1 sm:flex-row sm:items-start sm:gap-x-4">
|
package/src/pages/Problems.tsx
CHANGED
|
@@ -14,6 +14,7 @@ import { contextVar } from "../lib/context-color";
|
|
|
14
14
|
import { absoluteTime, plural, relativeTime } from "../lib/format";
|
|
15
15
|
import { SectionTitle } from "../components/PageHeader";
|
|
16
16
|
import { ProblemRow } from "../components/ProblemRow";
|
|
17
|
+
import { CatEmptyState } from "../components/CatIllustration";
|
|
17
18
|
|
|
18
19
|
export function Problems() {
|
|
19
20
|
useDocumentTitle("Problems");
|
|
@@ -160,14 +161,15 @@ export function Problems() {
|
|
|
160
161
|
/** The one line the reader wants to see. Nothing else earns the space. */
|
|
161
162
|
function ClearSkies({ checked }: { checked: number }) {
|
|
162
163
|
return (
|
|
163
|
-
<
|
|
164
|
+
<CatEmptyState
|
|
165
|
+
scene="clear"
|
|
166
|
+
title={checked === 0 ? "Nothing to resolve yet" : "Clear skies"}
|
|
167
|
+
className="mt-section max-w-prose"
|
|
168
|
+
meta={<span title={absoluteTime(catalog.generatedAt)}>last checked {relativeTime(catalog.generatedAt)}</span>}
|
|
169
|
+
>
|
|
164
170
|
{checked === 0
|
|
165
|
-
? "
|
|
166
|
-
: `
|
|
167
|
-
|
|
168
|
-
last checked {relativeTime(catalog.generatedAt)}
|
|
169
|
-
</span>
|
|
170
|
-
</div>
|
|
171
|
+
? "No service calls another, and no event has a consumer."
|
|
172
|
+
: `All ${checked} ${plural(checked, "edge")} resolved.`}
|
|
173
|
+
</CatEmptyState>
|
|
171
174
|
);
|
|
172
175
|
}
|
|
173
|
-
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { useDocumentTitle } from "../app/title";
|
|
2
2
|
import { useMemo } from "react";
|
|
3
|
-
import {
|
|
3
|
+
import { Link } from "react-router";
|
|
4
|
+
import { catalog, index } from "../data";
|
|
4
5
|
import type { ProtoModule } from "../catalog";
|
|
5
|
-
import {
|
|
6
|
+
import { CapabilityEmpty, Empty } from "../components/PageHeader";
|
|
6
7
|
import { RowActions } from "../components/RowActions";
|
|
7
8
|
import { DataTable } from "../table/DataTable";
|
|
8
9
|
import type { ColumnSpec } from "../table/types";
|
|
@@ -88,12 +89,13 @@ export function RegistryIndex() {
|
|
|
88
89
|
|
|
89
90
|
{bare ? (
|
|
90
91
|
<div className="mt-section">
|
|
91
|
-
<
|
|
92
|
-
No schema modules yet
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
92
|
+
<CapabilityEmpty
|
|
93
|
+
title="No schema modules discovered yet"
|
|
94
|
+
signal="Signals: .proto files and registry metadata from BSR or local modules → modules[]."
|
|
95
|
+
actions={<Link className="product-primary" to={catalog.contexts.length === 0 ? paths.settingsProjects() : paths.settingsPipeline()}>{catalog.contexts.length === 0 ? "Connect a project" : "Review extraction"}</Link>}
|
|
96
|
+
>
|
|
97
|
+
Registry modules show where shared contracts are published, pinned and consumed. Review the project’s protobuf and registry extractors to populate this index.
|
|
98
|
+
</CapabilityEmpty>
|
|
97
99
|
</div>
|
|
98
100
|
) : (
|
|
99
101
|
<div className="mt-section max-w-table">
|
|
@@ -284,7 +284,7 @@ export function ServicePage() {
|
|
|
284
284
|
height={300}
|
|
285
285
|
/>
|
|
286
286
|
<div className="mt-section" />
|
|
287
|
-
<Markdown mermaid>{service.readme}</Markdown>
|
|
287
|
+
<Markdown mermaid sourceRoot={service.path}>{service.readme}</Markdown>
|
|
288
288
|
{/* What to type to build, test and run it. Under the README
|
|
289
289
|
rather than in a tab of its own: the list is short, it is the
|
|
290
290
|
first thing a reader new to the checkout wants, and the README
|