import { AssigneePicker, FileTree, IssuesList as PluginIssuesList, ManagedRoutinesList as PluginManagedRoutinesList, MarkdownBlock, MarkdownEditor, ProjectPicker, usePluginAction, usePluginData, usePluginStream, usePluginToast, useHostLocation, useHostNavigation, type FileTreeNode, type ManagedRoutinesListItem, type PluginPageProps, type PluginRouteSidebarProps, type PluginSettingsPageProps, type PluginSidebarProps, } from "@paperclipai/plugin-sdk/ui"; import { useCallback, useEffect, useMemo, useRef, useState, type AnchorHTMLAttributes, type CSSProperties, type ReactElement, type ReactNode } from "react"; import { readIngestOperationIssueId, uploadIssueAttachmentFile } from "./issue-attachments.js"; // --------------------------------------------------------------------------- // Shared design tokens — copied from the UX wireframe shared.css so the plugin // looks identical inside the host whether or not host theme tokens are // available at runtime. // --------------------------------------------------------------------------- const tokens = { border: "var(--border, oklch(0.269 0 0))", card: "var(--card, oklch(0.205 0 0))", bg: "var(--background, oklch(0.145 0 0))", fg: "var(--foreground, oklch(0.985 0 0))", muted: "var(--muted-foreground, oklch(0.708 0 0))", accent: "var(--accent, oklch(0.269 0 0))", primary: "var(--primary, oklch(0.985 0 0))", primaryFg: "var(--primary-foreground, oklch(0.205 0 0))", destructive: "var(--destructive, oklch(0.637 0.237 25.331))", pluginBg: "oklch(0.3 0.06 70)", pluginFg: "oklch(0.92 0.08 80)", pluginBorder: "oklch(0.55 0.15 70)", hiddenOpBg: "oklch(0.27 0.04 280)", hiddenOpFg: "oklch(0.85 0.08 280)", hiddenOpBorder: "oklch(0.45 0.1 280)", callout: { bg: "oklch(0.2 0.04 250)", fg: "oklch(0.85 0.08 250)", border: "oklch(0.4 0.1 250)" }, statusDone: "oklch(0.65 0.16 145)", statusRunning: "oklch(0.7 0.13 200)", statusBlocked: "oklch(0.6 0.21 25)", statusInProgress: "oklch(0.58 0.18 280)", statusTodo: "oklch(0.6 0.17 250)", statusPaused: "oklch(0.72 0.15 70)", }; type Tone = "todo" | "in_progress" | "in_review" | "done" | "blocked" | "running" | "paused" | "failed" | "queued" | "default"; const toneStyles: Record = { default: { background: "var(--secondary, oklch(0.269 0 0))", color: tokens.fg, border: `1px solid ${tokens.border}` }, todo: { background: "oklch(0.27 0.06 250)", color: "oklch(0.85 0.1 250)" }, in_progress: { background: "oklch(0.27 0.06 280)", color: "oklch(0.85 0.1 280)" }, in_review: { background: "oklch(0.27 0.07 305)", color: "oklch(0.85 0.1 305)" }, done: { background: "oklch(0.27 0.06 145)", color: "oklch(0.85 0.1 145)" }, blocked: { background: "oklch(0.27 0.08 25)", color: "oklch(0.82 0.13 25)" }, running: { background: "oklch(0.27 0.06 200)", color: "oklch(0.83 0.11 200)" }, paused: { background: "oklch(0.27 0.07 70)", color: "oklch(0.85 0.1 70)" }, failed: { background: "oklch(0.27 0.08 25)", color: "oklch(0.82 0.13 25)" }, queued: { background: "oklch(0.27 0.06 250)", color: "oklch(0.85 0.1 250)" }, }; const fontStack = `ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif`; const mobileMediaQuery = "(max-width: 767px)"; const PLUGIN_ID = "paperclipai.plugin-llm-wiki"; const WIKI_SIDEBAR_NAV_STATE_KEY = "paperclipWikiSidebarTreePath"; const ROUTE_SIDEBAR_EXPANDED_STORAGE_PREFIX = `${PLUGIN_ID}:route-sidebar-expanded:v2`; const WIKI_TOC_STICKY_TOP = 88; const WIKI_SPACE_PREFETCH_LIMIT = 8; const DEFAULT_ROUTE_SIDEBAR_EXPANDED_PATHS = [ "wiki", "wiki/sources", "wiki/projects", "wiki/entities", "wiki/concepts", "wiki/synthesis", ] as const; // --------------------------------------------------------------------------- // Shared types coming back from the worker. // --------------------------------------------------------------------------- type FolderStatus = { configured: boolean; path: string | null; realPath: string | null; access: "read" | "readWrite"; readable: boolean; writable: boolean; requiredDirectories: string[]; requiredFiles: string[]; missingDirectories: string[]; missingFiles: string[]; healthy: boolean; problems: { code: string; message: string; path?: string }[]; checkedAt: string; }; type ManagedAgent = { status: string; source?: "managed" | "selected"; agentId?: string | null; resourceKey?: string | null; details?: { name?: string; status?: string; adapterType?: string | null; icon?: string | null; urlKey?: string | null } | null; defaultDrift?: { entryFile: string; changedFiles: string[] } | null; }; type ManagedProject = { status: string; source?: "managed" | "selected"; projectId?: string | null; resourceKey?: string | null; details?: { name?: string; status?: string; color?: string | null } | null; }; type ManagedRoutine = { status: string; routineId?: string | null; resourceKey?: string | null; missingRefs?: Array<{ pluginKey?: string; resourceKind: string; resourceKey: string }>; defaultDrift?: { changedFields: string[]; defaultTitle?: string | null; defaultDescription?: string | null; } | null; routine?: { id?: string; title?: string; status?: string; assigneeAgentId?: string | null; projectId?: string | null; lastTriggeredAt?: string | null; lastEnqueuedAt?: string | null; managedByPlugin?: { pluginDisplayName?: string; resourceKey?: string; } | null; } | null; details?: { title?: string; status?: string; cronExpression?: string | null; enabled?: boolean; nextRunAt?: string | null; lastRunAt?: string | null; assigneeAgentId?: string | null; } | null; }; type ManagedRoutineDefaultDrift = NonNullable; type ManagedRoutinesListItemWithDrift = ManagedRoutinesListItem & { defaultDrift?: ManagedRoutineDefaultDrift | null; }; type ManagedSkill = { status: string; skillId?: string | null; resourceKey?: string | null; defaultDrift?: { changedFiles: string[] } | null; skill?: { id?: string; name?: string; key?: string; description?: string | null; } | null; details?: { name?: string; key?: string; description?: string | null; } | null; }; type OverviewData = { status: "ok"; checkedAt: string; wikiId: string; folder: FolderStatus; managedAgent: ManagedAgent; managedProject: ManagedProject; managedSkills: ManagedSkill[]; operationCount: number; eventIngestion: EventIngestionSettings; capabilities: string[]; prompts: { query: string; lint: string }; }; type EventIngestionSettings = { enabled: boolean; sources: { issues: boolean; comments: boolean; documents: boolean; }; wikiId: string; maxCharacters: number; }; type WikiEventIngestionSource = "issues" | "comments" | "documents"; type PaperclipIngestionSourceScope = | { kind: "active_projects"; limit: number; statuses?: Array<"in_progress" | "todo" | "done"> } | { kind: "selected_projects"; projectIds: string[] } | { kind: "root_issues"; issueIds: string[] } | { kind: "company_all"; requiresBoardConfirmation: true }; type PaperclipIngestionProfile = { version: 1; enabled: boolean; sourceScopes: PaperclipIngestionSourceScope[]; sourceKinds: { issues: boolean; comments: boolean; documents: boolean; attachments: "off" | "metadata_only"; workProducts: "off" | "metadata_only"; }; cursor: { maxWindowCharacters: number; maxCharactersPerSource: number; minSourceAgeMinutes: number; maxWindowsPerRun: number; staleAfterHours: number; }; backfill: { defaultStartAt?: string | null; defaultEndAt?: string | null; requireManualQueue: boolean; }; }; type PaperclipIngestionProfileData = { wikiId: string; space: Pick; profile: PaperclipIngestionProfile; effectiveState: "enabled" | "disabled" | "policy_blocked" | "pending_approval" | "enabled_no_scopes"; policyBlocks: string[]; historicalPageCount: number; overlapCount: number; }; type SettingsData = { folder: FolderStatus; managedAgent: ManagedAgent; managedProject: ManagedProject; managedRoutine?: ManagedRoutine; managedRoutines?: ManagedRoutine[]; managedSkills?: ManagedSkill[]; distillationPolicy?: { autoApplyAllowed: boolean; autoApplyRestriction: string | null; deploymentMode: "local_trusted" | "authenticated" | null; deploymentExposure: "private" | "public" | null; }; eventIngestion: EventIngestionSettings; agentOptions: Array<{ id: string; name: string; status?: string | null; adapterType?: string | null; icon?: string | null; urlKey?: string | null }>; projectOptions: Array<{ id: string; name: string; status?: string | null; color?: string | null }>; capabilities: string[]; }; type WikiSpace = { id: string; companyId: string; wikiId: string; slug: string; displayName: string; spaceType: string; folderMode: string; rootFolderKey: string; pathPrefix: string | null; configuredRootPath: string | null; accessScope: string; ownerUserId: string | null; ownerAgentId: string | null; teamKey: string | null; settings: Record; status: string; createdAt: string | null; updatedAt: string | null; }; type WikiSpacesData = { spaces: WikiSpace[]; }; type WikiSpaceWithFolderStatus = WikiSpace & { relativeRoot: string; folder: FolderStatus; }; const DEFAULT_SPACE_SLUG = "default"; type WikiPageRow = { path: string; title: string | null; pageType: string | null; backlinkCount: number; sourceCount: number; contentHash: string | null; updatedAt: string; }; type WikiSourceRow = { rawPath: string; title: string | null; sourceType: string; url: string | null; status: string; createdAt: string; }; type PagesData = { pages: WikiPageRow[]; sources: WikiSourceRow[]; }; type PageContentData = { wikiId: string; path: string; contents: string; title: string | null; pageType: string | null; backlinks: string[]; sourceRefs: Array | string>; updatedAt: string | null; hash: string; }; type WikiOperationRow = { id: string; operationType: string; status: string; hiddenIssueId: string | null; hiddenIssueIdentifier: string | null; hiddenIssueTitle: string | null; hiddenIssueStatus: string | null; projectId: string | null; runIds: unknown[]; costCents: number; warnings: unknown[]; affectedPages: unknown[]; metadata?: Record; createdAt: string; updatedAt: string; }; type OperationsData = { operations: WikiOperationRow[]; }; type TemplateData = { path: string; contents: string; hash: string | null; exists: boolean; }; type WikiFrontmatterValue = string | string[]; type WikiFrontmatterProperty = { key: string; value: WikiFrontmatterValue; }; type ParsedWikiMarkdown = { body: string; frontmatter: WikiFrontmatterProperty[]; }; type WikiTocHeading = { id: string; text: string; level: number; }; // --------------------------------------------------------------------------- // Small presentational primitives. // --------------------------------------------------------------------------- function Badge({ children, tone = "default", style }: { children: ReactNode; tone?: Tone; style?: CSSProperties }) { return ( {children} ); } function HiddenOpBadge() { return ( 📖 wiki task ); } function StatusIcon({ status }: { status: string }) { const map: Record = { done: { color: tokens.statusDone, filled: true }, in_progress: { color: tokens.statusInProgress }, running: { color: tokens.statusRunning, pulse: true }, queued: { color: tokens.statusTodo }, todo: { color: tokens.statusTodo }, blocked: { color: tokens.statusBlocked }, failed: { color: tokens.statusBlocked }, paused: { color: tokens.statusPaused }, }; const tone = map[status] ?? { color: tokens.muted }; return ( ); } function Card({ children, style }: { children: ReactNode; style?: CSSProperties }) { return (
{children}
); } function CardHeader({ title, right, badges }: { title: ReactNode; right?: ReactNode; badges?: ReactNode }) { return (

{title}

{badges} {right ?
{right}
: null}
); } function CardBody({ children, padding = 16 }: { children: ReactNode; padding?: number | string }) { return
{children}
; } const unfilledSurfaceStyle: CSSProperties = { background: "transparent", }; function PropRow({ label, value }: { label: ReactNode; value: ReactNode }) { return (
{label} {value}
); } function Tiny({ children, style }: { children: ReactNode; style?: CSSProperties }) { return
{children}
; } function Mono({ children, style }: { children: ReactNode; style?: CSSProperties }) { return {children}; } type ButtonVariant = "primary" | "default" | "ghost" | "destructive"; type ButtonSize = "sm" | "md"; function Button({ variant = "default", size = "md", disabled, loading, onClick, children, type = "button", style, title, }: { variant?: ButtonVariant; size?: ButtonSize; disabled?: boolean; loading?: boolean; onClick?: () => void; children: ReactNode; type?: "button" | "submit"; style?: CSSProperties; title?: string; }) { const palette: Record = { primary: { background: tokens.primary, color: tokens.primaryFg, border: `1px solid transparent`, }, default: { background: tokens.card, color: tokens.fg, border: `1px solid ${tokens.border}`, }, ghost: { background: "transparent", color: tokens.fg, border: `1px solid transparent`, }, destructive: { background: "transparent", color: "oklch(0.7 0.2 25)", border: `1px solid oklch(0.5 0.18 25)`, }, }; return ( ); } function TextInput(props: React.InputHTMLAttributes) { return ( ); } function TextArea(props: React.TextareaHTMLAttributes) { return (