fix(web): harden shell routes and add regression coverage
This commit is contained in:
parent
b1dee94173
commit
3ddfa25acb
@ -1,5 +1,5 @@
|
||||
import { AppShell } from "@/components/AppShell";
|
||||
import { mockNotes, mockWorkspaces } from "@/lib/mock-data";
|
||||
import { mockNotes, mockOperatorWorkflows, mockSavedViews, mockWorkspaces } from "@/lib/mock-data";
|
||||
|
||||
export default function DashboardPage() {
|
||||
const recentNotes = mockNotes.slice(0, 3);
|
||||
@ -8,7 +8,7 @@ export default function DashboardPage() {
|
||||
<AppShell
|
||||
title="Dashboard"
|
||||
description="Operational entry point for recent notes, active workspaces, and agent-relevant follow-ups."
|
||||
actions={<div className="badge">Scaffold milestone</div>}
|
||||
actions={<div className="badge">Operational shell</div>}
|
||||
>
|
||||
<section style={{ display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(240px, 1fr))", gap: "var(--ml-space-4)" }}>
|
||||
<div className="surface-card" style={{ padding: "var(--ml-space-5)" }}>
|
||||
@ -25,6 +25,46 @@ export default function DashboardPage() {
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section style={{ display: "grid", gridTemplateColumns: "minmax(0, 1.1fr) minmax(320px, 0.9fr)", gap: "var(--ml-space-4)" }}>
|
||||
<section className="surface-card" style={{ padding: "var(--ml-space-6)", display: "grid", gap: "var(--ml-space-4)" }}>
|
||||
<div style={{ fontSize: "var(--ml-fs-xl)", fontWeight: 700 }}>Saved views</div>
|
||||
<div style={{ display: "grid", gap: "var(--ml-space-3)" }}>
|
||||
{mockSavedViews.map((view) => (
|
||||
<article key={view.id} className="surface-muted" style={{ padding: "var(--ml-space-4)", display: "grid", gap: "var(--ml-space-2)" }}>
|
||||
<div style={{ display: "flex", justifyContent: "space-between", gap: "var(--ml-space-3)", flexWrap: "wrap" }}>
|
||||
<strong>{view.name}</strong>
|
||||
<span className="badge">{view.scope}</span>
|
||||
</div>
|
||||
<div style={{ color: "var(--ml-text-secondary)" }}>{view.description}</div>
|
||||
<div style={{ display: "flex", justifyContent: "space-between", gap: "var(--ml-space-3)", flexWrap: "wrap" }}>
|
||||
<span style={{ color: "var(--ml-text-secondary)" }}>{view.query}</span>
|
||||
<span style={{ color: "var(--ml-text-secondary)" }}>{view.resultCount} results</span>
|
||||
</div>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="surface-card" style={{ padding: "var(--ml-space-6)", display: "grid", gap: "var(--ml-space-4)" }}>
|
||||
<div style={{ fontSize: "var(--ml-fs-xl)", fontWeight: 700 }}>Operator workflows</div>
|
||||
<div style={{ display: "grid", gap: "var(--ml-space-3)" }}>
|
||||
{mockOperatorWorkflows.map((workflow) => (
|
||||
<article key={workflow.id} className="surface-muted" style={{ padding: "var(--ml-space-4)", display: "grid", gap: "var(--ml-space-2)" }}>
|
||||
<div style={{ display: "flex", justifyContent: "space-between", gap: "var(--ml-space-3)", flexWrap: "wrap" }}>
|
||||
<strong>{workflow.name}</strong>
|
||||
<span className="badge">{workflow.status}</span>
|
||||
</div>
|
||||
<div style={{ color: "var(--ml-text-secondary)" }}>Owner: {workflow.owner}</div>
|
||||
<div style={{ display: "flex", justifyContent: "space-between", gap: "var(--ml-space-3)", flexWrap: "wrap" }}>
|
||||
<span style={{ color: "var(--ml-text-secondary)" }}>Queue: {workflow.queueCount}</span>
|
||||
<span style={{ color: "var(--ml-text-secondary)" }}>SLA: {workflow.sla}</span>
|
||||
</div>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<section className="surface-card" style={{ padding: "var(--ml-space-6)", display: "grid", gap: "var(--ml-space-4)" }}>
|
||||
<div style={{ fontSize: "var(--ml-fs-xl)", fontWeight: 700 }}>Recent note activity</div>
|
||||
<div style={{ display: "grid", gap: "var(--ml-space-3)" }}>
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { AppShell } from "@/components/AppShell";
|
||||
import { AgentTimeline } from "@/components/AgentTimeline";
|
||||
import { ProposalReviewCard } from "@/components/ProposalReviewCard";
|
||||
import { mockOperatorWorkflows } from "@/lib/mock-data";
|
||||
import { mockAgentTimeline, mockApprovalQueue } from "@/lib/review-data";
|
||||
|
||||
export default function ReviewsPage() {
|
||||
@ -8,24 +9,50 @@ export default function ReviewsPage() {
|
||||
<AppShell
|
||||
title="Agent review"
|
||||
description="Approval queue, proposal comparison, and audit-oriented review surfaces for agent-mediated edits."
|
||||
actions={<div className="badge">W3 in progress</div>}
|
||||
actions={<div className="badge">Operator workflow shell</div>}
|
||||
>
|
||||
<section className="surface-card" style={{ padding: "var(--ml-space-5)", display: "grid", gap: "var(--ml-space-4)" }}>
|
||||
<div style={{ fontWeight: 700 }}>Approval queue</div>
|
||||
<div style={{ display: "grid", gap: "var(--ml-space-3)" }}>
|
||||
{mockApprovalQueue.map((item) => (
|
||||
<div key={item.id} className="surface-muted" style={{ padding: "var(--ml-space-4)", display: "flex", justifyContent: "space-between", gap: "var(--ml-space-3)", alignItems: "center", flexWrap: "wrap" }}>
|
||||
<div style={{ display: "grid", gap: 4 }}>
|
||||
<strong>{item.title}</strong>
|
||||
<span style={{ color: "var(--ml-text-secondary)" }}>{item.owner}</span>
|
||||
</div>
|
||||
<div style={{ display: "flex", gap: "var(--ml-space-2)", flexWrap: "wrap" }}>
|
||||
<span className="badge">{item.severity}</span>
|
||||
<span className="badge">{item.status}</span>
|
||||
<section style={{ display: "grid", gridTemplateColumns: "minmax(260px, 320px) minmax(0, 1fr)", gap: "var(--ml-space-4)" }}>
|
||||
<aside className="surface-card" style={{ padding: "var(--ml-space-5)", display: "grid", gap: "var(--ml-space-4)" }}>
|
||||
<div style={{ fontWeight: 700 }}>Operator workflows</div>
|
||||
<div style={{ display: "grid", gap: "var(--ml-space-3)" }}>
|
||||
{mockOperatorWorkflows.map((workflow) => (
|
||||
<div key={workflow.id} className="surface-muted" style={{ padding: "var(--ml-space-4)", display: "grid", gap: "var(--ml-space-2)" }}>
|
||||
<strong>{workflow.name}</strong>
|
||||
<span style={{ color: "var(--ml-text-secondary)" }}>Owner: {workflow.owner}</span>
|
||||
<div style={{ display: "flex", gap: "var(--ml-space-2)", flexWrap: "wrap" }}>
|
||||
<span className="badge">{workflow.status}</span>
|
||||
<span className="badge">Queue: {workflow.queueCount}</span>
|
||||
<span className="badge">SLA {workflow.sla}</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<section className="surface-card" style={{ padding: "var(--ml-space-5)", display: "grid", gap: "var(--ml-space-4)" }}>
|
||||
<div style={{ display: "flex", justifyContent: "space-between", gap: "var(--ml-space-3)", flexWrap: "wrap" }}>
|
||||
<div style={{ fontWeight: 700 }}>Approval queue</div>
|
||||
<div style={{ display: "flex", gap: "var(--ml-space-2)", flexWrap: "wrap" }}>
|
||||
<span className="badge">severity:medium+</span>
|
||||
<span className="badge">status:pending</span>
|
||||
<span className="badge">owner:any</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ display: "grid", gap: "var(--ml-space-3)" }}>
|
||||
{mockApprovalQueue.map((item) => (
|
||||
<div key={item.id} className="surface-muted" style={{ padding: "var(--ml-space-4)", display: "flex", justifyContent: "space-between", gap: "var(--ml-space-3)", alignItems: "center", flexWrap: "wrap" }}>
|
||||
<div style={{ display: "grid", gap: 4 }}>
|
||||
<strong>{item.title}</strong>
|
||||
<span style={{ color: "var(--ml-text-secondary)" }}>{item.owner}</span>
|
||||
</div>
|
||||
<div style={{ display: "flex", gap: "var(--ml-space-2)", flexWrap: "wrap" }}>
|
||||
<span className="badge">{item.severity}</span>
|
||||
<span className="badge">{item.status}</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<ProposalReviewCard
|
||||
|
||||
26
web/src/app/(app)/search/page.test.tsx
Normal file
26
web/src/app/(app)/search/page.test.tsx
Normal file
@ -0,0 +1,26 @@
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import SearchPage from "./page";
|
||||
|
||||
vi.mock("next/link", () => ({
|
||||
default: ({ href, children, ...props }: React.ComponentProps<"a"> & { href: string }) => (
|
||||
<a href={href} {...props}>
|
||||
{children}
|
||||
</a>
|
||||
),
|
||||
}));
|
||||
|
||||
describe("SearchPage", () => {
|
||||
it("renders an accessible search field, saved searches, and note links", () => {
|
||||
render(<SearchPage />);
|
||||
|
||||
expect(screen.getByRole("heading", { level: 1, name: "Search" })).toBeInTheDocument();
|
||||
expect(screen.getByRole("textbox", { name: "Search notes" })).toBeInTheDocument();
|
||||
expect(screen.getByText("Saved searches")).toBeInTheDocument();
|
||||
expect(screen.getByText("Launch readiness")).toBeInTheDocument();
|
||||
expect(screen.getByRole("link", { name: /MVP cut line for agentic notes launch/i })).toHaveAttribute(
|
||||
"href",
|
||||
"/notes/note-prd-cutline"
|
||||
);
|
||||
});
|
||||
});
|
||||
@ -1,33 +1,71 @@
|
||||
import Link from "next/link";
|
||||
import { AppShell } from "@/components/AppShell";
|
||||
import { mockNotes } from "@/lib/mock-data";
|
||||
import { mockNotes, mockSavedViews } from "@/lib/mock-data";
|
||||
|
||||
export default function SearchPage() {
|
||||
return (
|
||||
<AppShell
|
||||
title="Search"
|
||||
description="Lexical search, tag filtering, and retrieval entry points. Semantic ranking and explainability remain follow-up work."
|
||||
actions={<div className="badge">Advanced retrieval next</div>}
|
||||
actions={<div className="badge">Dense retrieval shell</div>}
|
||||
>
|
||||
<section className="surface-card" style={{ padding: "var(--ml-space-6)", display: "grid", gap: "var(--ml-space-4)" }}>
|
||||
<input className="input-shell" placeholder="Search notes, tags, tasks, and linked context" />
|
||||
<div style={{ display: "flex", gap: "var(--ml-space-2)", flexWrap: "wrap" }}>
|
||||
<span className="badge">workspace:all</span>
|
||||
<span className="badge">status:active</span>
|
||||
<span className="badge">source:manual+agent</span>
|
||||
</div>
|
||||
<div style={{ display: "grid", gap: "var(--ml-space-3)" }}>
|
||||
{mockNotes.map((note) => (
|
||||
<article key={note.id} className="surface-muted" style={{ padding: "var(--ml-space-4)", display: "grid", gap: "var(--ml-space-2)" }}>
|
||||
<strong>{note.title}</strong>
|
||||
<span style={{ color: "var(--ml-text-secondary)" }}>{note.excerpt}</span>
|
||||
<div style={{ display: "flex", gap: "var(--ml-space-2)", flexWrap: "wrap" }}>
|
||||
{note.tags.map((tag) => (
|
||||
<span key={tag} className="badge">#{tag}</span>
|
||||
))}
|
||||
</div>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
<section style={{ display: "grid", gridTemplateColumns: "minmax(260px, 320px) minmax(0, 1fr)", gap: "var(--ml-space-4)" }}>
|
||||
<aside className="surface-card" style={{ padding: "var(--ml-space-5)", display: "grid", gap: "var(--ml-space-4)" }}>
|
||||
<div style={{ fontWeight: 700 }}>Saved searches</div>
|
||||
<div style={{ display: "grid", gap: "var(--ml-space-3)" }}>
|
||||
{mockSavedViews
|
||||
.filter((view) => view.scope === "search")
|
||||
.map((view) => (
|
||||
<div key={view.id} className="surface-muted" style={{ padding: "var(--ml-space-4)", display: "grid", gap: "var(--ml-space-2)" }}>
|
||||
<strong>{view.name}</strong>
|
||||
<span style={{ color: "var(--ml-text-secondary)" }}>{view.query}</span>
|
||||
<span className="badge">{view.resultCount} results</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div style={{ display: "grid", gap: "var(--ml-space-2)" }}>
|
||||
<strong>Retrieval filters</strong>
|
||||
<span style={{ color: "var(--ml-text-secondary)" }}>workspace:any</span>
|
||||
<span style={{ color: "var(--ml-text-secondary)" }}>status:active + draft</span>
|
||||
<span style={{ color: "var(--ml-text-secondary)" }}>relationship scope: linked + cited</span>
|
||||
<span style={{ color: "var(--ml-text-secondary)" }}>explainability: matched fields</span>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<section className="surface-card" style={{ padding: "var(--ml-space-6)", display: "grid", gap: "var(--ml-space-4)" }}>
|
||||
<input
|
||||
aria-label="Search notes"
|
||||
className="input-shell"
|
||||
placeholder="Search notes, tags, tasks, and linked context"
|
||||
/>
|
||||
<div style={{ display: "flex", gap: "var(--ml-space-2)", flexWrap: "wrap" }}>
|
||||
<span className="badge">workspace:all</span>
|
||||
<span className="badge">status:active</span>
|
||||
<span className="badge">source:manual+agent</span>
|
||||
<span className="badge">matched:title+tags</span>
|
||||
</div>
|
||||
<div style={{ display: "grid", gap: "var(--ml-space-3)" }}>
|
||||
{mockNotes.map((note) => (
|
||||
<Link key={note.id} href={`/notes/${note.id}`} className="surface-muted" style={{ padding: "var(--ml-space-4)", display: "grid", gap: "var(--ml-space-2)" }}>
|
||||
<div style={{ display: "grid", gridTemplateColumns: "minmax(0, 1.5fr) repeat(3, minmax(100px, auto))", gap: "var(--ml-space-3)", alignItems: "start" }}>
|
||||
<div style={{ display: "grid", gap: "var(--ml-space-2)" }}>
|
||||
<strong>{note.title}</strong>
|
||||
<span style={{ color: "var(--ml-text-secondary)" }}>{note.excerpt}</span>
|
||||
</div>
|
||||
<span style={{ color: "var(--ml-text-secondary)" }}>{note.status}</span>
|
||||
<span style={{ color: "var(--ml-text-secondary)" }}>{note.updatedBy}</span>
|
||||
<span style={{ color: "var(--ml-text-secondary)" }}>{note.workspaceId.replace("workspace-", "")}</span>
|
||||
</div>
|
||||
<div style={{ display: "flex", gap: "var(--ml-space-2)", flexWrap: "wrap" }}>
|
||||
{note.tags.map((tag) => (
|
||||
<span key={tag} className="badge">#{tag}</span>
|
||||
))}
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
</AppShell>
|
||||
);
|
||||
|
||||
26
web/src/app/(app)/workspaces/page.test.tsx
Normal file
26
web/src/app/(app)/workspaces/page.test.tsx
Normal file
@ -0,0 +1,26 @@
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import WorkspacesPage from "./page";
|
||||
|
||||
vi.mock("next/link", () => ({
|
||||
default: ({ href, children, ...props }: React.ComponentProps<"a"> & { href: string }) => (
|
||||
<a href={href} {...props}>
|
||||
{children}
|
||||
</a>
|
||||
),
|
||||
}));
|
||||
|
||||
describe("WorkspacesPage", () => {
|
||||
it("renders an accessible workspace filter and saved workspace views", () => {
|
||||
render(<WorkspacesPage />);
|
||||
|
||||
expect(screen.getByRole("heading", { level: 1, name: "Workspaces" })).toBeInTheDocument();
|
||||
expect(screen.getByRole("textbox", { name: "Filter workspaces" })).toBeInTheDocument();
|
||||
expect(screen.getByText("Saved views")).toBeInTheDocument();
|
||||
expect(screen.getByText("All workspaces")).toBeInTheDocument();
|
||||
expect(screen.getByRole("link", { name: /MVP cut line for agentic notes launch/i })).toHaveAttribute(
|
||||
"href",
|
||||
"/notes/note-prd-cutline"
|
||||
);
|
||||
});
|
||||
});
|
||||
@ -1,6 +1,6 @@
|
||||
import Link from "next/link";
|
||||
import { AppShell } from "@/components/AppShell";
|
||||
import { getNotesForWorkspace, mockWorkspaces } from "@/lib/mock-data";
|
||||
import { getNotesForWorkspace, mockSavedViews, mockWorkspaces } from "@/lib/mock-data";
|
||||
|
||||
export default function WorkspacesPage() {
|
||||
return (
|
||||
@ -9,15 +9,36 @@ export default function WorkspacesPage() {
|
||||
description="Workspace-level organization, filters, and saved-view entry points for note collections."
|
||||
actions={<div className="badge">Saved views scaffolded</div>}
|
||||
>
|
||||
<section className="surface-card" style={{ padding: "var(--ml-space-5)", display: "grid", gap: "var(--ml-space-4)" }}>
|
||||
<div style={{ display: "grid", gap: "var(--ml-space-3)" }}>
|
||||
<input className="input-shell" placeholder="Filter workspaces by owner, tag, or visibility" />
|
||||
<div style={{ display: "flex", gap: "var(--ml-space-2)", flexWrap: "wrap" }}>
|
||||
<span className="badge">Saved view: All workspaces</span>
|
||||
<span className="badge">Saved view: Product strategy</span>
|
||||
<span className="badge">Saved view: Agent review</span>
|
||||
<section style={{ display: "grid", gridTemplateColumns: "minmax(260px, 320px) minmax(0, 1fr)", gap: "var(--ml-space-4)" }}>
|
||||
<aside className="surface-card" style={{ padding: "var(--ml-space-5)", display: "grid", gap: "var(--ml-space-4)" }}>
|
||||
<div style={{ fontWeight: 700 }}>Saved views</div>
|
||||
<div style={{ display: "grid", gap: "var(--ml-space-3)" }}>
|
||||
{mockSavedViews
|
||||
.filter((view) => view.scope === "workspace")
|
||||
.map((view) => (
|
||||
<div key={view.id} className="surface-muted" style={{ padding: "var(--ml-space-4)", display: "grid", gap: "var(--ml-space-2)" }}>
|
||||
<strong>{view.name}</strong>
|
||||
<span style={{ color: "var(--ml-text-secondary)" }}>{view.description}</span>
|
||||
<span className="badge">{view.resultCount} results</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<section className="surface-card" style={{ padding: "var(--ml-space-5)", display: "grid", gap: "var(--ml-space-4)" }}>
|
||||
<div style={{ display: "grid", gap: "var(--ml-space-3)" }}>
|
||||
<input
|
||||
aria-label="Filter workspaces"
|
||||
className="input-shell"
|
||||
placeholder="Filter workspaces by owner, tag, or visibility"
|
||||
/>
|
||||
<div style={{ display: "flex", gap: "var(--ml-space-2)", flexWrap: "wrap" }}>
|
||||
<span className="badge">owner:any</span>
|
||||
<span className="badge">visibility:any</span>
|
||||
<span className="badge">sort:updated</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<section style={{ display: "grid", gap: "var(--ml-space-4)" }}>
|
||||
@ -30,7 +51,10 @@ export default function WorkspacesPage() {
|
||||
<div style={{ fontSize: "var(--ml-fs-xl)", fontWeight: 700 }}>{workspace.name}</div>
|
||||
<div style={{ color: "var(--ml-text-secondary)", marginTop: 6 }}>{workspace.description}</div>
|
||||
</div>
|
||||
<div className="badge">{workspace.visibility}</div>
|
||||
<div style={{ display: "grid", gap: 8, justifyItems: "end" }}>
|
||||
<div className="badge">{workspace.visibility}</div>
|
||||
<span style={{ color: "var(--ml-text-secondary)" }}>Owner: {workspace.owner}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ display: "flex", gap: "var(--ml-space-2)", flexWrap: "wrap" }}>
|
||||
{workspace.tags.map((tag) => (
|
||||
|
||||
@ -15,7 +15,7 @@ const navItems = [
|
||||
];
|
||||
|
||||
export function Sidebar() {
|
||||
const pathname = usePathname();
|
||||
const pathname = usePathname() ?? "";
|
||||
|
||||
return (
|
||||
<aside className="sidebar" style={{ padding: "var(--ml-space-6)" }} aria-label="Primary">
|
||||
|
||||
@ -1,4 +1,10 @@
|
||||
import type { NoteDetail, NoteSummary, WorkspaceSummary } from "@/lib/types";
|
||||
import type {
|
||||
NoteDetail,
|
||||
NoteSummary,
|
||||
OperatorWorkflowSummary,
|
||||
SavedViewSummary,
|
||||
WorkspaceSummary,
|
||||
} from "@/lib/types";
|
||||
|
||||
export const mockWorkspaces: WorkspaceSummary[] = [
|
||||
{
|
||||
@ -76,6 +82,60 @@ export const mockNotes: NoteSummary[] = [
|
||||
},
|
||||
];
|
||||
|
||||
export const mockSavedViews: SavedViewSummary[] = [
|
||||
{
|
||||
id: "view-all-workspaces",
|
||||
name: "All workspaces",
|
||||
scope: "workspace",
|
||||
description: "Default operational workspace listing across all note domains.",
|
||||
query: "visibility:any sort:updated",
|
||||
resultCount: 3,
|
||||
},
|
||||
{
|
||||
id: "view-launch-readiness",
|
||||
name: "Launch readiness",
|
||||
scope: "search",
|
||||
description: "Notes tagged for launch, cut-line, approval, and release follow-up.",
|
||||
query: "tag:launch tag:mvp status:active",
|
||||
resultCount: 2,
|
||||
},
|
||||
{
|
||||
id: "view-agent-review",
|
||||
name: "Agent review queue",
|
||||
scope: "review",
|
||||
description: "Proposals and approval items requiring operator attention.",
|
||||
query: "review:proposed severity:medium+",
|
||||
resultCount: 2,
|
||||
},
|
||||
];
|
||||
|
||||
export const mockOperatorWorkflows: OperatorWorkflowSummary[] = [
|
||||
{
|
||||
id: "workflow-approvals",
|
||||
name: "Approval triage",
|
||||
queueCount: 2,
|
||||
owner: "Operator",
|
||||
sla: "< 4h",
|
||||
status: "healthy",
|
||||
},
|
||||
{
|
||||
id: "workflow-artifacts",
|
||||
name: "Artifact follow-up",
|
||||
queueCount: 3,
|
||||
owner: "Knowledge Ops",
|
||||
sla: "< 1d",
|
||||
status: "at_risk",
|
||||
},
|
||||
{
|
||||
id: "workflow-search-gaps",
|
||||
name: "Search quality review",
|
||||
queueCount: 1,
|
||||
owner: "Web Agent",
|
||||
sla: "< 2d",
|
||||
status: "healthy",
|
||||
},
|
||||
];
|
||||
|
||||
export const mockNoteDetails: Record<string, NoteDetail> = {
|
||||
"note-prd-cutline": {
|
||||
...mockNotes[0],
|
||||
|
||||
Loading…
Reference in New Issue
Block a user