Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Callout } from "@/components/site/primitives";

// AMS (gittensory-miner) observability cross-reference (#5191). A dual-role self-hoster running both ORB (the
// review service) and AMS (the miner) on one box otherwise has no in-app pointer from the operations / quickstart /
// workflow docs to the miner's observability setup. Keeping the callout — and its link target — in one place keeps
// the wording byte-identical across all three routes instead of relying on three hand-copied copies staying in sync.
//
// The target is the in-repo "Observing your miner" guide (landed in #5190): the single AMS observability entry point,
// which itself covers pointing Grafana at the redacted AMS ledger datasources AND loading an AMS dashboard from
// grafana/dashboards/. It is a markdown guide, not an in-app /docs/* route, so a GitHub-blob link is the correct
// target here — the same convention the docs already use for in-repo file references (see docs.self-hosting-configuration.tsx).
export const AMS_OBSERVABILITY_DOC_URL =
"https://github.com/JSONbored/gittensory/blob/main/packages/gittensory-miner/docs/observability.md";

/** A `note` callout pointing a dual-role ORB+AMS operator at the "Observing your miner" observability guide. */
export function AmsObservabilityCallout() {
return (
<Callout variant="note" title="Running the miner on this box too?">
If you also run <strong>AMS</strong> (the <code>gittensory-miner</code>) on this host, see{" "}
<a href={AMS_OBSERVABILITY_DOC_URL} target="_blank" rel="noopener noreferrer">
Observing your miner
</a>{" "}
to point Grafana at the redacted AMS ledger datasources and load its Grafana dashboard —
separate from the ORB review-service observability above.
</Callout>
);
}
28 changes: 15 additions & 13 deletions apps/gittensory-ui/src/routeTree.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,9 @@ import { Route as DocsScoreabilityRouteImport } from './routes/docs.scoreability
import { Route as DocsQuickstartRouteImport } from './routes/docs.quickstart'
import { Route as DocsPrivacySecurityRouteImport } from './routes/docs.privacy-security'
import { Route as DocsOwnerChecklistRouteImport } from './routes/docs.owner-checklist'
import { Route as DocsMinerCodingAgentRouteImport } from './routes/docs.miner-coding-agent'
import { Route as DocsMinerWorkflowRouteImport } from './routes/docs.miner-workflow'
import { Route as DocsMinerQuickstartRouteImport } from './routes/docs.miner-quickstart'
import { Route as DocsMinerCodingAgentRouteImport } from './routes/docs.miner-coding-agent'
import { Route as DocsMcpClientsRouteImport } from './routes/docs.mcp-clients'
import { Route as DocsMaintainerWorkflowRouteImport } from './routes/docs.maintainer-workflow'
import { Route as DocsMaintainerSelfHostingRouteImport } from './routes/docs.maintainer-self-hosting'
Expand Down Expand Up @@ -252,11 +252,6 @@ const DocsOwnerChecklistRoute = DocsOwnerChecklistRouteImport.update({
path: '/owner-checklist',
getParentRoute: () => DocsRoute,
} as any)
const DocsMinerCodingAgentRoute = DocsMinerCodingAgentRouteImport.update({
id: '/miner-coding-agent',
path: '/miner-coding-agent',
getParentRoute: () => DocsRoute,
} as any)
const DocsMinerWorkflowRoute = DocsMinerWorkflowRouteImport.update({
id: '/miner-workflow',
path: '/miner-workflow',
Expand All @@ -267,6 +262,11 @@ const DocsMinerQuickstartRoute = DocsMinerQuickstartRouteImport.update({
path: '/miner-quickstart',
getParentRoute: () => DocsRoute,
} as any)
const DocsMinerCodingAgentRoute = DocsMinerCodingAgentRouteImport.update({
id: '/miner-coding-agent',
path: '/miner-coding-agent',
getParentRoute: () => DocsRoute,
} as any)
const DocsMcpClientsRoute = DocsMcpClientsRouteImport.update({
id: '/mcp-clients',
path: '/mcp-clients',
Expand Down Expand Up @@ -556,6 +556,7 @@ export interface FileRoutesById {
'/docs/maintainer-self-hosting': typeof DocsMaintainerSelfHostingRoute
'/docs/maintainer-workflow': typeof DocsMaintainerWorkflowRoute
'/docs/mcp-clients': typeof DocsMcpClientsRoute
'/docs/miner-coding-agent': typeof DocsMinerCodingAgentRoute
'/docs/miner-quickstart': typeof DocsMinerQuickstartRoute
'/docs/miner-workflow': typeof DocsMinerWorkflowRoute
'/docs/owner-checklist': typeof DocsOwnerChecklistRoute
Expand Down Expand Up @@ -746,6 +747,7 @@ export interface FileRouteTypes {
| '/docs/maintainer-self-hosting'
| '/docs/maintainer-workflow'
| '/docs/mcp-clients'
| '/docs/miner-coding-agent'
| '/docs/miner-quickstart'
| '/docs/miner-workflow'
| '/docs/owner-checklist'
Expand Down Expand Up @@ -1036,20 +1038,20 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof DocsMinerWorkflowRouteImport
parentRoute: typeof DocsRoute
}
'/docs/miner-coding-agent': {
id: '/docs/miner-coding-agent'
path: '/miner-coding-agent'
fullPath: '/docs/miner-coding-agent'
preLoaderRoute: typeof DocsMinerCodingAgentRouteImport
parentRoute: typeof DocsRoute
}
'/docs/miner-quickstart': {
id: '/docs/miner-quickstart'
path: '/miner-quickstart'
fullPath: '/docs/miner-quickstart'
preLoaderRoute: typeof DocsMinerQuickstartRouteImport
parentRoute: typeof DocsRoute
}
'/docs/miner-coding-agent': {
id: '/docs/miner-coding-agent'
path: '/miner-coding-agent'
fullPath: '/docs/miner-coding-agent'
preLoaderRoute: typeof DocsMinerCodingAgentRouteImport
parentRoute: typeof DocsRoute
}
'/docs/mcp-clients': {
id: '/docs/mcp-clients'
path: '/mcp-clients'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { render, screen } from "@testing-library/react";
import { type ReactNode } from "react";
import { describe, expect, it, vi } from "vitest";

import {
AMS_OBSERVABILITY_DOC_URL,
AmsObservabilityCallout,
} from "../components/site/ams-observability-callout";
import { MinerQuickstart } from "./docs.miner-quickstart";
import { MinerWorkflow } from "./docs.miner-workflow";
import { SelfHostingOperations } from "./docs.self-hosting-operations";

vi.mock("@tanstack/react-router", async (importOriginal) => {
const actual = await importOriginal<typeof import("@tanstack/react-router")>();
return {
...actual,
Link: ({ to, children }: { to: string; children: ReactNode }) => <a href={to}>{children}</a>,
};
});

vi.mock("@/components/site/docs-page", () => ({
DocsPage: ({ children, title }: { children: ReactNode; title: string }) => (
<div data-testid="docs-page">
<h1>{title}</h1>
{children}
</div>
),
}));

vi.mock("@/components/site/primitives", () => ({
Callout: ({ children, title }: { children: ReactNode; title?: string }) => (
<section data-testid="callout">
{title ? <strong>{title}</strong> : null}
<div>{children}</div>
</section>
),
CodeBlock: ({ code }: { code: string }) => <pre>{code}</pre>,
FeatureRow: ({ items }: { items: Array<{ title: string; description: string }> }) => (
<dl>
{items.map((item) => (
<div key={item.title}>
<dt>{item.title}</dt>
<dd>{item.description}</dd>
</div>
))}
</dl>
),
}));

vi.mock("@/components/site/workflow-mirror", () => ({
WorkflowMirror: () => <div data-testid="workflow-mirror" />,
}));

// Every route that embeds the shared callout, so a new route add/remove can't silently skip one (#5191).
const ROUTES_WITH_CALLOUT: ReadonlyArray<[string, () => ReactNode]> = [
["/docs/self-hosting-operations", SelfHostingOperations],
["/docs/miner-quickstart", MinerQuickstart],
["/docs/miner-workflow", MinerWorkflow],
];

describe("AMS observability cross-reference callout", () => {
it("renders a link to the Observing your miner guide", () => {
render(<AmsObservabilityCallout />);
const link = screen.getByRole("link", { name: "Observing your miner" });
expect(link.getAttribute("href")).toBe(AMS_OBSERVABILITY_DOC_URL);
});

it("targets a well-formed, non-empty absolute https URL (guards against a blank/copy-paste link)", () => {
expect(AMS_OBSERVABILITY_DOC_URL).toBeTruthy();
const url = new URL(AMS_OBSERVABILITY_DOC_URL);
expect(url.protocol).toBe("https:");
expect(url.hostname).toBe("github.com");
});

it.each(ROUTES_WITH_CALLOUT)("wires the callout into %s", (_path, RouteComponent) => {
const { container } = render(<RouteComponent />);
const link = container.querySelector(`a[href="${AMS_OBSERVABILITY_DOC_URL}"]`);
expect(link).not.toBeNull();
expect(link?.textContent).toBe("Observing your miner");
});
});
4 changes: 3 additions & 1 deletion apps/gittensory-ui/src/routes/docs.miner-quickstart.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { createFileRoute } from "@tanstack/react-router";

import { AmsObservabilityCallout } from "@/components/site/ams-observability-callout";
import { DocsPage } from "@/components/site/docs-page";
import { CodeBlock, Callout } from "@/components/site/primitives";
import { Link } from "@tanstack/react-router";
Expand All @@ -26,7 +27,7 @@ export const Route = createFileRoute("/docs/miner-quickstart")({
component: MinerQuickstart,
});

function MinerQuickstart() {
export function MinerQuickstart() {
return (
<DocsPage
eyebrow="Get started"
Expand Down Expand Up @@ -192,6 +193,7 @@ gittensory-mcp preflight --login your-login --repo owner/repo --base origin/main
page with the <a href="/docs/miner-workflow">miner workflow</a> for the full loop and{" "}
<a href="/docs/privacy-security">privacy &amp; security</a> for the boundary details.
</Callout>
<AmsObservabilityCallout />
</DocsPage>
);
}
4 changes: 3 additions & 1 deletion apps/gittensory-ui/src/routes/docs.miner-workflow.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { createFileRoute } from "@tanstack/react-router";
import { Link } from "@tanstack/react-router";

import { AmsObservabilityCallout } from "@/components/site/ams-observability-callout";
import { DocsPage } from "@/components/site/docs-page";
import { CodeBlock, Callout } from "@/components/site/primitives";
import { WorkflowMirror, type MirroredStep } from "@/components/site/workflow-mirror";
Expand All @@ -25,7 +26,7 @@ export const Route = createFileRoute("/docs/miner-workflow")({
component: MinerWorkflow,
});

function MinerWorkflow() {
export function MinerWorkflow() {
const steps: MirroredStep[] = [
{
title: "Plan",
Expand Down Expand Up @@ -136,6 +137,7 @@ function MinerWorkflow() {
commits, prefer cleaning open work over opening more — risk-adjusted priority is part of the
score model.
</Callout>
<AmsObservabilityCallout />
</DocsPage>
);
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { createFileRoute, Link } from "@tanstack/react-router";

import { AmsObservabilityCallout } from "@/components/site/ams-observability-callout";
import { DocsPage } from "@/components/site/docs-page";
import { Callout, CodeBlock, FeatureRow } from "@/components/site/primitives";

Expand All @@ -25,7 +26,7 @@ export const Route = createFileRoute("/docs/self-hosting-operations")({
component: SelfHostingOperations,
});

function SelfHostingOperations() {
export function SelfHostingOperations() {
return (
<DocsPage
eyebrow="Self-hosting"
Expand Down Expand Up @@ -96,6 +97,7 @@ selfhost_webhook_enqueue_binding_missing`}
code={`docker compose --profile postgres --profile observability up -d
docker compose --profile postgres --profile observability --profile backup up -d`}
/>
<AmsObservabilityCallout />

<h2>Host clock sync (NTP)</h2>
<Callout variant="warn" title="A single NTP source is a silent single point of failure">
Expand Down
Loading