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
109 changes: 103 additions & 6 deletions apps/gittensory-ui/src/components/site/app-panels/owner-panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { useApiResource } from "@/lib/api/use-api-resource";
import {
buildRegistrationWorkspaceView,
splitRepoFullName,
type OwnerWorkflowState,
type GittensorConfigRecommendationPayload,
type RegistrationReadinessPayload,
type RegistrationWorkspaceView,
Expand All @@ -32,6 +33,12 @@ const FRESHNESS_STATUS_MAP: Record<RegistrationWorkspaceView["freshness"]["statu
unknown: "info",
};

const WORKFLOW_STATUS_MAP: Record<OwnerWorkflowState, Status> = {
accepted: "ready",
needs_cleanup: "warn",
not_ready: "blocked",
};

export function OwnerPanel() {
const [repo, setRepo] = useState("entrius/gittensor");
const parts = splitRepoFullName(repo.trim());
Expand Down Expand Up @@ -119,7 +126,7 @@ export function OwnerPanel() {
);
}

function RegistrationWorkspace({
export function RegistrationWorkspace({
workspace,
generatedLabel,
}: {
Expand All @@ -136,6 +143,9 @@ function RegistrationWorkspace({
<StatusPill status={LANE_STATUS_MAP[workspace.summary.status]}>
{workspace.summary.ready ? "Ready" : "Not ready"}
</StatusPill>
<StatusPill status={WORKFLOW_STATUS_MAP[workspace.workflow.overallState]}>
{formatWorkflowState(workspace.workflow.overallState)}
</StatusPill>
<StatusPill status={FRESHNESS_STATUS_MAP[workspace.freshness.status]}>
{workspace.freshness.status}
</StatusPill>
Expand Down Expand Up @@ -163,11 +173,50 @@ function RegistrationWorkspace({
</dl>
</section>

<section className="grid gap-4 lg:grid-cols-2">
<WorkspaceSectionCard section={workspace.lanes.directPr} />
<WorkspaceSectionCard section={workspace.lanes.issueDiscovery} />
<WorkspaceSectionCard section={workspace.lanes.maintainerEconomics} />
<WorkspaceSectionCard section={workspace.lanes.minerGuidance} />
<section className="space-y-4">
<div className="flex flex-wrap items-start justify-between gap-3">
<div>
<h3 className="font-display text-token-md font-semibold">Guided owner workflow</h3>
<p className="mt-1 text-token-xs text-muted-foreground">
{workspace.workflow.overallHeadline}
</p>
</div>
<StatusPill status={WORKFLOW_STATUS_MAP[workspace.workflow.overallState]}>
{formatWorkflowState(workspace.workflow.overallState)}
</StatusPill>
</div>

{workspace.workflow.nextSteps.length > 0 ? (
<div className="rounded-token border-hairline bg-card p-5">
<h4 className="font-medium">Next owner actions</h4>
<ul className="mt-3 list-disc space-y-1 pl-5 text-token-xs text-muted-foreground">
{workspace.workflow.nextSteps.map((step) => (
<li key={step}>{step}</li>
))}
</ul>
</div>
) : null}

<div className="grid gap-4 lg:grid-cols-2">
{workspace.workflow.buckets.map((bucket) => (
<OwnerWorkflowBucketCard key={bucket.id} bucket={bucket} />
))}
</div>
</section>

<section className="space-y-4">
<div>
<h3 className="font-display text-token-md font-semibold">Supporting readiness signals</h3>
<p className="mt-1 text-token-xs text-muted-foreground">
Detailed lane tradeoffs and operational signals behind the guided workflow.
</p>
</div>
<div className="grid gap-4 lg:grid-cols-2">
<WorkspaceSectionCard section={workspace.lanes.directPr} />
<WorkspaceSectionCard section={workspace.lanes.issueDiscovery} />
<WorkspaceSectionCard section={workspace.lanes.maintainerEconomics} />
<WorkspaceSectionCard section={workspace.lanes.minerGuidance} />
</div>
</section>

<section className="space-y-4">
Expand Down Expand Up @@ -252,6 +301,50 @@ function WorkspaceSectionCard({
);
}

function OwnerWorkflowBucketCard({
bucket,
}: {
bucket: RegistrationWorkspaceView["workflow"]["buckets"][number];
}) {
return (
<article className="rounded-token border-hairline bg-card p-5">
<div className="flex items-start justify-between gap-2">
<div>
<h4 className="font-medium">{bucket.title}</h4>
<p className="mt-1 text-token-xs text-muted-foreground">{bucket.summary}</p>
</div>
<StatusPill status={WORKFLOW_STATUS_MAP[bucket.state]}>
{formatWorkflowState(bucket.state)}
</StatusPill>
</div>

{bucket.items.length > 0 ? (
<ul className="mt-4 space-y-3">
{bucket.items.map((item) => (
<li key={item.id} className="rounded-token border border-border/80 bg-muted/10 p-3">
<div className="flex flex-wrap items-center gap-2">
<span className="text-token-xs font-medium text-foreground">{item.title}</span>
<StatusPill status={WORKFLOW_STATUS_MAP[item.state]} className="shrink-0">
{formatWorkflowState(item.state)}
</StatusPill>
</div>
<p className="mt-2 text-token-xs text-muted-foreground">{item.summary}</p>
<p className="mt-2 text-token-2xs text-muted-foreground">
<span className="font-mono uppercase tracking-wider text-foreground/70">
{item.remediationKind === "manual" ? "Manual follow-up" : "Action"}
</span>{" "}
{item.remediation}
</p>
</li>
))}
</ul>
) : (
<p className="mt-4 text-token-xs text-muted-foreground">No follow-up needed.</p>
)}
</article>
);
}

function Metric({ label, value }: { label: string; value: string }) {
return (
<div className="rounded-token border-hairline bg-muted/20 px-3 py-2">
Expand All @@ -268,3 +361,7 @@ function formatGeneratedAt(value: string) {
if (!Number.isFinite(parsed)) return value;
return new Date(parsed).toLocaleString();
}

function formatWorkflowState(state: OwnerWorkflowState) {
return state.replace(/_/g, " ");
}
2 changes: 1 addition & 1 deletion apps/gittensory-ui/src/lib/registration-workspace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -457,7 +457,7 @@ export function buildRegistrationOwnerWorkflow(
docsItems.push({
id: "docs-crawl",
title: "Onboarding docs",
state: "needs_cleanup",
state: "accepted",
summary: docs.note,
remediation:
"Manually verify CONTRIBUTING.md, README onboarding steps, and issue templates in GitHub; remote doc crawling is not enabled in this signal yet.",
Expand Down
2 changes: 1 addition & 1 deletion apps/gittensory-ui/src/routes/app.owner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ function OwnerRoute() {
<PageHeader
eyebrow="Owner"
title="Registration workspace"
description="Readiness report with direct-PR vs issue-discovery tradeoffs, maintainer economics, queue health, and suggested Gittensor config."
description="Guided owner workflow for policy, data quality, queue health, onboarding docs, maintainer capacity, and suggested Gittensor config."
/>
<OwnerPanel />
</div>
Expand Down
2 changes: 1 addition & 1 deletion test/unit/mcp-cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -916,7 +916,7 @@ describe("gittensory-mcp CLI", () => {
),
).rejects.toThrow("Refusing to print unsafe public packet markdown from the server.");
}
}, 10000);
}, 30000);

it("sends bounded structured validation summaries without local logs", async () => {
tempDir = mkdtempSync(join(tmpdir(), "gittensory-cli-"));
Expand Down
22 changes: 22 additions & 0 deletions test/unit/owner-panel-source.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { readFileSync } from "node:fs";

import { describe, expect, it } from "vitest";

const ownerPanelSource = readFileSync(
"apps/gittensory-ui/src/components/site/app-panels/owner-panel.tsx",
"utf8",
);

describe("owner panel workflow surface", () => {
it("renders the guided owner workflow as the primary section", () => {
expect(ownerPanelSource).toContain("Guided owner workflow");
expect(ownerPanelSource).toContain("Next owner actions");
expect(ownerPanelSource).toContain("Supporting readiness signals");
});

it("shows workflow bucket state pills and remediation labels", () => {
expect(ownerPanelSource).toContain("formatWorkflowState");
expect(ownerPanelSource).toContain("Manual follow-up");
expect(ownerPanelSource).toContain('item.remediationKind === "manual" ? "Manual follow-up" : "Action"');
});
});
6 changes: 3 additions & 3 deletions test/unit/registration-workspace-ui.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,10 +164,10 @@ describe("registration workspace UI helpers", () => {
"maintainer_capacity",
]);
const docs = workflow.buckets.find((bucket) => bucket.id === "docs_onboarding");
expect(docs?.state).toBe("needs_cleanup");
expect(docs?.state).toBe("accepted");
expect(docs?.items[0]?.remediationKind).toBe("manual");
expect(workflow.overallState).toBe("needs_cleanup");
expect(workflow.nextSteps.length).toBeGreaterThan(0);
expect(workflow.overallState).toBe("accepted");
expect(workflow.nextSteps).toEqual([]);
});

it("blocked readiness maps workflow to not ready with concrete blocker remediation", () => {
Expand Down