paperclip/packages/plugins/plugin-llm-wiki/tests/screenshots/harness.tsx

1062 lines
41 KiB
TypeScript
Raw Normal View History

[codex] Add LLM Wiki plugin package to master (#5716) ## Thinking Path > - Paperclip orchestrates AI agents for zero-human companies. > - The plugin system is the extension surface for optional product capabilities without baking every workflow into core. > - The LLM Wiki plugin package was reviewed in stacked PR #5592, which targeted `pap-9173-llm-wiki-rest`. > - The stack base PR #5597 merged to `master` before #5592 was merged into that branch, so the plugin package never reached `master`. > - A direct PR from `pap-9173-llm-wiki-rest` back to `master` would be noisy because that branch has diverged from current `master`. > - This pull request reapplies the reviewed `packages/plugins/plugin-llm-wiki/` package onto current `master` and updates Docker deps-stage manifest coverage. > - The branch intentionally no longer changes `pnpm-workspace.yaml` after maintainer feedback; because the new package is now a root workspace importer, the remaining integration question is how maintainers want the root lockfile handled under the current PR policy. ## What Changed - Added the LLM Wiki plugin package under `packages/plugins/plugin-llm-wiki/` from the merged PR #5592 head. - Preserved the post-review cleanup from #5592: generated design/screenshot artifacts are not committed, and `src/ui/index.tsx` / `src/wiki.ts` are small public entrypoints. - Added the new plugin package manifest to the Docker deps stage so policy can validate package manifest coverage. - Removed the earlier `pnpm-workspace.yaml` exclusion per maintainer request, so the plugin is included by the existing `packages/plugins/*` workspace glob. ## Verification Current head: - PGlite migration harness: ran migrations 001-003, verified old non-space distillation unique constraints were removed, inserted duplicate cursor and work-item keys in a second space, then reran migration 003 successfully - `node ./scripts/check-docker-deps-stage.mjs` - `git diff --check` Known current-head install result after removing the workspace exclusion: - `pnpm install --frozen-lockfile` fails because `pnpm-lock.yaml` has no importer for `packages/plugins/plugin-llm-wiki/package.json`. Previously verified on the same plugin source before the workspace-exclusion removal: - `pnpm --filter @paperclipai/plugin-sdk build` - `cd packages/plugins/plugin-llm-wiki && pnpm install --lockfile=false && pnpm test` ## Risks - The branch now includes `packages/plugins/plugin-llm-wiki` in the root workspace but does not update `pnpm-lock.yaml`. Root frozen install will fail until maintainers choose a lockfile path that fits repo policy. - Committing `pnpm-lock.yaml` directly on this PR conflicts with the current PR policy check, while excluding the package from `pnpm-workspace.yaml` was rejected in maintainer feedback. - The package includes UI code already reviewed in #5592; generated screenshot/design artifacts were intentionally removed per maintainer request, so visual review should regenerate screenshots locally if needed. - The package depends on plugin host support from #5597, which is already merged to `master`. > For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and discuss it in `#dev` before opening the PR. Feature PRs that overlap with planned core work may need to be redirected — check the roadmap first. See `CONTRIBUTING.md`. ## Model Used - OpenAI GPT-5 Codex via Codex CLI, tool use and local code execution enabled; context window not exposed. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have run the targeted checks listed above - [x] I have added or updated tests where applicable - [ ] If this change affects the UI, I have included before/after screenshots - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] I will address all Greptile and reviewer comments before requesting merge Stack context: #5592 was merged into `pap-9173-llm-wiki-rest` after #5597 had already merged that branch to `master`, so this follow-up PR is needed to carry the plugin package itself into `master`. Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-05-11 20:45:41 -05:00
import { useEffect, useState } from "react";
import { SettingsPage, SidebarLink, WikiPage, WikiRouteSidebar } from "../../src/ui/index.js";
import type {
FileTreeNode,
FileTreeBadge,
FileTreeProps,
IssuesListProps,
} from "@paperclipai/plugin-sdk/ui";
// ---------------------------------------------------------------------------
// Bridge mocks. The real SDK runtime hooks read from a global registry; we
// inline an alternate registry here so the plugin UI can render with canned
// data inside a screenshot harness.
// ---------------------------------------------------------------------------
const NOW = "2026-05-01T20:30:00.000Z";
const FOLDER_HEALTHY = {
folderKey: "wiki-root",
configured: true,
path: "/Users/operator/work/wiki/engineering",
realPath: "/Users/operator/work/wiki/engineering",
access: "readWrite" as const,
readable: true,
writable: true,
requiredDirectories: ["raw", "wiki", "wiki/sources", "wiki/projects", "wiki/entities", "wiki/concepts", "wiki/synthesis"],
requiredFiles: ["AGENTS.md", "IDEA.md", "wiki/index.md", "wiki/log.md"],
missingDirectories: [],
missingFiles: [],
healthy: true,
problems: [],
checkedAt: NOW,
};
const MANAGED_AGENT = {
status: "active",
agentId: "agt-c14a-7b2f-4e90",
resourceKey: "paperclipai.plugin-llm-wiki:agent:wiki-maintainer",
details: { name: "Wiki Maintainer", status: "active", adapterType: "claude_local", icon: "book-open", urlKey: "wiki-maintainer" },
};
const MANAGED_PROJECT = {
status: "active",
projectId: "prj-llmw-7e1a",
resourceKey: "paperclipai.plugin-llm-wiki:project:llm-wiki",
details: { name: "LLM Wiki Operations", status: "in_progress" },
};
const MANAGED_ROUTINE = {
status: "active",
routineId: "rtn-llmw-night",
resourceKey: "paperclipai.plugin-llm-wiki:routine:nightly-wiki-lint",
routine: {
id: "rtn-llmw-night",
title: "Run LLM Wiki lint",
status: "active",
cronExpression: "0 3 * * *",
enabled: true,
assigneeAgentId: "agt-c14a-7b2f-4e90",
projectId: "prj-llmw-7e1a",
nextRunAt: "2026-05-02T03:00:00Z",
lastRunAt: "2026-05-01T03:00:00Z",
},
details: {
title: "Run LLM Wiki lint",
status: "active",
cronExpression: "0 3 * * *",
enabled: true,
assigneeAgentId: "agt-c14a-7b2f-4e90",
nextRunAt: "2026-05-02T03:00:00Z",
lastRunAt: "2026-05-01T03:00:00Z",
},
};
const MANAGED_ROUTINES = [
{
status: "active",
routineId: "rtn-llmw-cursor",
resourceKey: "paperclipai.plugin-llm-wiki:routine:cursor-window-processing",
routine: {
id: "rtn-llmw-cursor",
title: "Process LLM Wiki updates",
status: "active",
cronExpression: "0 */6 * * *",
enabled: true,
assigneeAgentId: "agt-c14a-7b2f-4e90",
projectId: "prj-llmw-7e1a",
lastRunAt: "2026-05-01T18:00:00Z",
},
details: {
title: "Process LLM Wiki updates",
status: "active",
cronExpression: "0 */6 * * *",
enabled: true,
},
},
MANAGED_ROUTINE,
{
status: "active",
routineId: "rtn-llmw-index",
resourceKey: "paperclipai.plugin-llm-wiki:routine:index-refresh",
routine: {
id: "rtn-llmw-index",
title: "Refresh LLM Wiki index",
status: "active",
cronExpression: "0 * * * *",
enabled: true,
assigneeAgentId: "agt-c14a-7b2f-4e90",
projectId: "prj-llmw-7e1a",
lastRunAt: "2026-05-01T20:00:00Z",
},
details: {
title: "Refresh LLM Wiki index",
status: "active",
cronExpression: "0 * * * *",
enabled: true,
},
},
];
const MANAGED_SKILL = {
status: "resolved",
skillId: "skl-llmw-maintainer",
resourceKey: "wiki-maintainer",
details: {
name: "LLM Wiki Maintainer",
key: "plugin/paperclipai-plugin-llm-wiki/wiki-maintainer",
description: "Use the LLM Wiki plugin tools to maintain a cited local company wiki.",
},
};
const MANAGED_SKILLS = [
MANAGED_SKILL,
{ status: "resolved", skillId: "skl-llmw-ingest", resourceKey: "wiki-ingest", details: { name: "Wiki Ingest", key: "plugin/paperclipai-plugin-llm-wiki/wiki-ingest", description: null } },
{ status: "resolved", skillId: "skl-llmw-query", resourceKey: "wiki-query", details: { name: "Wiki Query", key: "plugin/paperclipai-plugin-llm-wiki/wiki-query", description: null } },
{ status: "resolved", skillId: "skl-llmw-lint", resourceKey: "wiki-lint", details: { name: "Wiki Lint", key: "plugin/paperclipai-plugin-llm-wiki/wiki-lint", description: null } },
{ status: "resolved", skillId: "skl-llmw-distill", resourceKey: "paperclip-distill", details: { name: "Paperclip Distill", key: "plugin/paperclipai-plugin-llm-wiki/paperclip-distill", description: null } },
{ status: "resolved", skillId: "skl-llmw-index", resourceKey: "index-refresh", details: { name: "Index Refresh", key: "plugin/paperclipai-plugin-llm-wiki/index-refresh", description: null } },
];
const OVERVIEW = {
status: "ok",
checkedAt: NOW,
wikiId: "default",
folder: FOLDER_HEALTHY,
managedAgent: MANAGED_AGENT,
managedProject: MANAGED_PROJECT,
managedSkills: MANAGED_SKILLS,
operationCount: 128,
capabilities: [
"api.routes.register",
"issues.create",
"issues.read",
"agents.managed",
"projects.managed",
"routines.managed",
"agent.tools.register",
],
prompts: {
query: "Answer from the LLM Wiki.\n\nRead wiki/index.md first, inspect relevant pages and raw/source references, cite the pages used, and say when the wiki does not contain enough evidence.",
lint: "Lint the LLM Wiki for contradictions, stale claims, orphan pages, missing backlinks, weak provenance, and wiki/index.md / wiki/log.md drift.",
},
eventIngestion: {
enabled: false,
sources: { issues: false, comments: false, documents: false },
wikiId: "default",
maxCharacters: 12000,
},
};
const SETTINGS = {
folder: FOLDER_HEALTHY,
managedAgent: MANAGED_AGENT,
managedProject: MANAGED_PROJECT,
managedRoutine: MANAGED_ROUTINE,
managedRoutines: MANAGED_ROUTINES,
managedSkills: MANAGED_SKILLS,
agentOptions: [{ id: "agt-c14a-7b2f-4e90", name: "Wiki Maintainer", status: "active", icon: "book-open", urlKey: "wiki-maintainer" }],
projectOptions: [],
eventIngestion: OVERVIEW.eventIngestion,
capabilities: OVERVIEW.capabilities,
};
const PAGES = {
pages: [
{ path: "wiki/areas/control-plane.md", title: "Control plane", pageType: "areas", backlinkCount: 5, sourceCount: 2, contentHash: "a1b2c3d4", updatedAt: "2026-05-01T18:00:00Z" },
{ path: "wiki/areas/plugin-runtime.md", title: "Plugin runtime", pageType: "areas", backlinkCount: 4, sourceCount: 2, contentHash: "b1b2c3d4", updatedAt: "2026-05-01T16:00:00Z" },
{ path: "wiki/concepts/managed-resources.md", title: "Managed resources", pageType: "concepts", backlinkCount: 7, sourceCount: 2, contentHash: "c1b2c3d4", updatedAt: "2026-05-01T19:30:00Z" },
{ path: "wiki/concepts/origin-kind.md", title: "Origin kind", pageType: "concepts", backlinkCount: 3, sourceCount: 1, contentHash: "d1b2c3d4", updatedAt: "2026-05-01T15:00:00Z" },
{ path: "wiki/concepts/heartbeat.md", title: "Agent heartbeat", pageType: "concepts", backlinkCount: 2, sourceCount: 1, contentHash: "e1b2c3d4", updatedAt: "2026-04-30T10:00:00Z" },
{ path: "wiki/projects/llm-wiki/standup.md", title: "LLM Wiki plugin Standup", pageType: "project-standup", backlinkCount: 1, sourceCount: 3, contentHash: "s1b2c3d4", updatedAt: "2026-05-01T20:00:00Z" },
{ path: "wiki/projects/llm-wiki/index.md", title: "LLM Wiki plugin", pageType: "projects", backlinkCount: 6, sourceCount: 3, contentHash: "f1b2c3d4", updatedAt: "2026-05-01T17:30:00Z" },
{ path: "wiki/sources/karpathy-llm-wiki.md", title: "Karpathy LLM Wiki gist (summary)", pageType: "sources", backlinkCount: 3, sourceCount: 1, contentHash: "g1b2c3d4", updatedAt: "2026-05-01T11:30:00Z" },
{ path: "wiki/index.md", title: "LLM Wiki Index", pageType: "index", backlinkCount: 0, sourceCount: 0, contentHash: "h1b2c3d4", updatedAt: "2026-05-01T19:30:00Z" },
{ path: "wiki/log.md", title: "LLM Wiki Log", pageType: "log", backlinkCount: 0, sourceCount: 0, contentHash: "i1b2c3d4", updatedAt: "2026-05-01T19:30:00Z" },
],
sources: [
{ rawPath: "raw/karpathy-llm-wiki.md", title: "Karpathy LLM Wiki gist", sourceType: "url", url: "https://gist.github.com/karpathy/.../llm-wiki", status: "captured", createdAt: "2026-05-01T19:00:00Z" },
{ rawPath: "raw/paperclip-v1-spec.md", title: "Paperclip V1 spec", sourceType: "file", url: null, status: "captured", createdAt: "2026-05-01T18:30:00Z" },
{ rawPath: "raw/design-doc-2026-04.md", title: "Design doc — April 2026", sourceType: "file", url: null, status: "captured", createdAt: "2026-04-28T14:00:00Z" },
],
};
const PAGE_CONTENT = {
wikiId: "default",
path: "wiki/concepts/managed-resources.md",
contents: `# Managed Resources
A **plugin-managed resource** is a normal Paperclip resource (agent, routine, project) that a plugin has seeded with suggested defaults. The plugin marks ownership via a stable \`resourceKey\` so that installs, imports, and upgrades can find the resource even when UUIDs differ.
The host treats managed resources as fully editable. The operator can rename, change adapter config, edit instructions, change schedule, etc. The plugin can offer a *reset to plugin defaults* action that re-applies the suggested values.
## Why it matters
- Stable references survive dev resets and re-imports.
- Plugin upgrades don't clobber operator edits.
- Operations remain inspectable through native Agents / Routines / Projects pages.
`,
title: "Managed Resources",
pageType: "concepts",
backlinks: ["wiki/areas/plugin-runtime.md", "wiki/concepts/origin-kind.md", "wiki/projects/llm-wiki/index.md"],
sourceRefs: ["raw/karpathy-llm-wiki.md", "raw/paperclip-v1-spec.md"],
updatedAt: "2026-05-01T19:30:00Z",
hash: "deadbeefcafe1234",
};
const OPERATIONS = {
operations: [
{ id: "2944aaaaaaaa", operationType: "ingest", status: "running", hiddenIssueId: "ii-2944", hiddenIssueIdentifier: "PAP-OP-2944", hiddenIssueTitle: "Ingest URL: karpathy/llm-wiki gist", hiddenIssueStatus: "in_progress", projectId: "prj-llmw-7e1a", costCents: 1, warnings: [], affectedPages: [], createdAt: "2026-05-01T20:29:48Z", updatedAt: "2026-05-01T20:29:48Z" },
{ id: "2943bbbbbbbb", operationType: "query", status: "done", hiddenIssueId: "ii-2943", hiddenIssueIdentifier: "PAP-OP-2943", hiddenIssueTitle: "How do plugin-managed routines resolve their default agent…", hiddenIssueStatus: "done", projectId: "prj-llmw-7e1a", costCents: 1, warnings: [], affectedPages: [], createdAt: "2026-05-01T20:25:00Z", updatedAt: "2026-05-01T20:26:30Z" },
{ id: "2942cccccccc", operationType: "file-as-page", status: "done", hiddenIssueId: "ii-2942", hiddenIssueIdentifier: "PAP-OP-2942", hiddenIssueTitle: "File answer as concepts/routine-agent-resolution.md", hiddenIssueStatus: "done", projectId: "prj-llmw-7e1a", costCents: 0, warnings: [], affectedPages: [{ path: "wiki/concepts/routine-agent-resolution.md" }], createdAt: "2026-05-01T20:25:30Z", updatedAt: "2026-05-01T20:25:35Z" },
{ id: "2941dddddddd", operationType: "query", status: "done", hiddenIssueId: "ii-2941", hiddenIssueIdentifier: "PAP-OP-2941", hiddenIssueTitle: "Compare managed agent reset vs detach…", hiddenIssueStatus: "done", projectId: "prj-llmw-7e1a", costCents: 1, warnings: [], affectedPages: [], createdAt: "2026-05-01T20:18:00Z", updatedAt: "2026-05-01T20:18:30Z" },
{ id: "2940eeeeeeee", operationType: "lint", status: "done", hiddenIssueId: "ii-2940", hiddenIssueIdentifier: "PAP-OP-2940", hiddenIssueTitle: "Nightly wiki lint · 17 findings (2 critical)", hiddenIssueStatus: "done", projectId: "prj-llmw-7e1a", costCents: 4, warnings: [
{ severity: "critical", message: "Contradiction: managed-resources.md vs raw/karpathy-llm-wiki.md (run-time vs install-time resolution)", path: "wiki/concepts/managed-resources.md" },
{ severity: "critical", message: "Conflicting claim about plugin manifest version", path: "wiki/concepts/origin-kind.md" },
{ severity: "orphan", message: "people/aaron-norvig.md has no inbound backlinks", path: "wiki/people/aaron-norvig.md" },
{ severity: "orphan", message: "concepts/sandboxing.md is referenced from raw/ only", path: "wiki/concepts/sandboxing.md" },
{ severity: "stale", message: "areas/control-plane.md last source observed 41d ago", path: "wiki/areas/control-plane.md" },
{ severity: "backlink", message: "Missing reverse links: managed-resources.md -> wiki/projects/llm-wiki/index.md" },
{ severity: "index", message: "index.md does not list 5 pages created in last 24h" },
], affectedPages: [], createdAt: "2026-05-01T18:00:00Z", updatedAt: "2026-05-01T18:02:30Z" },
{ id: "2939ffffffff", operationType: "ingest", status: "done", hiddenIssueId: "ii-2939", hiddenIssueIdentifier: "PAP-OP-2939", hiddenIssueTitle: "paperclip-v1-spec.md · 3 pages created", hiddenIssueStatus: "done", projectId: "prj-llmw-7e1a", costCents: 2, warnings: [], affectedPages: [{ path: "wiki/areas/control-plane.md" }, { path: "wiki/concepts/managed-resources.md" }, { path: "wiki/concepts/origin-kind.md" }], createdAt: "2026-05-01T18:30:00Z", updatedAt: "2026-05-01T18:32:00Z" },
{ id: "2934gggggggg", operationType: "ingest", status: "failed", hiddenIssueId: "ii-2934", hiddenIssueIdentifier: "PAP-OP-2934", hiddenIssueTitle: "Internal wiki dump (PDF) · outbound URL not on allowlist", hiddenIssueStatus: "blocked", projectId: "prj-llmw-7e1a", costCents: 0, warnings: [{ message: "outbound URL not on allowlist" }], affectedPages: [], createdAt: "2026-05-01T16:00:00Z", updatedAt: "2026-05-01T16:00:30Z" },
],
};
const TEMPLATE_IDEA = {
path: "IDEA.md",
exists: true,
hash: "abc123",
contents: `# LLM Wiki
A pattern for building personal knowledge bases using LLMs.
`,
};
const TEMPLATE_AGENTS = {
path: "AGENTS.md",
exists: true,
hash: "def456",
contents: `# LLM Wiki Maintainer
You maintain this wiki through Paperclip plugin tools.
Before answering or editing:
1. Read AGENTS.md.
2. Read wiki/index.md.
3. Cite raw sources or source pages for factual claims.
4. Use proposed patches for substantial rewrites.
5. Append a short note to wiki/log.md for maintenance changes.
`,
};
const HOST_CONTEXT = {
companyId: "company-demo",
companyPrefix: "PAP",
projectId: null,
entityId: null,
entityType: null,
userId: "user-demo",
parentEntityId: null,
};
function fakeData<T>(value: T) {
return { data: value, loading: false, error: null, refresh: () => undefined };
}
const DISTILLATION_CURSOR = {
id: "cur-control-plane-1",
sourceScope: "project",
scopeKey: "prj-control-plane",
projectId: "prj-control-plane",
projectName: "Control plane",
projectColor: "#2563eb",
rootIssueId: null,
rootIssueIdentifier: null,
rootIssueTitle: null,
lastProcessedAt: "2026-05-04T15:00:00Z",
lastObservedAt: "2026-05-04T18:55:00Z",
pendingEventCount: 4,
lastSourceHash: "7f3a92041b210def",
lastSuccessfulRunId: "run-1",
};
const DISTILLATION_RUNS = [
{
id: "run-1",
cursorId: "cur-control-plane-1",
workItemId: null,
projectId: "prj-control-plane",
projectName: "Control plane",
rootIssueId: null,
rootIssueIdentifier: null,
sourceWindowStart: "2026-04-30T14:22:00Z",
sourceWindowEnd: "2026-05-04T15:01:00Z",
sourceHash: "7f3a92041b210def",
status: "succeeded",
costCents: 12,
retryCount: 0,
warnings: [],
metadata: {},
operationIssueId: "ii-op-3201",
operationIssueIdentifier: "PAP-OP-3201",
operationIssueTitle: "LLM Wiki distillation · Control plane",
affectedPagePaths: ["wiki/projects/control-plane/index.md"],
createdAt: "2026-05-04T15:01:00Z",
updatedAt: "2026-05-04T15:02:14Z",
},
{
id: "run-2",
cursorId: "cur-billing-1",
workItemId: null,
projectId: "prj-billing",
projectName: "Billing",
rootIssueId: null,
rootIssueIdentifier: null,
sourceWindowStart: "2026-05-03T08:00:00Z",
sourceWindowEnd: "2026-05-04T18:55:00Z",
sourceHash: "92d44102be7c08aa",
status: "review_required",
costCents: 8,
retryCount: 0,
warnings: ["Touches the Decisions section. Auto-apply policy is 'Status sections only'."],
metadata: {},
operationIssueId: "ii-op-3204",
operationIssueIdentifier: "PAP-OP-3204",
operationIssueTitle: "LLM Wiki distillation · Billing",
affectedPagePaths: ["wiki/projects/billing/index.md", "wiki/projects/billing/decisions.md"],
createdAt: "2026-05-04T18:55:00Z",
updatedAt: "2026-05-04T18:56:14Z",
},
{
id: "run-3",
cursorId: "cur-routines-1",
workItemId: null,
projectId: "prj-routines",
projectName: "Routine engine",
rootIssueId: null,
rootIssueIdentifier: null,
sourceWindowStart: "2026-05-04T11:00:00Z",
sourceWindowEnd: "2026-05-04T18:00:00Z",
sourceHash: "ab12dde98c3f0001",
status: "running",
costCents: 4,
retryCount: 0,
warnings: [],
metadata: {},
operationIssueId: "ii-op-3205",
operationIssueIdentifier: "PAP-OP-3205",
operationIssueTitle: "LLM Wiki distillation · Routine engine",
affectedPagePaths: [],
createdAt: "2026-05-04T18:58:00Z",
updatedAt: "2026-05-04T18:58:00Z",
},
{
id: "run-4",
cursorId: "cur-export-1",
workItemId: null,
projectId: "prj-exports",
projectName: "Exports",
rootIssueId: null,
rootIssueIdentifier: null,
sourceWindowStart: "2026-05-03T20:00:00Z",
sourceWindowEnd: "2026-05-04T03:00:00Z",
sourceHash: "ee01acd482211110",
status: "failed",
costCents: 0,
retryCount: 1,
warnings: ["model error: 429", "Source bundle clipped: 6 issues over 48k chars."],
metadata: {},
operationIssueId: "ii-op-3198",
operationIssueIdentifier: "PAP-OP-3198",
operationIssueTitle: "LLM Wiki distillation · Exports",
affectedPagePaths: [],
createdAt: "2026-05-04T03:01:00Z",
updatedAt: "2026-05-04T03:01:30Z",
},
];
const DISTILLATION_PAGE_BINDINGS = [
{
id: "bind-control-plane",
pagePath: "wiki/projects/control-plane/index.md",
projectId: "prj-control-plane",
projectName: "Control plane",
rootIssueId: null,
lastAppliedSourceHash: "7f3a92041b210def",
lastDistillationRunId: "run-1",
lastRunStatus: "succeeded",
lastRunCompletedAt: "2026-05-04T15:02:14Z",
lastRunSourceWindowEnd: "2026-05-04T15:01:00Z",
lastRunSourceHash: "7f3a92041b210def",
metadata: { sourceRefs: [{ id: "PAP-3416", kind: "issue", title: "Distillation kickoff" }, { id: "PAP-3416#c1", kind: "comment", title: "Comment" }] },
updatedAt: "2026-05-04T15:02:14Z",
},
{
id: "bind-billing",
pagePath: "wiki/projects/billing/index.md",
projectId: "prj-billing",
projectName: "Billing",
rootIssueId: null,
lastAppliedSourceHash: "11abf991a081ddff",
lastDistillationRunId: "run-2",
lastRunStatus: "review_required",
lastRunCompletedAt: "2026-05-04T18:56:14Z",
lastRunSourceWindowEnd: "2026-05-04T18:55:00Z",
lastRunSourceHash: "92d44102be7c08aa",
metadata: {},
updatedAt: "2026-05-04T18:56:14Z",
},
];
const DISTILLATION_OVERVIEW = {
cursors: [
DISTILLATION_CURSOR,
{
...DISTILLATION_CURSOR,
id: "cur-billing-1",
scopeKey: "prj-billing",
projectId: "prj-billing",
projectName: "Billing",
lastProcessedAt: "2026-05-04T18:55:00Z",
lastObservedAt: "2026-05-04T18:55:00Z",
pendingEventCount: 0,
lastSuccessfulRunId: "run-2",
},
{
...DISTILLATION_CURSOR,
id: "cur-routines-1",
scopeKey: "prj-routines",
projectId: "prj-routines",
projectName: "Routine engine",
lastProcessedAt: null,
lastObservedAt: "2026-05-04T18:00:00Z",
pendingEventCount: 11,
lastSuccessfulRunId: null,
},
],
runs: DISTILLATION_RUNS,
workItems: [
{ id: "wi-1", workItemKind: "review", status: "review_required", priority: "high", projectId: "prj-billing", rootIssueId: null, metadata: {}, createdAt: "2026-05-04T18:56:00Z", updatedAt: "2026-05-04T18:56:00Z" },
],
pageBindings: DISTILLATION_PAGE_BINDINGS,
reviewWorkItems: [
{ id: "wi-1", workItemKind: "review", status: "review_required", priority: "high", projectId: "prj-billing", rootIssueId: null, metadata: {}, createdAt: "2026-05-04T18:56:00Z", updatedAt: "2026-05-04T18:56:00Z" },
],
counts: { cursors: 3, runningRuns: 1, failedRuns24h: 1, reviewRequired: 1 },
};
const DISTILLATION_OVERVIEW_UNCONFIGURED = {
cursors: [],
runs: [],
workItems: [],
pageBindings: [],
reviewWorkItems: [],
counts: { cursors: 0, runningRuns: 0, failedRuns24h: 0, reviewRequired: 0 },
};
const DISTILLATION_PROVENANCE = {
binding: DISTILLATION_PAGE_BINDINGS[0],
runs: [DISTILLATION_RUNS[0]],
snapshot: {
id: "snap-1",
distillationRunId: "run-1",
sourceHash: "7f3a92041b210def",
maxCharacters: 48000,
clipped: false,
sourceRefs: [
{ id: "PAP-3416", kind: "issue", title: "How should we distill?" },
{ id: "PAP-3416#c1", kind: "comment", title: "Approach: cursors" },
{ id: "PAP-3416#doc:plan", kind: "document", title: "Plan v3" },
],
metadata: { issueCount: 12, commentCount: 8, documentCount: 4 },
createdAt: "2026-05-04T15:01:00Z",
},
cursor: DISTILLATION_CURSOR,
};
const SPACES = {
spaces: [
{
id: "space-default",
companyId: HOST_CONTEXT.companyId,
wikiId: "default",
slug: "default",
displayName: "default",
spaceType: "managed",
folderMode: "managed_subfolder",
rootFolderKey: "wiki-root",
pathPrefix: null,
configuredRootPath: null,
accessScope: "shared",
ownerUserId: null,
ownerAgentId: null,
teamKey: null,
settings: {},
status: "active",
createdAt: "2026-04-12T08:00:00Z",
updatedAt: "2026-05-01T12:00:00Z",
},
{
id: "space-team-research",
companyId: HOST_CONTEXT.companyId,
wikiId: "default",
slug: "team-research",
displayName: "team-research",
spaceType: "managed",
folderMode: "managed_subfolder",
rootFolderKey: "wiki-root",
pathPrefix: "spaces/team-research",
configuredRootPath: null,
accessScope: "shared",
ownerUserId: null,
ownerAgentId: null,
teamKey: null,
settings: {},
status: "active",
createdAt: "2026-04-22T08:00:00Z",
updatedAt: "2026-05-01T11:00:00Z",
},
{
id: "space-customer-feedback",
companyId: HOST_CONTEXT.companyId,
wikiId: "default",
slug: "customer-feedback",
displayName: "customer-feedback",
spaceType: "managed",
folderMode: "managed_subfolder",
rootFolderKey: "wiki-root",
pathPrefix: "spaces/customer-feedback",
configuredRootPath: null,
accessScope: "shared",
ownerUserId: null,
ownerAgentId: null,
teamKey: null,
settings: {},
status: "active",
createdAt: "2026-04-25T08:00:00Z",
updatedAt: "2026-05-01T10:00:00Z",
},
],
};
function dataResolver(key: string, params?: Record<string, unknown>) {
if (key === "overview") return fakeData(OVERVIEW);
if (key === "settings") return fakeData(SETTINGS);
if (key === "spaces") return fakeData(SPACES);
if (key === "space") {
const slug = typeof params?.spaceSlug === "string" ? params.spaceSlug : "default";
const space = SPACES.spaces.find((s) => s.slug === slug) ?? SPACES.spaces[0];
return fakeData({
...space,
relativeRoot: space.pathPrefix ?? "",
folder: FOLDER_HEALTHY,
});
}
if (key === "pages") return fakeData(PAGES);
if (key === "page-content") return fakeData(PAGE_CONTENT);
if (key === "operations") {
const opType = typeof params?.operationType === "string" ? params.operationType : null;
const filtered = opType && opType !== "all"
? OPERATIONS.operations.filter((op) => op.operationType === opType)
: OPERATIONS.operations;
return fakeData({ operations: filtered });
}
if (key === "distillation-overview") {
const flag = typeof window !== "undefined" ? window.location.search.includes("unconfigured=1") : false;
return fakeData(flag ? DISTILLATION_OVERVIEW_UNCONFIGURED : DISTILLATION_OVERVIEW);
}
if (key === "distillation-page-provenance") {
return fakeData(DISTILLATION_PROVENANCE);
}
if (key === "template" && params?.path === "IDEA.md") return fakeData(TEMPLATE_IDEA);
if (key === "template" && params?.path === "AGENTS.md") return fakeData(TEMPLATE_AGENTS);
return { data: null, loading: false, error: null, refresh: () => undefined };
}
function MockFileTree(props: FileTreeProps) {
const expanded = new Set(props.expandedPaths ?? []);
const badges = props.fileBadges ?? {};
const renderNodes = (nodes: FileTreeNode[], depth: number) => (
<ul style={{ listStyle: "none", margin: 0, padding: 0 }}>
{nodes.map((node) => {
const isExpanded = node.kind === "dir" && expanded.has(node.path);
const isSelected = node.kind === "file" && node.path === props.selectedFile;
const badge = badges[node.path] as FileTreeBadge | undefined;
return (
<li key={node.path}>
<button
type="button"
onClick={() => {
if (node.kind === "dir") props.onToggleDir?.(node.path);
else props.onSelectFile?.(node.path);
}}
style={{
display: "flex",
alignItems: "center",
gap: 6,
width: "100%",
border: "none",
background: isSelected ? "var(--accent, oklch(0.269 0 0))" : "transparent",
color: "var(--foreground, oklch(0.985 0 0))",
fontSize: 12,
textAlign: "left",
padding: `4px 8px 4px ${depth * 16 + 12}px`,
cursor: "pointer",
borderRadius: 4,
fontFamily: "ui-sans-serif, system-ui, -apple-system, sans-serif",
}}
aria-expanded={node.kind === "dir" ? isExpanded : undefined}
aria-selected={isSelected || undefined}
>
<span style={{ width: 12, color: "var(--muted-foreground, oklch(0.708 0 0))" }} aria-hidden>
{node.kind === "dir" ? (isExpanded ? "▾" : "▸") : ""}
</span>
<span style={{ flex: 1, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
{node.kind === "dir" ? `📁 ${node.name}` : node.name}
</span>
{badge ? (
<span
style={{
fontSize: 10,
padding: "1px 6px",
borderRadius: 999,
background: "color-mix(in oklab, var(--accent, oklch(0.4 0.04 250)) 50%, transparent)",
color: "var(--foreground, oklch(0.985 0 0))",
textTransform: "uppercase",
letterSpacing: "0.04em",
}}
title={badge.tooltip}
>
{badge.label}
</span>
) : null}
</button>
{node.kind === "dir" && isExpanded ? renderNodes(node.children, depth + 1) : null}
</li>
);
})}
</ul>
);
if (props.loading) {
return <div style={{ padding: 12, fontSize: 12, color: "var(--muted-foreground, oklch(0.708 0 0))" }}>Loading</div>;
}
if (props.error) {
return <div style={{ padding: 12, fontSize: 12, color: "var(--destructive, oklch(0.7 0.2 25))" }}>{props.error.message}</div>;
}
if (props.nodes.length === 0) {
return (
<div style={{ padding: 16, fontSize: 12, color: "var(--muted-foreground, oklch(0.708 0 0))" }}>
<div style={{ fontWeight: 600, color: "var(--foreground, oklch(0.985 0 0))" }}>{props.empty?.title ?? "No files"}</div>
<div>{props.empty?.description ?? "Nothing to show."}</div>
</div>
);
}
return <div role="tree" aria-label={props.ariaLabel ?? "Files"}>{renderNodes(props.nodes, 0)}</div>;
}
function MockIssuesList(props: IssuesListProps) {
const isNarrow = typeof window !== "undefined" && window.innerWidth < 700;
const rows = OPERATIONS.operations.map((op) => ({
id: op.hiddenIssueId ?? op.id,
identifier: op.hiddenIssueIdentifier ?? `op-${op.id.slice(0, 6)}`,
title: op.hiddenIssueTitle ?? `LLM Wiki ${op.operationType}`,
status: op.hiddenIssueStatus ?? op.status,
priority: op.operationType === "query" ? "medium" : "low",
updatedAt: op.updatedAt,
operationType: op.operationType,
}));
return (
<div style={{ display: "grid", gap: 10, padding: 18, fontFamily: "ui-sans-serif, system-ui, -apple-system, sans-serif" }}>
<div style={{ display: "flex", alignItems: "center", gap: 10, flexWrap: "wrap" }}>
<strong style={{ fontSize: 14 }}>Issues</strong>
<span style={{ fontSize: 11, color: "var(--muted-foreground, oklch(0.708 0 0))" }}>
{rows.length} loaded · {props.filters?.originKindPrefix ?? props.projectId ?? "all"}
</span>
</div>
<div style={{ border: "1px solid var(--border, oklch(0.269 0 0))", borderRadius: 8, overflow: "hidden", background: "var(--card, oklch(0.205 0 0))" }}>
{rows.map((issue) => (
<div
key={issue.id}
style={{
display: "grid",
gridTemplateColumns: isNarrow ? "minmax(0, 1fr) auto" : "110px minmax(0, 1fr) 110px 90px",
gap: isNarrow ? "6px 10px" : 12,
alignItems: "center",
padding: "10px 12px",
borderBottom: "1px solid var(--border, oklch(0.269 0 0))",
fontSize: 13,
}}
>
<span style={{ fontFamily: "ui-monospace, SFMono-Regular, monospace", fontSize: 12, color: "var(--muted-foreground, oklch(0.708 0 0))" }}>{issue.identifier}</span>
{isNarrow ? null : <span style={{ minWidth: 0, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{issue.title}</span>}
{isNarrow ? null : <span style={{ fontSize: 11, color: "var(--muted-foreground, oklch(0.708 0 0))" }}>{issue.operationType}</span>}
<span style={{ justifySelf: "end", maxWidth: "100%", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", fontSize: 11, border: "1px solid var(--border, oklch(0.269 0 0))", borderRadius: 999, padding: "2px 8px" }}>{issue.status}</span>
{isNarrow ? (
<span style={{ gridColumn: "1 / -1", minWidth: 0, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{issue.title}</span>
) : null}
{isNarrow ? (
<span style={{ gridColumn: "1 / -1", fontSize: 11, color: "var(--muted-foreground, oklch(0.708 0 0))" }}>{issue.operationType}</span>
) : null}
</div>
))}
</div>
</div>
);
}
function MockMarkdownBlock({ content, className }: { content: string; className?: string }) {
return (
<div
className={className}
style={{ whiteSpace: "pre-wrap", fontSize: 13, lineHeight: 1.65 }}
>
{content}
</div>
);
}
function MockMarkdownEditor({
value,
onChange,
placeholder,
className,
}: {
value: string;
onChange: (value: string) => void;
placeholder?: string;
className?: string;
}) {
return (
<textarea
className={className}
value={value}
placeholder={placeholder}
onChange={(event) => onChange(event.currentTarget.value)}
style={{
width: "100%",
minHeight: 260,
boxSizing: "border-box",
border: "1px solid var(--border, oklch(0.269 0 0))",
borderRadius: 8,
background: "var(--card, oklch(0.205 0 0))",
color: "var(--foreground, oklch(0.985 0 0))",
padding: 12,
font: "13px ui-sans-serif, system-ui, -apple-system, sans-serif",
}}
/>
);
}
function MockAssigneePicker({
value,
placeholder,
onChange,
}: {
value: string;
placeholder?: string;
onChange: (value: string, selection: { assigneeAgentId: string | null; assigneeUserId: string | null }) => void;
}) {
return (
<button
type="button"
onClick={() => onChange("agent:agt-c14a-7b2f-4e90", { assigneeAgentId: "agt-c14a-7b2f-4e90", assigneeUserId: null })}
style={{
width: "100%",
minHeight: 44,
border: "1px solid var(--border, oklch(0.269 0 0))",
borderRadius: 8,
background: "var(--card, oklch(0.205 0 0))",
color: "var(--foreground, oklch(0.985 0 0))",
padding: "8px 12px",
textAlign: "left",
font: "13px ui-sans-serif, system-ui, -apple-system, sans-serif",
}}
>
{value ? "Wiki Maintainer" : (placeholder ?? "Select maintainer")}
</button>
);
}
type MockManagedRoutinesListProps = {
routines: Array<{
key: string;
title: string;
status: string;
cronExpression?: string | null;
lastRunAt?: string | Date | null;
lastRunStatus?: string | null;
href?: string | null;
managedByPluginDisplayName?: string | null;
missingRefs?: Array<{ resourceKind: string; resourceKey: string }>;
defaultDrift?: { changedFields: string[] } | null;
}>;
agents?: Array<{ id: string; name: string }>;
projects?: Array<{ id: string; name: string }>;
pluginDisplayName?: string | null;
runningRoutineKey?: string | null;
statusMutationRoutineKey?: string | null;
resettingRoutineKey?: string | null;
onRunNow?: (routine: { key: string }) => void;
onToggleEnabled?: (routine: { key: string }, enabled: boolean) => void;
onReset?: (routine: { key: string }) => void;
};
function MockManagedRoutinesList(props: MockManagedRoutinesListProps) {
if (props.routines.length === 0) {
return (
<div style={{ padding: 16, fontSize: 12, color: "var(--muted-foreground, oklch(0.708 0 0))" }}>
No managed routines.
</div>
);
}
return (
<div
style={{
display: "grid",
gap: 0,
border: "1px solid var(--border, oklch(0.269 0 0))",
borderRadius: 8,
overflow: "hidden",
background: "var(--card, oklch(0.205 0 0))",
fontFamily: "ui-sans-serif, system-ui, -apple-system, sans-serif",
}}
>
{props.routines.map((routine, index) => (
<div
key={routine.key}
style={{
display: "grid",
gridTemplateColumns: "minmax(0, 1fr) 110px 110px",
gap: 12,
alignItems: "center",
padding: "10px 14px",
borderTop: index === 0 ? "none" : "1px solid var(--border, oklch(0.269 0 0))",
fontSize: 13,
}}
>
<div style={{ display: "grid", gap: 2, minWidth: 0 }}>
<span style={{ fontWeight: 600, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
{routine.title}
</span>
<span
style={{
fontSize: 11,
color: "var(--muted-foreground, oklch(0.708 0 0))",
fontFamily: "ui-monospace, SFMono-Regular, monospace",
}}
>
{routine.cronExpression ?? "—"}
</span>
</div>
<span
style={{
justifySelf: "start",
fontSize: 11,
border: "1px solid var(--border, oklch(0.269 0 0))",
borderRadius: 999,
padding: "2px 8px",
}}
>
{routine.status}
</span>
<div style={{ justifySelf: "end", display: "flex", gap: 6 }}>
<button
type="button"
style={{
fontSize: 11,
padding: "4px 8px",
borderRadius: 6,
border: "1px solid var(--border, oklch(0.269 0 0))",
background: "transparent",
color: "var(--foreground, oklch(0.985 0 0))",
cursor: "pointer",
}}
onClick={() => props.onRunNow?.({ key: routine.key })}
>
Run now
</button>
<button
type="button"
style={{
fontSize: 11,
padding: "4px 8px",
borderRadius: 6,
border: "1px solid var(--border, oklch(0.269 0 0))",
background: "transparent",
color: "var(--foreground, oklch(0.985 0 0))",
cursor: "pointer",
}}
onClick={() => props.onReset?.({ key: routine.key })}
>
Reset
</button>
</div>
</div>
))}
</div>
);
}
function MockProjectPicker({
value,
placeholder,
onChange,
}: {
value: string;
placeholder?: string;
onChange: (projectId: string) => void;
}) {
return (
<button
type="button"
onClick={() => onChange("prj-llmw-7e1a")}
style={{
width: "100%",
minHeight: 44,
border: "1px solid var(--border, oklch(0.269 0 0))",
borderRadius: 8,
background: "var(--card, oklch(0.205 0 0))",
color: "var(--foreground, oklch(0.985 0 0))",
padding: "8px 12px",
textAlign: "left",
font: "13px ui-sans-serif, system-ui, -apple-system, sans-serif",
}}
>
{value ? "LLM Wiki Operations" : (placeholder ?? "Project")}
</button>
);
}
// Captured at module load so we can flip it before render and trigger
// re-renders via popstate when the hash/search changes.
function readHostLocation() {
if (typeof window === "undefined") {
return { pathname: "/PAP/wiki", search: "", hash: "" };
}
return { pathname: window.location.pathname, search: window.location.search, hash: window.location.hash };
}
const hostLocationListeners = new Set<() => void>();
function useHostLocationMock() {
const [snapshot, setSnapshot] = useState(readHostLocation);
useEffect(() => {
const update = () => setSnapshot(readHostLocation());
window.addEventListener("popstate", update);
window.addEventListener("hashchange", update);
hostLocationListeners.add(update);
return () => {
window.removeEventListener("popstate", update);
window.removeEventListener("hashchange", update);
hostLocationListeners.delete(update);
};
}, []);
return snapshot;
}
const sdkUi: Record<string, unknown> = {
usePluginData: dataResolver,
usePluginAction: () => async () => ({ status: "ok" }),
usePluginStream: () => ({ events: [], lastEvent: null, connecting: false, connected: false, error: null, close: () => undefined }),
usePluginToast: () => () => null,
useHostContext: () => HOST_CONTEXT,
useHostNavigation: () => ({
resolveHref: (to: string) => `/PAP${to.startsWith("/") ? to : `/${to}`}`,
navigate: (to: string) => {
const href = `/PAP${to.startsWith("/") ? to : `/${to}`}`;
window.history.pushState({}, "", href);
hostLocationListeners.forEach((fn) => fn());
},
linkProps: (to: string) => ({
href: `/PAP${to.startsWith("/") ? to : `/${to}`}`,
onClick: (event: { preventDefault: () => void }) => {
event.preventDefault();
const href = `/PAP${to.startsWith("/") ? to : `/${to}`}`;
window.history.pushState({}, "", href);
hostLocationListeners.forEach((fn) => fn());
},
}),
}),
useHostLocation: useHostLocationMock,
MarkdownBlock: MockMarkdownBlock,
MarkdownEditor: MockMarkdownEditor,
FileTree: MockFileTree,
IssuesList: MockIssuesList,
AssigneePicker: MockAssigneePicker,
ProjectPicker: MockProjectPicker,
ManagedRoutinesList: MockManagedRoutinesList,
};
(globalThis as { __paperclipPluginBridge__?: { sdkUi?: Record<string, unknown> } }).__paperclipPluginBridge__ = { sdkUi };
// ---------------------------------------------------------------------------
// Harness app: chooses which view to render based on a global hash. Playwright
// flips the hash to switch views and path segments to drive Wiki sections.
// ---------------------------------------------------------------------------
type ViewKey = "wiki" | "wiki-sidebar" | "settings" | "sidebar";
function useView() {
const [view, setView] = useState<ViewKey>(() => readView());
useEffect(() => {
const handler = () => setView(readView());
window.addEventListener("hashchange", handler);
return () => window.removeEventListener("hashchange", handler);
}, []);
return view;
}
function readView(): ViewKey {
if (typeof window === "undefined") return "wiki";
const hash = window.location.hash.replace(/^#/, "");
if (hash === "settings" || hash === "sidebar" || hash === "wiki-sidebar") return hash;
return "wiki";
}
export function App() {
const view = useView();
return (
<div style={{ height: "100vh", width: "100vw", overflow: "hidden", background: "var(--background, oklch(0.145 0 0))", color: "var(--foreground, oklch(0.985 0 0))" }}>
{view === "wiki-sidebar" ? (
<div style={{ display: "flex", height: "100vh" }}>
<WikiRouteSidebar context={HOST_CONTEXT as never} />
<div style={{ flex: 1 }}>
<WikiPage context={HOST_CONTEXT as never} />
</div>
</div>
) : view === "sidebar" ? (
<aside style={{ width: 240, background: "var(--sidebar, oklch(0.145 0 0))", padding: 12, height: "100vh", borderRight: "1px solid var(--border, oklch(0.269 0 0))" }}>
<SidebarLink context={HOST_CONTEXT as never} />
</aside>
) : view === "settings" ? (
<SettingsPage context={HOST_CONTEXT as never} />
) : (
<WikiPage context={HOST_CONTEXT as never} />
)}
</div>
);
}