@dendelion/paper-camp 0.20.0 → 0.21.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +34 -0
- package/dist/app/assets/index-By-exAb7.js +2 -0
- package/dist/app/assets/index-By-exAb7.js.map +1 -0
- package/dist/app/assets/{index-DMDqfoCe.js → index-CAmVn0M_.js} +2 -2
- package/dist/app/assets/{index-DMDqfoCe.js.map → index-CAmVn0M_.js.map} +1 -1
- package/dist/app/assets/index-CBsx6hnr.js +2 -0
- package/dist/app/assets/index-CBsx6hnr.js.map +1 -0
- package/dist/app/assets/index-DHe347Nk.js +2 -0
- package/dist/app/assets/index-DHe347Nk.js.map +1 -0
- package/dist/app/assets/index-DQ8GyDR3.js +7 -0
- package/dist/app/assets/index-DQ8GyDR3.js.map +1 -0
- package/dist/app/assets/index-Dff7A8Rp.js +2 -0
- package/dist/app/assets/{index-BHmWkgBW.js.map → index-Dff7A8Rp.js.map} +1 -1
- package/dist/app/assets/index-DqrpCrzX.js +4 -0
- package/dist/app/assets/index-DqrpCrzX.js.map +1 -0
- package/dist/app/assets/{index-D_Lo8ptS.js → index-H8Ja9gnB.js} +4 -4
- package/dist/app/assets/{index-D_Lo8ptS.js.map → index-H8Ja9gnB.js.map} +1 -1
- package/dist/app/assets/main-C8ibbBFi.js +425 -0
- package/dist/app/assets/main-C8ibbBFi.js.map +1 -0
- package/dist/app/assets/main-DEUaro51.css +1 -0
- package/dist/app/assets/{sidebar-section-B-A20bg9.js → sidebar-section-0u_lUROT.js} +2 -2
- package/dist/app/assets/{sidebar-section-B-A20bg9.js.map → sidebar-section-0u_lUROT.js.map} +1 -1
- package/dist/app/index.html +6 -6
- package/dist/app/toolbar.js +13775 -5454
- package/dist/app/toolbar.js.map +1 -1
- package/dist/chunks/{phase-progress.DPAZ01Gg.js → phase-progress.DimXpZFz.js} +1337 -1300
- package/dist/chunks/phase-progress.DimXpZFz.js.map +1 -0
- package/dist/cli/index.js +5269 -5072
- package/dist/cli/index.js.map +1 -1
- package/dist/core/corpus-format.d.ts +14 -0
- package/dist/core/corpus-format.d.ts.map +1 -1
- package/dist/core/entity-view.d.ts +6 -0
- package/dist/core/entity-view.d.ts.map +1 -0
- package/dist/core/git-log.d.ts +2 -0
- package/dist/core/git-log.d.ts.map +1 -1
- package/dist/core/git-pr/pr-lookup.d.ts.map +1 -1
- package/dist/core/index.js +106 -104
- package/dist/core/issues.d.ts +2 -3
- package/dist/core/issues.d.ts.map +1 -1
- package/dist/core/parse/parser.d.ts.map +1 -1
- package/dist/core/parse/schemas.d.ts +11 -0
- package/dist/core/parse/schemas.d.ts.map +1 -1
- package/dist/core/phase-run.d.ts +1 -0
- package/dist/core/phase-run.d.ts.map +1 -1
- package/dist/core/readers.d.ts +4 -3
- package/dist/core/readers.d.ts.map +1 -1
- package/dist/core/release-notes.d.ts +2 -0
- package/dist/core/release-notes.d.ts.map +1 -1
- package/dist/core/roadmap.d.ts +21 -0
- package/dist/core/roadmap.d.ts.map +1 -1
- package/dist/core/run-order.d.ts +2 -1
- package/dist/core/run-order.d.ts.map +1 -1
- package/dist/core/scaffold/scaffold.d.ts +1 -1
- package/dist/core/scaffold/scaffold.d.ts.map +1 -1
- package/dist/core/sections.d.ts.map +1 -1
- package/dist/core/serialize/entity-file.d.ts +79 -0
- package/dist/core/serialize/entity-file.d.ts.map +1 -0
- package/dist/core/serialize/index.d.ts +1 -0
- package/dist/core/serialize/index.d.ts.map +1 -1
- package/dist/core/serialize/serializer.d.ts +1 -76
- package/dist/core/serialize/serializer.d.ts.map +1 -1
- package/dist/core/stats.d.ts +2 -0
- package/dist/core/stats.d.ts.map +1 -1
- package/dist/core/status/status.d.ts +18 -1
- package/dist/core/status/status.d.ts.map +1 -1
- package/dist/types/index.d.ts +36 -18
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +16 -15
- package/dist/types/index.js.map +1 -1
- package/package.json +2 -2
- package/dist/app/assets/index-3pzg7Iyw.js +0 -2
- package/dist/app/assets/index-3pzg7Iyw.js.map +0 -1
- package/dist/app/assets/index-BHmWkgBW.js +0 -2
- package/dist/app/assets/index-BuzzfADD.js +0 -2
- package/dist/app/assets/index-BuzzfADD.js.map +0 -1
- package/dist/app/assets/index-CkLeljbk.js +0 -2
- package/dist/app/assets/index-CkLeljbk.js.map +0 -1
- package/dist/app/assets/index-DfekHwwU.js +0 -7
- package/dist/app/assets/index-DfekHwwU.js.map +0 -1
- package/dist/app/assets/index-Gi97Lmex.js +0 -4
- package/dist/app/assets/index-Gi97Lmex.js.map +0 -1
- package/dist/app/assets/main-Bm0W4ucz.css +0 -1
- package/dist/app/assets/main-DP_okYxh.js +0 -215
- package/dist/app/assets/main-DP_okYxh.js.map +0 -1
- package/dist/chunks/phase-progress.DPAZ01Gg.js.map +0 -1
package/dist/types/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../../src/types/index.ts"],"sourcesContent":["export type PlanStatus = 'idea' | 'planned' | 'in-progress' | 'review' | 'done' | 'dropped';\n\nexport const PLAN_STATUSES: PlanStatus[] = [\n 'idea',\n 'planned',\n 'in-progress',\n 'review',\n 'done',\n 'dropped',\n];\n\nexport interface LogEntry {\n date: string;\n text: string;\n}\n\nexport type PlanKind = 'feat' | 'fix' | 'chore' | 'docs' | 'refactor';\n\nexport const PLAN_KINDS: PlanKind[] = ['feat', 'fix', 'chore', 'docs', 'refactor'];\n\nexport const AGENT_IDS = ['claude-code', 'opencode'] as const;\n\nexport type AgentId = (typeof AGENT_IDS)[number];\n\nexport const AGENT_LABELS: Record<AgentId, string> = {\n 'claude-code': 'Claude Code',\n opencode: 'OpenCode',\n};\n\nexport interface PhaseRun {\n durationMs: number;\n inputTokens: number;\n outputTokens: number;\n model?: string;\n attempts: number;\n}\n\nexport interface PhaseItem {\n done: boolean;\n text: string;\n description?: string;\n source?: 'review' | 'manual' | 'issue';\n run?: PhaseRun;\n}\n\nexport type PhaseMilestone = 'implement' | 'verify' | 'checkbox';\n\n/** A body section anchor is `body` for now — the whole prose block is the only\n * body section a plan/entity has until later phases break it into sub-sections. */\nexport type MarginNoteAnchor = { kind: 'phase'; index: number } | { kind: 'body' };\n\nexport type MarginNoteState = 'open' | 'resolved';\n\nexport type MarginNoteKind = 'note' | 'decision' | 'question';\n\nexport interface MarginNote {\n anchor: MarginNoteAnchor;\n prose: string;\n state: MarginNoteState;\n kind?: MarginNoteKind;\n}\n\n/** What a thread message originally was, before log/review/notes/clarifications folded\n * into one ordered thread on the entity — 'note'/'decision'/'question' carry `state`,\n * the rest are plain historical records. 'chat' is thread-native (no legacy section):\n * Paper Scout conversation, collapsed by default in thread views. */\nexport type ThreadMessageKind =\n | 'log'\n | 'clarification'\n | 'review'\n | 'note'\n | 'decision'\n | 'question'\n | 'chat';\n\nexport interface ThreadMessage {\n kind: ThreadMessageKind;\n /** Absent for messages ported from the old Notes section, which never carried a date. */\n date?: string;\n text: string;\n /** Only meaningful for note/decision/question kinds. */\n state?: MarginNoteState;\n /** Absent means 'user' — no thread message predates this field being agent-authored. */\n from?: 'user' | 'agent';\n}\n\n/** Ambient context a chat mount (desk, toolbar) feeds alongside a message — each\n * mount populates whatever it has (IDEA-130); Scout folds it into the prompt\n * silently, it never becomes a user-visible field. */\nexport interface MountContext {\n route?: string;\n focusedIdeaId?: string;\n viewport?: { width: number; height: number };\n}\n\nexport interface RawEntry {\n title: string;\n fields: Record<string, string>;\n body: string;\n phases: PhaseItem[];\n log?: LogEntry[];\n clarifications?: LogEntry[];\n notes?: MarginNote[];\n review?: LogEntry[];\n}\n\nexport interface ParseWarning {\n title: string;\n message: string;\n}\n\nexport interface ParseResult<T> {\n entries: T[];\n warnings: ParseWarning[];\n}\n\n/** A PR's reviewable/mergeable state, as surfaced by `gh pr list`. */\nexport type PrState = 'draft' | 'open' | 'closed' | 'merged';\n\n/** GitHub's PR review decision, as surfaced by `gh pr list --json reviewDecision`. */\nexport type ReviewDecision = 'approved' | 'changes-requested' | 'review-required';\n\n// Per-comment detail; PrInfo.unresolvedThreadCount only counts these.\nexport interface ReviewThread {\n /** GraphQL node id — what `resolveReviewThread`/`replyToReviewThread` address. */\n id: string;\n /** File path the comment is anchored to; absent for a PR-level (not diff-anchored) comment. */\n path?: string;\n line?: number;\n author?: string;\n body: string;\n}\n\n// Parsed from the JSON object a fix-review agent's prompt requires as its final line.\nexport interface FixReviewResult {\n commit: { title: string; message: string };\n addressed: string[];\n skipped: { threadId: string; why: string }[];\n}\n\nexport interface PrReviewFinding {\n path: string;\n line: number;\n body: string;\n}\n\nexport type PrReviewVerdict = 'approve' | 'comment' | 'request-changes';\n\n// Parsed from the JSON object a pr-review agent's prompt requires as its final\n// line — see buildPrReviewPrompt. findings become per-line PR review comments;\n// the other fields are rendered per destination (IDEA-170, IDEA-175).\nexport interface PrReviewResult {\n verdict: PrReviewVerdict;\n assessment: string;\n concerns: string[];\n findings: PrReviewFinding[];\n}\n\nexport interface FeedbackPhaseEdit {\n op: 'add' | 'reword';\n /** 1-based position in the plan's current phase list; required for \"reword\", ignored for \"add\". */\n index?: number;\n text: string;\n description?: string;\n}\n\nexport interface FeedbackEdit {\n phases?: FeedbackPhaseEdit[];\n /** The complete replacement body, when the message corrects body prose. */\n body?: string;\n}\n\n// Parsed from the JSON object a feedback-reply agent's prompt requires as its\n// final line. The chat never creates ideas — a request is either a reply or an\n// edit on the current plan.\nexport interface FeedbackReplyResult {\n reply: string;\n edit?: FeedbackEdit;\n /** True when the user's message answers an open question the agent asked\n * earlier in this thread — the route persists that message as a clarification. */\n answersQuestion?: boolean;\n}\n\n/** Live-resolved PR info for an entity's branch — see `core/pr.ts`. */\nexport interface PrInfo {\n number: number;\n url: string;\n state: PrState;\n /** Undefined when no review has been requested/decided, or when unresolved (offline, closed/merged PR). */\n reviewDecision?: ReviewDecision;\n /** Count of unresolved review threads. Only fetched for open/draft PRs; undefined when not fetched or unresolved. */\n unresolvedThreadCount?: number;\n /** Whether a comment or review landed after the PR's last commit — a proxy for \"since the last agent pass\" (a pass ends with a push). Only fetched for open/draft PRs. */\n hasNewCommentsSincePush?: boolean;\n /** Full SHA of the PR's current head commit — what a pr-review is scoped to (IDEA-170). */\n headSha?: string;\n headBranch?: string;\n /** A posted review's footer names this entity and `headSha` — set once the poll\n * observes it (IDEA-175). */\n scoutReviewObserved?: boolean;\n}\n\n/** Advisory info for the \"Review PR\" action — nothing here gates the button,\n * it only labels it (IDEA-174 fix 2). */\nexport interface PrReviewStatus {\n ready: boolean;\n headSha: string | null;\n lastReviewedSha: string | null;\n /** null when no CI is configured, or the PR's branch has no runs yet. */\n ciGreen: boolean | null;\n}\n\nexport interface PlanEntry {\n title: string;\n status: PlanStatus;\n kind?: PlanKind;\n /** 'fix' when this plan is a follow-up fix entity (IDEA-187) — undefined for an\n * ordinary plan-bearing idea. Distinct from `kind`, which carries the commit type. */\n entityKind?: EntityKind;\n id?: string;\n idea?: string;\n agent?: AgentId;\n created: string;\n updated?: string;\n audited?: string;\n auditedHash?: string;\n /** Version tag (e.g. v0.13.1) that first shipped this idea, stamped from the release train. */\n released?: string;\n tags: string[];\n /** Absent renders under the virtual \"No subject\" group. */\n subject?: string;\n /** Absent means unordered — sorts after all ordered entries, by created date. */\n order?: number;\n /** See EntityEntry.issueSource. */\n issueSource?: string;\n body: string;\n phases: PhaseItem[];\n /** Post-build findings, same checkbox grammar as `phases` — see EntityEntry.fixes. */\n fixes?: PhaseItem[];\n log?: LogEntry[];\n clarifications?: LogEntry[];\n notes?: MarginNote[];\n /** Prose written against the whole finished plan, distinct from the flat `log` — an\n * agent later splits each entry into rework phases or a follow-up idea. */\n review?: LogEntry[];\n /** The entity's single ordered feedback thread — see EntityEntry.thread. */\n thread?: ThreadMessage[];\n /** Set by readEntities from which dir the file came from — see EntityEntry.archived. */\n archived?: boolean;\n pr?: PrInfo;\n /** True when `status` is a phase-count/stored-override guess because GitHub's PR\n * state couldn't be resolved, rather than derived from a resolved PR — see\n * isStatusFallback. */\n statusFallback?: boolean;\n}\n\nexport interface ArchivableIdea {\n id: string;\n title: string;\n pr: PrInfo;\n}\n\n/** One open `question` thread message, resolved to the idea it's parked on. `ageDays`\n * is `Infinity` for a question ported from the pre-Thread Notes section, which never\n * carried a date — the oldest kind of unresolved question there is. */\nexport interface ParkedQuestion {\n entityId: string;\n entityTitle: string;\n text: string;\n date?: string;\n ageDays: number;\n}\n\n/** The two event-sourced notification kinds (IDEA-153) — 'completed' when a task\n * reaches done/error, 'reply' when an agent posts a non-blocking feedback thread\n * entry. Unlike ParkedQuestion (derived live from thread state), these are stored\n * as discrete events on read, so a since-resolved source can't erase them. */\nexport type StoredNotificationKind = 'completed' | 'reply';\n\nexport interface StoredNotification {\n id: string;\n kind: StoredNotificationKind;\n entityId: string;\n entityTitle: string;\n text: string;\n date: string;\n read: boolean;\n /** Only set for 'completed' — the task outcome that triggered it. */\n outcome?: 'done' | 'error';\n}\n\n/** One unified feed entry (IDEA-153) — a parked question (open→resolved, always\n * unread while it's shown at all) alongside the two stored, read/unread kinds. */\nexport type Notification = (ParkedQuestion & { kind: 'question' }) | StoredNotification;\n\nexport type ConsistencyIssueKind = 'orphan-subject' | 'title-style';\n\nexport interface ConsistencyIssue {\n kind: ConsistencyIssueKind;\n section: 'plans';\n title: string;\n message: string;\n planId?: string;\n}\n\nexport interface CommentStats {\n commentLines: number;\n sourceLines: number;\n ratio: number;\n}\n\nexport interface TasksPerWeek {\n /** ISO week, e.g. \"2026-W05\". */\n week: string;\n count: number;\n}\n\nexport interface UsagePerWeek {\n /** ISO week, e.g. \"2026-W05\". */\n week: string;\n agentMinutes: number;\n inputTokens: number;\n outputTokens: number;\n}\n\nexport interface IdeaCost {\n planId: string;\n planTitle: string;\n inputTokens: number;\n outputTokens: number;\n durationMs: number;\n}\n\nexport interface CapacityStat {\n snapshot: RateLimitSnapshot;\n capturedAt: string;\n}\n\nexport interface ProjectStats {\n generatedAt: string;\n comments: CommentStats;\n testLines: number;\n /** Null when no coverage report has been emitted yet (run `pnpm test`). */\n testCoveragePct: number | null;\n entitiesByStatus: Partial<Record<EntityStatus, number>>;\n openQuestions: number;\n decisions: number;\n tasksPerWeek: TasksPerWeek[];\n usagePerWeek: UsagePerWeek[];\n medianPhaseDurationMs: number | null;\n mostExpensiveIdeas: IdeaCost[];\n capacity: CapacityStat | null;\n}\n\nexport type IdeaKind = 'idea' | 'note';\n\nexport type IdeaStatus = 'open' | 'done' | 'dropped';\n\nexport interface IdeaEntry {\n id: string | null;\n title: string;\n body: string;\n kind?: IdeaKind;\n status?: IdeaStatus;\n /** Absent renders under the virtual \"No subject\" group. */\n subject?: string;\n /** Absent means unordered — sorts after all ordered entries, by created date. */\n order?: number;\n /** Fallback sort key for unordered entries — see `order`. Absent on view-model-only IdeaEntry literals that never enter the worklist sort. */\n created?: string;\n log?: LogEntry[];\n}\n\n// Unified entity — one file per entity, plan as an optional Phases section.\n// Supersedes PlanEntry/IdeaEntry once the migration cutover lands.\n\n/** Work classification — same Conventional-Commits values as PlanKind; `kind` renamed to `type` in the unified schema. */\nexport type EntityType = PlanKind;\n\n/** Plan lifecycle plus the note-only `open`. Notes use open → done/dropped; everything else uses the PlanStatus track. */\nexport type EntityStatus = PlanStatus | 'open';\n\n/** \"note\" never grows phases; \"fix\" is a follow-up entity linked to a done/archived\n * parent via `idea` — see entityFrontmatterSchema. Omitted for a normal idea. */\nexport type EntityKind = 'note' | 'fix';\n\nexport interface EntityEntry {\n id: string;\n title: string;\n /** Absent until the entity is classified (usually when its plan is drafted). */\n type?: EntityType;\n kind?: EntityKind;\n /** Stored override, not the source of truth — see entityFrontmatterSchema. */\n status?: EntityStatus;\n /** IDEA-N backlink to the parent this fix addresses; required when kind: fix. */\n idea?: string;\n agent?: AgentId;\n created: string;\n updated?: string;\n audited?: string;\n auditedHash?: string;\n /** Version tag (e.g. v0.13.1) that first shipped this idea, stamped from the release train. */\n released?: string;\n tags: string[];\n /** Absent renders as the virtual \"No subject\" group. */\n subject?: string;\n /** Absent means unordered — sorts after all ordered entries, by created date. */\n order?: number;\n /** The Issue.id (`sourceKind:sourceKey`, IDEA-192) this entity was spawned to promote,\n * if any — how a promoted issue is re-matched to its target on every read, since\n * issues carry no store of their own. */\n issueSource?: string;\n body: string;\n phases: PhaseItem[];\n /** Post-build findings, same checkbox grammar as `phases` — appended below Phases\n * rather than rewriting the plan's already-finished phase history. */\n fixes?: PhaseItem[];\n /** The entity's single ordered feedback thread — folds what used to be separate\n * log/clarifications/notes/review sections. */\n thread?: ThreadMessage[];\n /** Set by readEntities from which of the two scanned dirs the file came from, not the frontmatter. */\n archived?: boolean;\n /** Frontmatter keys this paper-camp doesn't recognise, carried through verbatim so a\n * write never drops a field a newer version wrote — see entityFrontmatterKnownKeys. */\n unknownFrontmatter?: Record<string, unknown>;\n}\n\n// No `id`/`status`: it only becomes a real idea if a human promotes it.\nexport interface SuggestionEntry {\n date: string;\n title: string;\n description: string;\n}\n\nexport interface RoadmapItem {\n name: string;\n description: string;\n candidates: string[];\n linked: string[];\n}\n\nexport interface RoadmapHorizon {\n title: string;\n items: RoadmapItem[];\n}\n\nexport interface Roadmap {\n goal: string;\n horizons: RoadmapHorizon[];\n standingConcerns: RoadmapItem[];\n}\n\nexport interface RoadmapLink {\n id: string;\n status: PlanStatus;\n taskRuns: number;\n pr?: PrInfo;\n released: boolean;\n}\n\nexport interface RoadmapRollup {\n total: number;\n done: number;\n}\n\nexport interface ResolvedRoadmapItem extends RoadmapItem {\n links: RoadmapLink[];\n rollup: RoadmapRollup;\n}\n\nexport interface ResolvedRoadmapHorizon {\n title: string;\n items: ResolvedRoadmapItem[];\n rollup: RoadmapRollup;\n}\n\nexport interface ResolvedRoadmap {\n goal: string;\n horizons: ResolvedRoadmapHorizon[];\n events: RoadmapEvent[];\n}\n\nexport type RoadmapEventKind = 'created' | 'task-run';\n\nexport interface RoadmapEvent {\n date: string;\n kind: RoadmapEventKind;\n entityId: string;\n horizonTitle: string;\n itemName: string;\n label: string;\n}\n\nexport interface EnvEntry {\n key: string;\n value: string;\n /** Set on GET responses when the value is withheld — secrets never reach the client. */\n isSet?: boolean;\n}\n\nexport interface AgentConfig {\n agent: AgentId;\n model?: string;\n effort?: string;\n}\n\n/** Model/effort passed to an adapter's buildArgs — the AgentConfig minus the agent id. */\nexport interface AgentRunOptions {\n model?: string;\n effort?: string;\n /** Prior claude-code session id to continue instead of starting cold; ignored by opencode. */\n resume?: string;\n}\n\n/** Maps option names to a fixed value list (renders a Select) or null (free-text or hidden). */\nexport type AgentOptionsDescriptor = Record<string, string[] | null | undefined>;\n\nexport const AGENT_OPTIONS: Record<AgentId, AgentOptionsDescriptor> = {\n 'claude-code': {\n model: ['opus', 'sonnet', 'fable', 'haiku'],\n effort: ['low', 'medium', 'high', 'xhigh', 'max'],\n },\n opencode: {\n model: null,\n },\n};\n\nexport function coerceAgentConfig(v: unknown): AgentConfig {\n const toAgent = (a: unknown): AgentId =>\n AGENT_IDS.includes(a as AgentId) ? (a as AgentId) : 'claude-code';\n if (typeof v === 'string') return { agent: toAgent(v) };\n // A missing key must not throw — that would 500 the /api/config read this protects.\n const obj = (v ?? {}) as Record<string, unknown>;\n return {\n agent: toAgent(obj.agent),\n ...(typeof obj.model === 'string' && { model: obj.model }),\n ...(typeof obj.effort === 'string' && { effort: obj.effort }),\n };\n}\n\nexport interface DefaultAgentsMap {\n phase: AgentConfig;\n planDraft: AgentConfig;\n ideaExtend: AgentConfig;\n commitSuggest: AgentConfig;\n feedback: AgentConfig;\n codeReview: AgentConfig;\n}\n\nexport const DEFAULT_AGENTS: DefaultAgentsMap = {\n phase: { agent: 'opencode' },\n planDraft: { agent: 'claude-code' },\n ideaExtend: { agent: 'claude-code' },\n commitSuggest: { agent: 'claude-code' },\n feedback: { agent: 'claude-code', model: 'sonnet', effort: 'medium' },\n codeReview: { agent: 'claude-code', model: 'opus', effort: 'high' },\n};\n\n/** A reviewer must not use the same model as the task that wrote the code (IDEA-170) — self-review rubber-stamps. */\nexport function agentConfigsEqual(a: AgentConfig, b: AgentConfig): boolean {\n return a.agent === b.agent && (a.model ?? '') === (b.model ?? '');\n}\n\n/** Toolbar segments, left to right (IDEA-128/IDEA-133); trimmed per project via `IntegrationConfig.toolbar.segments`. */\nexport const TOOLBAR_SEGMENT_IDS = ['focus', 'scout', 'desk'] as const;\n\nexport type ToolbarSegmentId = (typeof TOOLBAR_SEGMENT_IDS)[number];\n\nexport interface IntegrationToolbarConfig {\n enabled?: boolean;\n segments?: ToolbarSegmentId[];\n /** Opt-in escape hatch: without it, the vite plugin stays off when `mode` is `production`. */\n allowProduction?: boolean;\n}\n\nexport interface IntegrationConfig {\n toolbar?: IntegrationToolbarConfig;\n route?: string;\n}\n\nexport interface DeskService {\n name: string;\n cmd: string;\n port?: number;\n healthcheck?: string;\n}\n\nexport interface DeskCheck {\n name: string;\n cmd: string;\n}\n\nexport interface DeskCi {\n repo: string;\n branch?: string;\n releasePlease?: boolean;\n}\n\n/** Dev-loop manifest (IDEA-119): declared services, one-click checks, and CI/release sources. */\nexport interface DeskConfig {\n services?: DeskService[];\n checks?: DeskCheck[];\n ci?: DeskCi;\n}\n\nexport type ServiceRunStatus = 'stopped' | 'running' | 'stopping' | 'crashed';\n\nexport type ServiceHealth = 'unknown' | 'up' | 'down';\n\n/** Live runtime view of a declared service (IDEA-119), merging manifest + process state. */\nexport interface ServiceState {\n name: string;\n cmd: string;\n port?: number;\n hasHealthcheck: boolean;\n status: ServiceRunStatus;\n health: ServiceHealth;\n pid: number | null;\n startedAt: string | null;\n exitCode: number | null;\n}\n\n/** Live result of a declared one-click check (IDEA-119), mirroring the Stack panel's CheckResult. */\nexport interface DeskCheckState {\n name: string;\n cmd: string;\n status: CheckStatus;\n lastRun: string | null;\n output: string;\n}\n\nexport type CiRunStatus =\n | 'success'\n | 'failure'\n | 'in_progress'\n | 'queued'\n | 'cancelled'\n | 'unknown';\n\nexport interface CiRun {\n workflow: string;\n status: CiRunStatus;\n url: string | null;\n}\n\nexport interface ReleasePr {\n number: number;\n title: string;\n url: string;\n version: string | null;\n}\n\n/** CI & release mirror for a declared repo (IDEA-119): latest Actions runs on the tracked\n * branch, the open release-please PR, and the released vs. queued version. */\nexport interface CiReleaseState {\n repo: string;\n branch: string;\n available: boolean;\n runs: CiRun[];\n releasePr: ReleasePr | null;\n releasedVersion: string | null;\n}\n\nexport interface PaperCampConfig {\n /** Corpus format version (see CORPUS_FORMAT_VERSION) — the shape this file and the\n * entity frontmatter it sits alongside conform to, not the npm package version. */\n version: number;\n projectName: string;\n initializedAt: string;\n /** The unified-entity `idea` counter; the per-kind plan counters are legacy, present only in pre-migration configs. */\n nextId?: Partial<Record<PlanKind, number>> & { idea?: number };\n port?: number;\n defaultAgents?: DefaultAgentsMap;\n /** Derived from `ROADMAP.md` on every read (IDEA-95), never written to disk; an idea's\n * `subject` not present here renders as \"No subject\". */\n subjects?: string[];\n /** Opts out of the first-run redirect to Settings > Setup while capabilities are incomplete. */\n setupDismissed?: boolean;\n /** In-app dev toolbar (IDEA-128) — the `@dendelion/paper-camp/vite` plugin's mount, toggleable for frontend targets. */\n integration?: IntegrationConfig;\n /** Dev-loop desk manifest (IDEA-119) — declared services, checks, and CI/release sources per project. */\n desk?: DeskConfig;\n /** Project-declared commands the desk/embed can run on demand (IDEA-157). */\n commands?: {\n /** Manual build for the Stack's Build action; no universal default. */\n build?: string;\n };\n}\n\nexport type CheckStatus = 'stale' | 'running' | 'pass' | 'fail';\n\nexport interface CheckResult {\n status: CheckStatus;\n /** The shell command this check runs — empty until a build command is configured. */\n cmd: string;\n lastRun: string | null;\n output: string;\n}\n\nexport type CheckName = 'lint' | 'format' | 'test' | 'consistency' | 'build';\n\nexport type CapabilityStatus = 'ok' | 'warn' | 'missing';\n\nexport interface CapabilityResult {\n id: string;\n status: CapabilityStatus;\n detail: string;\n}\n\nexport interface AgentAuthStatus {\n loggedIn: boolean | null;\n authMethod: string | null;\n apiProvider: string | null;\n}\n\nexport type LoginRelayPhase =\n | 'starting'\n | 'awaiting-authorization'\n | 'success'\n | 'error'\n | 'cancelled';\n\nexport interface LoginRelayState {\n phase: LoginRelayPhase;\n authorizeUrl: string | null;\n error?: string;\n}\n\nexport type ServiceId = 'git' | 'gh' | `agent:${AgentId}`;\n\n/** What a Connections row offers once its service isn't `ok` — copy a command, follow a link, or read plain instructions. */\nexport type ConnectAction =\n /** `runnable` marks a command with no placeholders and no interactive prompts — safe for the server to execute directly instead of just displaying it. */\n | { kind: 'command'; command: string; runnable?: boolean }\n | { kind: 'link'; url: string; label: string }\n | { kind: 'text'; message: string };\n\nexport interface ConnectionResult {\n id: ServiceId;\n label: string;\n unlocks: string;\n status: CapabilityStatus;\n detail: string;\n authenticated: boolean | null;\n connect: ConnectAction | null;\n}\n\nexport interface MergePolicy {\n allowSquashMerge: boolean;\n allowMergeCommit: boolean;\n allowRebaseMerge: boolean;\n squashMergeCommitTitle: string;\n squashMergeCommitMessage: string;\n}\n\nexport type MergePolicyResult =\n | { status: 'ok'; repo: string; policy: MergePolicy }\n | { status: 'unavailable'; reason: string };\n\nexport interface GitStatusEntry {\n path: string;\n status: string;\n staged: boolean;\n renameSource?: string;\n}\n\nexport interface GitStashEntry {\n index: number;\n branch: string;\n message: string;\n ageDays: number;\n own: boolean;\n}\n\nexport interface GitStatusResponse {\n branch: string;\n entries: GitStatusEntry[];\n ahead: number;\n behind: number;\n diverged: boolean;\n branchHygiene: BranchHygieneStatus;\n stashPending: boolean;\n stashes: GitStashEntry[];\n}\n\nexport interface FileDiffEntry {\n path: string;\n renameSource?: string;\n status: string;\n staged: boolean;\n binary: boolean;\n additions: number;\n deletions: number;\n // 'diff' is a unified patch (tracked files); 'raw' is untracked file content, not a\n // patch; 'too-large' means `patch` is empty and the file was skipped.\n contentKind: 'diff' | 'raw' | 'too-large';\n patch: string;\n}\n\nexport type BranchHygieneStatus =\n | 'clean-on-main'\n | 'stale-merged'\n | 'stale-no-upstream'\n | 'dirty'\n | 'fine';\n\nexport interface GitLiveState {\n branch: string;\n ahead: number;\n behind: number;\n dirtyCount: number;\n}\n\n// null means the entity file doesn't exist at that ref (never drafted there yet, or archived).\nexport type PhaseState = { done: number; total: number } | null;\n\n// The ref that's ahead of the current branch's phase state, and its own count — see IDEA-171.\nexport interface StaleBaseRef {\n ref: string;\n done: number;\n total: number;\n}\n\nexport interface GitSyncFailure {\n ok: false;\n stage: 'reconcile' | 'stash-pop' | 'conflicted';\n message: string;\n stashPending: boolean;\n // Only set when stage is 'conflicted': the files a rebase left with unresolved\n // markers, for a one-click \"ask the agent to resolve\" rather than a bare error.\n conflictedFiles?: string[];\n // Only set when stage is 'conflicted': the ref the rebase targeted, and each\n // conflicted file's last-seen content (with markers) before the rebase was\n // aborted for safety — the resolve-conflict agent's raw material.\n conflictRef?: string;\n conflictedContent?: { path: string; content: string }[];\n // Prompt for the agent recovery job (see git-sync-recovery.ts) — the deterministic\n // path's failure, the working-tree state, and the goal, packaged for a future\n // launch rather than thrown at the user.\n recoveryPrompt: string;\n // Only set when stage is 'conflicted': a prompt scoped to landing this one rebase\n // (see resolve-conflict-prompt.ts) — launched only on explicit \"ask the agent to\n // resolve\" confirmation from the sync-failed toast, never automatically.\n conflictPrompt?: string;\n}\n\nexport type GitSyncResult = { ok: true } | GitSyncFailure;\n\nexport type AgentTaskStatus = 'starting' | 'running' | 'stopping' | 'done' | 'error' | 'superseded';\n\nexport type TaskKind =\n | 'phase'\n | 'audit'\n | 'batch-reconcile'\n | 'batch-draft'\n | 'run-all'\n | 'draft'\n | 'extend'\n | 'suggest'\n | 'commit-suggest'\n | 'overlap-check'\n | 'prioritise'\n | 'sync'\n | 'reconcile'\n | 'fix-review'\n | 'resolve-conflict'\n | 'feedback'\n | 'pr-review'\n | 'issue-fix';\n\n// Persisted to papercamp/tasks.log (JSON Lines) — survives a dev-server restart,\n// unlike the in-memory task registry.\nexport interface TaskLogEntry {\n id: string;\n taskKind: TaskKind;\n planId?: string;\n planTitle: string;\n agentId: AgentId;\n startedAt: string;\n endedAt: string;\n outcome: 'done' | 'error' | 'superseded';\n reason?: string;\n usage?: RunUsage;\n phaseRuns?: PhaseRunRecord[];\n rateLimit?: RateLimitSnapshot;\n /** issue-fix only: the Issue.id (IDEA-192) this run was launched to fix, so a\n * repeat failure's thread can be reconstructed by matching entries to the issue. */\n issueId?: string;\n}\n\nexport interface RunUsage {\n durationMs: number;\n numTurns: number;\n model?: string;\n inputTokens: number;\n outputTokens: number;\n cacheCreationTokens: number;\n cacheReadTokens: number;\n costUsd: number;\n}\n\nexport interface RateLimitSnapshot {\n status: string;\n rateLimitType?: string;\n resetsAt?: number;\n overage?: boolean;\n}\n\nexport interface PhaseRunRecord {\n kind: 'phase' | 'fix';\n index: number;\n usage: RunUsage;\n}\n\nexport interface TrailHop<T> {\n reached: boolean;\n data?: T;\n}\n\nexport interface ProvenanceTrail {\n id: string;\n idea: TrailHop<{ title: string; status?: EntityStatus; type?: EntityType }>;\n phases: TrailHop<PhaseItem[]>;\n taskRuns: TrailHop<TaskLogEntry[]>;\n commits: TrailHop<string[]>;\n pr: TrailHop<PrInfo>;\n releaseLine: TrailHop<string>;\n}\n\nexport interface ReleaseNoteIdea {\n id: string;\n title: string;\n}\n\nexport interface ReleaseNoteSection {\n label: string;\n ideas: ReleaseNoteIdea[];\n}\n\nexport interface AgentTaskState {\n id: string;\n status: AgentTaskStatus;\n taskKind: TaskKind;\n planTitle: string;\n startedAt: string;\n planId?: string;\n phaseIndex?: number;\n fixAttempt?: number;\n fixAttemptCap?: number;\n ideaId?: string;\n agentId: AgentId;\n lines: string[];\n phaseAnchor?: PhaseMilestone;\n anchorEnteredAt?: number;\n lastStreamAt?: number;\n // fix-review only: prefills the commit form once the agent has reported.\n suggestedCommit?: { title: string; message: string };\n // pr-review only: the PR being reviewed, e.g. https://github.com/o/r/pull/7.\n prReviewUrl?: string;\n errorKind?: 'auth' | 'question';\n rateLimit?: RateLimitSnapshot;\n // issue-fix only: the Issue.id this run was launched to fix.\n issueId?: string;\n}\n\nexport interface OverlapVerdict {\n verdict: 'existing' | 'extend' | 'new';\n targetId: string | null;\n reasoning: string;\n}\n\n/** `why` carries one line per entry in `order`, same index — the reason for that id's placement. */\nexport interface PrioritiseVerdict {\n order: string[];\n why: string;\n}\n\nexport interface ReconcileQueueItem {\n planId: string;\n title: string;\n before: { body: string; phases: PhaseItem[] };\n}\n\n/** The four failures IDEA-192 collects into one place: a failed agent run, a red\n * project check, a PR review that requested changes, and a git rebase/sync failure. */\nexport type IssueSourceKind = 'agent-run' | 'check' | 'pr-review' | 'sync';\n\n/** Derived from the world on every read, never a mark-read button — 'open' while a\n * collector still finds the failure, 'closed' once it doesn't or the promoted fix\n * that carries it ships. */\nexport type IssueStatus = 'open' | 'closed';\n\n/** One collected failure (IDEA-192), read as a short thread. `id` is derived from\n * `sourceKind` + `sourceKey`, so a repeat failure of the same thing resolves to the\n * same issue and continues its thread instead of raising a duplicate. */\nexport interface Issue {\n id: string;\n sourceKind: IssueSourceKind;\n /** Stable within `sourceKind` — a task's `planId`, a check's `name`, a PR's\n * `number` + `headSha`, or a sync failure's branch + target ref. */\n sourceKey: string;\n entityId?: string;\n entityTitle?: string;\n title: string;\n reason: string;\n /** Last output lines, when the source captured any (a check's command output, a\n * sync failure's conflicted files). */\n output?: string;\n /** Absent when the source carries no timestamp of its own (a live PR review decision). */\n occurredAt?: string;\n thread: ThreadMessage[];\n status: IssueStatus;\n /** Set once promoted (IDEA-192 phase 6) — the id of whatever now carries the work:\n * a spawned fix entity, a spawned plain idea (no parent), or the open parent an\n * issue was appended to as an inline fix. A promoted issue closes when that\n * entity's status reaches 'done', independent of whether the original source is\n * still detected as failing. */\n promotedFixId?: string;\n}\n"],"names":["PLAN_STATUSES","PLAN_KINDS","AGENT_IDS","AGENT_LABELS","AGENT_OPTIONS","coerceAgentConfig","v","toAgent","a","obj","DEFAULT_AGENTS","agentConfigsEqual","b","TOOLBAR_SEGMENT_IDS"],"mappings":"AAEO,MAAMA,IAA8B;AAAA,EACzC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GASaC,IAAyB,CAAC,QAAQ,OAAO,SAAS,QAAQ,UAAU,GAEpEC,IAAY,CAAC,eAAe,UAAU,GAItCC,IAAwC;AAAA,EACnD,eAAe;AAAA,EACf,UAAU;AACZ,GA0eaC,IAAyD;AAAA,EACpE,eAAe;AAAA,IACb,OAAO,CAAC,QAAQ,UAAU,SAAS,OAAO;AAAA,IAC1C,QAAQ,CAAC,OAAO,UAAU,QAAQ,SAAS,KAAK;AAAA,EAAA;AAAA,EAElD,UAAU;AAAA,IACR,OAAO;AAAA,EAAA;AAEX;AAEO,SAASC,EAAkBC,GAAyB;AACzD,QAAMC,IAAU,CAACC,MACfN,EAAU,SAASM,CAAY,IAAKA,IAAgB;AACtD,MAAI,OAAOF,KAAM,SAAU,QAAO,EAAE,OAAOC,EAAQD,CAAC,EAAA;AAEpD,QAAMG,IAAOH,KAAK,CAAA;AAClB,SAAO;AAAA,IACL,OAAOC,EAAQE,EAAI,KAAK;AAAA,IACxB,GAAI,OAAOA,EAAI,SAAU,YAAY,EAAE,OAAOA,EAAI,MAAA;AAAA,IAClD,GAAI,OAAOA,EAAI,UAAW,YAAY,EAAE,QAAQA,EAAI,OAAA;AAAA,EAAO;AAE/D;AAWO,MAAMC,IAAmC;AAAA,EAC9C,OAAO,EAAE,OAAO,WAAA;AAAA,EAChB,WAAW,EAAE,OAAO,cAAA;AAAA,EACpB,YAAY,EAAE,OAAO,cAAA;AAAA,EACrB,eAAe,EAAE,OAAO,cAAA;AAAA,EACxB,UAAU,EAAE,OAAO,eAAe,OAAO,UAAU,QAAQ,SAAA;AAAA,EAC3D,YAAY,EAAE,OAAO,eAAe,OAAO,QAAQ,QAAQ,OAAA;AAC7D;AAGO,SAASC,EAAkBH,GAAgBI,GAAyB;AACzE,SAAOJ,EAAE,UAAUI,EAAE,UAAUJ,EAAE,SAAS,SAASI,EAAE,SAAS;AAChE;AAGO,MAAMC,IAAsB,CAAC,SAAS,SAAS,MAAM;"}
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../../src/types/index.ts"],"sourcesContent":["// Shared between server (isForbiddenRequest) and client (api-base) so a hosted\n// client sending this header and a server checking it can't drift apart.\nexport const PAIRING_TOKEN_HEADER = 'x-pairing-token' as const;\n\nexport type PlanStatus = 'idea' | 'planned' | 'in-progress' | 'review' | 'done' | 'dropped';\n\nexport const PLAN_STATUSES: PlanStatus[] = [\n 'idea',\n 'planned',\n 'in-progress',\n 'review',\n 'done',\n 'dropped',\n];\n\nexport interface LogEntry {\n date: string;\n text: string;\n}\n\nexport type PlanKind = 'feat' | 'fix' | 'chore' | 'docs' | 'refactor';\n\nexport const PLAN_KINDS: PlanKind[] = ['feat', 'fix', 'chore', 'docs', 'refactor'];\n\nexport const AGENT_IDS = ['claude-code', 'opencode'] as const;\n\nexport type AgentId = (typeof AGENT_IDS)[number];\n\nexport const AGENT_LABELS: Record<AgentId, string> = {\n 'claude-code': 'Claude Code',\n opencode: 'OpenCode',\n};\n\nexport interface PhaseRun {\n durationMs: number;\n inputTokens: number;\n outputTokens: number;\n model?: string;\n attempts: number;\n}\n\nexport interface PhaseItem {\n done: boolean;\n text: string;\n description?: string;\n source?: 'review' | 'manual' | 'issue';\n run?: PhaseRun;\n}\n\nexport type PhaseMilestone = 'implement' | 'verify' | 'checkbox';\n\n/** A body section anchor is `body` for now — the whole prose block is the only\n * body section a plan/entity has until later phases break it into sub-sections. */\nexport type MarginNoteAnchor = { kind: 'phase'; index: number } | { kind: 'body' };\n\nexport type MarginNoteState = 'open' | 'resolved';\n\nexport type MarginNoteKind = 'note' | 'decision' | 'question';\n\nexport interface MarginNote {\n anchor: MarginNoteAnchor;\n prose: string;\n state: MarginNoteState;\n kind?: MarginNoteKind;\n}\n\n/** What a thread message originally was, before log/review/notes/clarifications folded\n * into one ordered thread on the entity — 'note'/'decision'/'question' carry `state`,\n * the rest are plain historical records. 'chat' is thread-native (no legacy section):\n * Paper Scout conversation, collapsed by default in thread views. */\nexport type ThreadMessageKind =\n | 'log'\n | 'clarification'\n | 'review'\n | 'note'\n | 'decision'\n | 'question'\n | 'chat';\n\nexport interface ThreadMessage {\n kind: ThreadMessageKind;\n /** Absent for messages ported from the old Notes section, which never carried a date. */\n date?: string;\n text: string;\n /** Only meaningful for note/decision/question kinds. */\n state?: MarginNoteState;\n /** Absent means 'user' — no thread message predates this field being agent-authored. */\n from?: 'user' | 'agent';\n}\n\n/** Ambient context a chat mount (desk, toolbar) feeds alongside a message — each\n * mount populates whatever it has (IDEA-130); Scout folds it into the prompt\n * silently, it never becomes a user-visible field. */\nexport interface MountContext {\n route?: string;\n focusedIdeaId?: string;\n viewport?: { width: number; height: number };\n}\n\nexport interface RawEntry {\n title: string;\n fields: Record<string, string>;\n body: string;\n phases: PhaseItem[];\n log?: LogEntry[];\n clarifications?: LogEntry[];\n notes?: MarginNote[];\n review?: LogEntry[];\n}\n\nexport interface ParseWarning {\n title: string;\n message: string;\n}\n\nexport interface ParseResult<T> {\n entries: T[];\n warnings: ParseWarning[];\n}\n\n/** A PR's reviewable/mergeable state, as surfaced by `gh pr list`. */\nexport type PrState = 'draft' | 'open' | 'closed' | 'merged';\n\n/** GitHub's PR review decision, as surfaced by `gh pr list --json reviewDecision`. */\nexport type ReviewDecision = 'approved' | 'changes-requested' | 'review-required';\n\n// Per-comment detail; PrInfo.unresolvedThreadCount only counts these.\nexport interface ReviewThread {\n /** GraphQL node id — what `resolveReviewThread`/`replyToReviewThread` address. */\n id: string;\n /** File path the comment is anchored to; absent for a PR-level (not diff-anchored) comment. */\n path?: string;\n line?: number;\n author?: string;\n body: string;\n}\n\n// Parsed from the JSON object a fix-review agent's prompt requires as its final line.\nexport interface FixReviewResult {\n commit: { title: string; message: string };\n addressed: string[];\n skipped: { threadId: string; why: string }[];\n}\n\nexport interface PrReviewFinding {\n path: string;\n line: number;\n body: string;\n}\n\nexport type PrReviewVerdict = 'approve' | 'comment' | 'request-changes';\n\n/** Parsed from the JSON object a pr-review agent's prompt requires as its final\n * line (see buildPrReviewPrompt). `findings` become per-line PR review comments;\n * the other fields are rendered per destination. */\nexport interface PrReviewResult {\n verdict: PrReviewVerdict;\n assessment: string;\n concerns: string[];\n findings: PrReviewFinding[];\n}\n\nexport interface FeedbackPhaseEdit {\n op: 'add' | 'reword';\n /** 1-based position in the plan's current phase list; required for \"reword\", ignored for \"add\". */\n index?: number;\n text: string;\n description?: string;\n}\n\nexport interface FeedbackEdit {\n phases?: FeedbackPhaseEdit[];\n /** The complete replacement body, when the message corrects body prose. */\n body?: string;\n}\n\n/** Parsed from the JSON object a feedback-reply agent's prompt requires as its\n * final line. The chat never creates ideas — a request is either a reply or an\n * edit on the current plan. */\nexport interface FeedbackReplyResult {\n reply: string;\n edit?: FeedbackEdit;\n /** True when the user's message answers an open question the agent asked\n * earlier in this thread — the route persists that message as a clarification. */\n answersQuestion?: boolean;\n}\n\n/** Live-resolved PR info for an entity's branch — see `core/pr.ts`. */\nexport interface PrInfo {\n number: number;\n url: string;\n state: PrState;\n /** Undefined when no review has been requested/decided, or when unresolved (offline, closed/merged PR). */\n reviewDecision?: ReviewDecision;\n /** Count of unresolved review threads. Only fetched for open/draft PRs; undefined when not fetched or unresolved. */\n unresolvedThreadCount?: number;\n /** Whether a comment or review landed after the PR's last commit — a proxy for \"since the last agent pass\" (a pass ends with a push). Only fetched for open/draft PRs. */\n hasNewCommentsSincePush?: boolean;\n /** Full SHA of the PR's current head commit — what a pr-review is scoped to (IDEA-170). */\n headSha?: string;\n headBranch?: string;\n /** A posted review's footer names this entity and `headSha` — set once the poll\n * observes it (IDEA-175). */\n scoutReviewObserved?: boolean;\n}\n\n/** Advisory info for the \"Review PR\" action — nothing here gates the button,\n * it only labels it (IDEA-174 fix 2). */\nexport interface PrReviewStatus {\n ready: boolean;\n headSha: string | null;\n lastReviewedSha: string | null;\n /** null when no CI is configured, or the PR's branch has no runs yet. */\n ciGreen: boolean | null;\n}\n\nexport interface PlanEntry {\n title: string;\n status: PlanStatus;\n kind?: PlanKind;\n /** 'fix'/'board'/'ticket' mark a structural role beyond a plain plan-bearing idea —\n * undefined for an ordinary one. Distinct from `kind`, which carries the commit type. */\n entityKind?: EntityKind;\n id?: string;\n idea?: string;\n agent?: AgentId;\n created: string;\n updated?: string;\n audited?: string;\n auditedHash?: string;\n /** Version tag (e.g. v0.13.1) that first shipped this idea, stamped from the release train. */\n released?: string;\n tags: string[];\n /** Absent renders under the virtual \"No subject\" group. */\n subject?: string;\n /** Absent means unordered — sorts after all ordered entries, by created date. */\n order?: number;\n /** See EntityEntry.issueSource. */\n issueSource?: string;\n body: string;\n phases: PhaseItem[];\n /** Post-build findings, same checkbox grammar as `phases` — see EntityEntry.fixes. */\n fixes?: PhaseItem[];\n log?: LogEntry[];\n clarifications?: LogEntry[];\n notes?: MarginNote[];\n /** Prose written against the whole finished plan, distinct from the flat `log` — an\n * agent later splits each entry into rework phases or a follow-up idea. */\n review?: LogEntry[];\n /** The entity's single ordered feedback thread — see EntityEntry.thread. */\n thread?: ThreadMessage[];\n /** Set by readEntities from which dir the file came from — see EntityEntry.archived. */\n archived?: boolean;\n pr?: PrInfo;\n /** True when `status` is a guessed fallback because GitHub's PR state\n * couldn't be resolved — see isStatusFallback. */\n statusFallback?: boolean;\n}\n\nexport interface ArchivableIdea {\n id: string;\n title: string;\n pr: PrInfo;\n}\n\n/** One open `question` thread message, resolved to the idea it's parked on. `ageDays`\n * is `Infinity` for a question ported from the pre-Thread Notes section, which never\n * carried a date — the oldest kind of unresolved question there is. */\nexport interface ParkedQuestion {\n entityId: string;\n entityTitle: string;\n text: string;\n date?: string;\n ageDays: number;\n}\n\n/** The two event-sourced notification kinds (IDEA-153) — 'completed' when a task\n * reaches done/error, 'reply' when an agent posts a non-blocking feedback thread\n * entry. Unlike ParkedQuestion (derived live from thread state), these are stored\n * as discrete events on read, so a since-resolved source can't erase them. */\nexport type StoredNotificationKind = 'completed' | 'reply';\n\nexport interface StoredNotification {\n id: string;\n kind: StoredNotificationKind;\n entityId: string;\n entityTitle: string;\n text: string;\n date: string;\n read: boolean;\n /** Only set for 'completed' — the task outcome that triggered it. */\n outcome?: 'done' | 'error';\n}\n\n/** One unified feed entry (IDEA-153) — a parked question (open→resolved, always\n * unread while it's shown at all) alongside the two stored, read/unread kinds. */\nexport type Notification = (ParkedQuestion & { kind: 'question' }) | StoredNotification;\n\nexport type ConsistencyIssueKind = 'orphan-subject' | 'title-style';\n\nexport interface ConsistencyIssue {\n kind: ConsistencyIssueKind;\n section: 'plans';\n title: string;\n message: string;\n planId?: string;\n}\n\nexport interface CommentStats {\n commentLines: number;\n sourceLines: number;\n ratio: number;\n}\n\nexport interface TasksPerWeek {\n /** ISO week, e.g. \"2026-W05\". */\n week: string;\n count: number;\n}\n\nexport interface UsagePerWeek {\n /** ISO week, e.g. \"2026-W05\". */\n week: string;\n agentMinutes: number;\n inputTokens: number;\n outputTokens: number;\n}\n\nexport interface IdeaCost {\n planId: string;\n planTitle: string;\n inputTokens: number;\n outputTokens: number;\n durationMs: number;\n}\n\nexport interface CapacityStat {\n snapshot: RateLimitSnapshot;\n capturedAt: string;\n}\n\nexport interface ProjectStats {\n generatedAt: string;\n comments: CommentStats;\n testLines: number;\n /** Null when no coverage report has been emitted yet (run `pnpm test`). */\n testCoveragePct: number | null;\n entitiesByStatus: Partial<Record<EntityStatus, number>>;\n openQuestions: number;\n decisions: number;\n tasksPerWeek: TasksPerWeek[];\n usagePerWeek: UsagePerWeek[];\n medianPhaseDurationMs: number | null;\n mostExpensiveIdeas: IdeaCost[];\n capacity: CapacityStat | null;\n}\n\nexport type IdeaKind = 'idea' | 'note';\n\nexport type IdeaStatus = 'open' | 'done' | 'dropped';\n\nexport interface IdeaEntry {\n id: string | null;\n title: string;\n body: string;\n kind?: IdeaKind;\n status?: IdeaStatus;\n /** Absent renders under the virtual \"No subject\" group. */\n subject?: string;\n /** Absent means unordered — sorts after all ordered entries, by created date. */\n order?: number;\n /** Fallback sort key for unordered entries — see `order`. Absent on view-model-only IdeaEntry literals that never enter the worklist sort. */\n created?: string;\n log?: LogEntry[];\n}\n\n// Unified entity — one file per entity, plan as an optional Phases section.\n// Supersedes PlanEntry/IdeaEntry once the migration cutover lands.\n\n/** Work classification — same Conventional-Commits values as PlanKind; `kind` renamed to `type` in the unified schema. */\nexport type EntityType = PlanKind;\n\n/** Plan lifecycle plus the note-only `open`. Notes use open → done/dropped; everything else uses the PlanStatus track. */\nexport type EntityStatus = PlanStatus | 'open';\n\n/** \"note\" never grows phases; \"fix\" is a follow-up entity linked to a done/archived\n * parent via `idea` — see entityFrontmatterSchema. \"board\" carries no phases, decomposing\n * into \"ticket\" children instead (IDEA-201) — a ticket is a full entity with its own\n * `TICKET-N` id, linked back to its board via `idea`. Omitted for a normal idea. */\nexport type EntityKind = 'note' | 'fix' | 'board' | 'ticket';\n\nexport interface EntityEntry {\n id: string;\n title: string;\n /** Absent until the entity is classified (usually when its plan is drafted). */\n type?: EntityType;\n kind?: EntityKind;\n /** Stored override, not the source of truth — see entityFrontmatterSchema. */\n status?: EntityStatus;\n /** IDEA-N backlink to the parent this fix addresses; required when kind: fix. */\n idea?: string;\n agent?: AgentId;\n created: string;\n updated?: string;\n audited?: string;\n auditedHash?: string;\n /** Version tag (e.g. v0.13.1) that first shipped this idea, stamped from the release train. */\n released?: string;\n tags: string[];\n /** Absent renders as the virtual \"No subject\" group. */\n subject?: string;\n /** Absent means unordered — sorts after all ordered entries, by created date. Overlaid\n * with the run-order rank by readEntities — see `storedOrder` for the frontmatter value. */\n order?: number;\n /** The frontmatter `order` as read from disk, before readEntities overlays the run-order\n * rank onto `order` — what a write path should persist back. See entityFileInput. */\n storedOrder?: number;\n /** The Issue.id this entity was spawned to promote, if any — how a promoted\n * issue is re-matched to its target on every read. */\n issueSource?: string;\n body: string;\n phases: PhaseItem[];\n /** Post-build findings, same checkbox grammar as `phases` — appended below Phases\n * rather than rewriting the plan's already-finished phase history. */\n fixes?: PhaseItem[];\n /** The entity's single ordered feedback thread — folds what used to be separate\n * log/clarifications/notes/review sections. */\n thread?: ThreadMessage[];\n /** Set by readEntities from which of the two scanned dirs the file came from, not the frontmatter. */\n archived?: boolean;\n /** Frontmatter keys this paper-camp doesn't recognise, carried through verbatim so a\n * write never drops a field a newer version wrote — see entityFrontmatterKnownKeys. */\n unknownFrontmatter?: Record<string, unknown>;\n}\n\n// No `id`/`status`: it only becomes a real idea if a human promotes it.\nexport interface SuggestionEntry {\n date: string;\n title: string;\n description: string;\n}\n\nexport interface RoadmapItem {\n name: string;\n description: string;\n candidates: string[];\n linked: string[];\n}\n\nexport interface RoadmapHorizon {\n title: string;\n items: RoadmapItem[];\n}\n\nexport interface Roadmap {\n goal: string;\n horizons: RoadmapHorizon[];\n standingConcerns: RoadmapItem[];\n}\n\nexport interface RoadmapLink {\n id: string;\n status: PlanStatus;\n taskRuns: number;\n pr?: PrInfo;\n released: boolean;\n}\n\nexport interface RoadmapRollup {\n total: number;\n done: number;\n}\n\nexport interface ResolvedRoadmapItem extends RoadmapItem {\n links: RoadmapLink[];\n rollup: RoadmapRollup;\n}\n\nexport interface ResolvedRoadmapHorizon {\n title: string;\n items: ResolvedRoadmapItem[];\n rollup: RoadmapRollup;\n}\n\nexport interface ResolvedRoadmap {\n goal: string;\n horizons: ResolvedRoadmapHorizon[];\n events: RoadmapEvent[];\n}\n\nexport type RoadmapEventKind = 'created' | 'task-run';\n\nexport interface RoadmapEvent {\n date: string;\n kind: RoadmapEventKind;\n entityId: string;\n horizonTitle: string;\n itemName: string;\n label: string;\n}\n\nexport interface EnvEntry {\n key: string;\n value: string;\n /** Set on GET responses when the value is withheld — secrets never reach the client. */\n isSet?: boolean;\n}\n\nexport interface AgentConfig {\n agent: AgentId;\n model?: string;\n effort?: string;\n}\n\n/** Model/effort passed to an adapter's buildArgs — the AgentConfig minus the agent id. */\nexport interface AgentRunOptions {\n model?: string;\n effort?: string;\n /** Prior claude-code session id to continue instead of starting cold; ignored by opencode. */\n resume?: string;\n}\n\n/** Maps option names to a fixed value list (renders a Select) or null (free-text or hidden). */\nexport type AgentOptionsDescriptor = Record<string, string[] | null | undefined>;\n\nexport const AGENT_OPTIONS: Record<AgentId, AgentOptionsDescriptor> = {\n 'claude-code': {\n model: ['opus', 'sonnet', 'fable', 'haiku'],\n effort: ['low', 'medium', 'high', 'xhigh', 'max'],\n },\n opencode: {\n model: null,\n },\n};\n\nexport function coerceAgentConfig(v: unknown): AgentConfig {\n const toAgent = (a: unknown): AgentId =>\n AGENT_IDS.includes(a as AgentId) ? (a as AgentId) : 'claude-code';\n if (typeof v === 'string') return { agent: toAgent(v) };\n // A missing key must not throw — that would 500 the /api/config read this protects.\n const obj = (v ?? {}) as Record<string, unknown>;\n return {\n agent: toAgent(obj.agent),\n ...(typeof obj.model === 'string' && { model: obj.model }),\n ...(typeof obj.effort === 'string' && { effort: obj.effort }),\n };\n}\n\nexport interface DefaultAgentsMap {\n phase: AgentConfig;\n planDraft: AgentConfig;\n ideaExtend: AgentConfig;\n commitSuggest: AgentConfig;\n feedback: AgentConfig;\n codeReview: AgentConfig;\n}\n\nexport const DEFAULT_AGENTS: DefaultAgentsMap = {\n phase: { agent: 'opencode' },\n planDraft: { agent: 'claude-code' },\n ideaExtend: { agent: 'claude-code' },\n commitSuggest: { agent: 'claude-code' },\n feedback: { agent: 'claude-code', model: 'sonnet', effort: 'medium' },\n codeReview: { agent: 'claude-code', model: 'opus', effort: 'high' },\n};\n\n/** A reviewer must not use the same model as the task that wrote the code (IDEA-170) — self-review rubber-stamps. */\nexport function agentConfigsEqual(a: AgentConfig, b: AgentConfig): boolean {\n return a.agent === b.agent && (a.model ?? '') === (b.model ?? '');\n}\n\n/** Toolbar segments, left to right (IDEA-128/IDEA-133); trimmed per project via `IntegrationConfig.toolbar.segments`. */\nexport const TOOLBAR_SEGMENT_IDS = ['focus', 'scout', 'desk'] as const;\n\nexport type ToolbarSegmentId = (typeof TOOLBAR_SEGMENT_IDS)[number];\n\nexport interface IntegrationToolbarConfig {\n enabled?: boolean;\n segments?: ToolbarSegmentId[];\n /** Opt-in escape hatch: without it, the vite plugin stays off when `mode` is `production`. */\n allowProduction?: boolean;\n}\n\nexport interface IntegrationConfig {\n toolbar?: IntegrationToolbarConfig;\n route?: string;\n}\n\nexport interface DeskService {\n name: string;\n cmd: string;\n port?: number;\n healthcheck?: string;\n}\n\nexport interface DeskCheck {\n name: string;\n cmd: string;\n}\n\nexport interface DeskCi {\n repo: string;\n branch?: string;\n releasePlease?: boolean;\n}\n\n/** Dev-loop manifest (IDEA-119): declared services, one-click checks, and CI/release sources. */\nexport interface DeskConfig {\n services?: DeskService[];\n checks?: DeskCheck[];\n ci?: DeskCi;\n}\n\nexport type ServiceRunStatus = 'stopped' | 'running' | 'stopping' | 'crashed';\n\nexport type ServiceHealth = 'unknown' | 'up' | 'down';\n\n/** Live runtime view of a declared service (IDEA-119), merging manifest + process state. */\nexport interface ServiceState {\n name: string;\n cmd: string;\n port?: number;\n hasHealthcheck: boolean;\n status: ServiceRunStatus;\n health: ServiceHealth;\n pid: number | null;\n startedAt: string | null;\n exitCode: number | null;\n}\n\n/** Live result of a declared one-click check (IDEA-119), mirroring the Stack panel's CheckResult. */\nexport interface DeskCheckState {\n name: string;\n cmd: string;\n status: CheckStatus;\n lastRun: string | null;\n output: string;\n}\n\nexport type CiRunStatus =\n | 'success'\n | 'failure'\n | 'in_progress'\n | 'queued'\n | 'cancelled'\n | 'unknown';\n\nexport interface CiRun {\n workflow: string;\n status: CiRunStatus;\n url: string | null;\n}\n\nexport interface ReleasePr {\n number: number;\n title: string;\n url: string;\n version: string | null;\n}\n\n/** CI & release mirror for a declared repo (IDEA-119): latest Actions runs on the tracked\n * branch, the open release-please PR, and the released vs. queued version. */\nexport interface CiReleaseState {\n repo: string;\n branch: string;\n available: boolean;\n runs: CiRun[];\n releasePr: ReleasePr | null;\n releasedVersion: string | null;\n}\n\nexport interface PaperCampConfig {\n /** Corpus format version (see CORPUS_FORMAT_VERSION) — the shape this file and the\n * entity frontmatter it sits alongside conform to, not the npm package version. */\n version: number;\n projectName: string;\n initializedAt: string;\n /** The unified-entity `idea` counter, plus the `ticket` counter (IDEA-201); the\n * per-kind plan counters are legacy, present only in pre-migration configs. */\n nextId?: Partial<Record<PlanKind, number>> & { idea?: number; ticket?: number };\n port?: number;\n defaultAgents?: DefaultAgentsMap;\n /** Derived from `ROADMAP.md` on every read (IDEA-95), never written to disk; an idea's\n * `subject` not present here renders as \"No subject\". */\n subjects?: string[];\n /** Opts out of the first-run redirect to Settings > Setup while capabilities are incomplete. */\n setupDismissed?: boolean;\n /** In-app dev toolbar (IDEA-128) — the `@dendelion/paper-camp/vite` plugin's mount, toggleable for frontend targets. */\n integration?: IntegrationConfig;\n /** Dev-loop desk manifest (IDEA-119) — declared services, checks, and CI/release sources per project. */\n desk?: DeskConfig;\n /** Project-declared commands the desk/embed can run on demand (IDEA-157). */\n commands?: {\n /** Manual build for the Stack's Build action; no universal default. */\n build?: string;\n };\n}\n\nexport type CheckStatus = 'stale' | 'running' | 'pass' | 'fail';\n\nexport interface CheckResult {\n status: CheckStatus;\n /** The shell command this check runs — empty until a build command is configured. */\n cmd: string;\n lastRun: string | null;\n output: string;\n}\n\nexport type CheckName = 'lint' | 'format' | 'test' | 'consistency' | 'build';\n\nexport type CapabilityStatus = 'ok' | 'warn' | 'missing';\n\nexport interface CapabilityResult {\n id: string;\n status: CapabilityStatus;\n detail: string;\n}\n\nexport interface AgentAuthStatus {\n loggedIn: boolean | null;\n authMethod: string | null;\n apiProvider: string | null;\n}\n\nexport type LoginRelayPhase =\n | 'starting'\n | 'awaiting-authorization'\n | 'success'\n | 'error'\n | 'cancelled';\n\nexport interface LoginRelayState {\n phase: LoginRelayPhase;\n authorizeUrl: string | null;\n error?: string;\n}\n\nexport type ServiceId = 'git' | 'gh' | `agent:${AgentId}`;\n\n/** The two plugin extension points (IDEA-193): 'external' reaches another system on\n * the user's behalf (GitHub today) with a remote-account credential; 'local' is a\n * tool the runtime drives on the machine (git, an agent CLI) with its own local\n * session, never a remote identity. */\nexport type PluginKind = 'external' | 'local';\n\n/** What a Connections row offers once its service isn't `ok` — copy a command, follow a link, or read plain instructions. */\nexport type ConnectAction =\n /** `runnable` marks a command with no placeholders and no interactive prompts — safe for the server to execute directly instead of just displaying it. */\n | { kind: 'command'; command: string; runnable?: boolean }\n | { kind: 'link'; url: string; label: string }\n | { kind: 'text'; message: string };\n\nexport interface ConnectionResult {\n id: ServiceId;\n kind: PluginKind;\n label: string;\n unlocks: string;\n status: CapabilityStatus;\n detail: string;\n authenticated: boolean | null;\n connect: ConnectAction | null;\n}\n\nexport interface MergePolicy {\n allowSquashMerge: boolean;\n allowMergeCommit: boolean;\n allowRebaseMerge: boolean;\n squashMergeCommitTitle: string;\n squashMergeCommitMessage: string;\n}\n\nexport type MergePolicyResult =\n | { status: 'ok'; repo: string; policy: MergePolicy }\n | { status: 'unavailable'; reason: string };\n\nexport interface GitStatusEntry {\n path: string;\n status: string;\n staged: boolean;\n renameSource?: string;\n}\n\nexport interface GitStashEntry {\n index: number;\n branch: string;\n message: string;\n ageDays: number;\n own: boolean;\n}\n\nexport interface GitStatusResponse {\n branch: string;\n entries: GitStatusEntry[];\n ahead: number;\n behind: number;\n diverged: boolean;\n branchHygiene: BranchHygieneStatus;\n stashPending: boolean;\n stashes: GitStashEntry[];\n}\n\nexport interface FileDiffEntry {\n path: string;\n renameSource?: string;\n status: string;\n staged: boolean;\n binary: boolean;\n additions: number;\n deletions: number;\n // 'diff' is a unified patch (tracked files); 'raw' is untracked file content, not a\n // patch; 'too-large' means `patch` is empty and the file was skipped.\n contentKind: 'diff' | 'raw' | 'too-large';\n patch: string;\n}\n\nexport type BranchHygieneStatus =\n | 'clean-on-main'\n | 'stale-merged'\n | 'stale-no-upstream'\n | 'dirty'\n | 'fine';\n\nexport interface GitLiveState {\n branch: string;\n ahead: number;\n behind: number;\n dirtyCount: number;\n}\n\n// null means the entity file doesn't exist at that ref (never drafted there yet, or archived).\nexport type PhaseState = { done: number; total: number } | null;\n\n// The ref that's ahead of the current branch's phase state, and its own count — see IDEA-171.\nexport interface StaleBaseRef {\n ref: string;\n done: number;\n total: number;\n}\n\nexport interface GitSyncFailure {\n ok: false;\n stage: 'reconcile' | 'stash-pop' | 'conflicted';\n message: string;\n stashPending: boolean;\n // Only set when stage is 'conflicted': the files a rebase left with unresolved\n // markers, for a one-click \"ask the agent to resolve\" rather than a bare error.\n conflictedFiles?: string[];\n // Only set when stage is 'conflicted': the ref the rebase targeted, plus each\n // conflicted file's last-seen content — the resolve-conflict agent's raw material.\n conflictRef?: string;\n conflictedContent?: { path: string; content: string }[];\n // Prompt for the agent recovery job (see git-sync-recovery.ts), packaged for a\n // future launch rather than thrown at the user.\n recoveryPrompt: string;\n // Only set when stage is 'conflicted': a prompt scoped to landing this one rebase\n // (see resolve-conflict-prompt.ts), launched only on explicit user confirmation.\n conflictPrompt?: string;\n}\n\nexport type GitSyncResult = { ok: true } | GitSyncFailure;\n\nexport type AgentTaskStatus = 'starting' | 'running' | 'stopping' | 'done' | 'error' | 'superseded';\n\nexport type TaskKind =\n | 'phase'\n | 'audit'\n | 'batch-reconcile'\n | 'batch-draft'\n | 'run-all'\n | 'draft'\n | 'extend'\n | 'suggest'\n | 'commit-suggest'\n | 'overlap-check'\n | 'prioritise'\n | 'sync'\n | 'reconcile'\n | 'fix-review'\n | 'resolve-conflict'\n | 'feedback'\n | 'pr-review'\n | 'issue-fix';\n\n// Persisted to papercamp/tasks.log (JSON Lines) — survives a dev-server restart,\n// unlike the in-memory task registry.\nexport interface TaskLogEntry {\n id: string;\n taskKind: TaskKind;\n planId?: string;\n planTitle: string;\n agentId: AgentId;\n startedAt: string;\n endedAt: string;\n outcome: 'done' | 'error' | 'superseded';\n reason?: string;\n usage?: RunUsage;\n phaseRuns?: PhaseRunRecord[];\n rateLimit?: RateLimitSnapshot;\n /** issue-fix only: the Issue.id (IDEA-192) this run was launched to fix, so a\n * repeat failure's thread can be reconstructed by matching entries to the issue. */\n issueId?: string;\n}\n\nexport interface RunUsage {\n durationMs: number;\n numTurns: number;\n model?: string;\n inputTokens: number;\n outputTokens: number;\n cacheCreationTokens: number;\n cacheReadTokens: number;\n costUsd: number;\n}\n\nexport interface RateLimitSnapshot {\n status: string;\n rateLimitType?: string;\n resetsAt?: number;\n overage?: boolean;\n}\n\nexport interface PhaseRunRecord {\n kind: 'phase' | 'fix';\n index: number;\n usage: RunUsage;\n}\n\nexport interface TrailHop<T> {\n reached: boolean;\n data?: T;\n}\n\nexport interface ProvenanceTrail {\n id: string;\n idea: TrailHop<{ title: string; status?: EntityStatus; type?: EntityType }>;\n phases: TrailHop<PhaseItem[]>;\n taskRuns: TrailHop<TaskLogEntry[]>;\n commits: TrailHop<string[]>;\n pr: TrailHop<PrInfo>;\n releaseLine: TrailHop<string>;\n}\n\nexport interface ReleaseNoteIdea {\n id: string;\n title: string;\n}\n\nexport interface ReleaseNoteSection {\n label: string;\n ideas: ReleaseNoteIdea[];\n}\n\nexport interface AgentTaskState {\n id: string;\n status: AgentTaskStatus;\n taskKind: TaskKind;\n planTitle: string;\n startedAt: string;\n planId?: string;\n phaseIndex?: number;\n fixAttempt?: number;\n fixAttemptCap?: number;\n ideaId?: string;\n agentId: AgentId;\n lines: string[];\n phaseAnchor?: PhaseMilestone;\n anchorEnteredAt?: number;\n lastStreamAt?: number;\n // fix-review only: prefills the commit form once the agent has reported.\n suggestedCommit?: { title: string; message: string };\n // pr-review only: the PR being reviewed, e.g. https://github.com/o/r/pull/7.\n prReviewUrl?: string;\n errorKind?: 'auth' | 'question';\n rateLimit?: RateLimitSnapshot;\n // issue-fix only: the Issue.id this run was launched to fix.\n issueId?: string;\n}\n\nexport interface OverlapVerdict {\n verdict: 'existing' | 'extend' | 'new';\n targetId: string | null;\n reasoning: string;\n}\n\n/** `why` carries one line per entry in `order`, same index — the reason for that id's placement. */\nexport interface PrioritiseVerdict {\n order: string[];\n /** One reason per `order` entry, same index. May be short if the agent\n * under-supplied after a retry — callers fall back per missing index. */\n why: string[];\n}\n\nexport interface ReconcileQueueItem {\n planId: string;\n title: string;\n before: { body: string; phases: PhaseItem[] };\n}\n\n/** The four failures IDEA-192 collects into one place: a failed agent run, a red\n * project check, a PR review that requested changes, and a git rebase/sync failure. */\nexport type IssueSourceKind = 'agent-run' | 'check' | 'pr-review' | 'sync';\n\n/** Derived from the world on every read, never a mark-read button — 'open' while a\n * collector still finds the failure, 'closed' once it doesn't or the promoted fix\n * that carries it ships. */\nexport type IssueStatus = 'open' | 'closed';\n\n/** One collected failure (IDEA-192), read as a short thread. `id` is derived from\n * `sourceKind` + `sourceKey`, so a repeat failure of the same thing resolves to the\n * same issue and continues its thread instead of raising a duplicate. */\nexport interface Issue {\n id: string;\n sourceKind: IssueSourceKind;\n /** Stable within `sourceKind` — a task's `planId`, a check's `name`, a PR's\n * `number` + `headSha`, or a sync failure's branch + target ref. */\n sourceKey: string;\n entityId?: string;\n entityTitle?: string;\n title: string;\n reason: string;\n /** Last output lines, when the source captured any (a check's command output, a\n * sync failure's conflicted files). */\n output?: string;\n /** Absent when the source carries no timestamp of its own (a live PR review decision). */\n occurredAt?: string;\n thread: ThreadMessage[];\n status: IssueStatus;\n /** Set once promoted — the id of whatever now carries the work. A promoted\n * issue closes when that entity's status reaches 'done'. */\n promotedFixId?: string;\n}\n"],"names":["PAIRING_TOKEN_HEADER","PLAN_STATUSES","PLAN_KINDS","AGENT_IDS","AGENT_LABELS","AGENT_OPTIONS","coerceAgentConfig","v","toAgent","a","obj","DEFAULT_AGENTS","agentConfigsEqual","b","TOOLBAR_SEGMENT_IDS"],"mappings":"AAEO,MAAMA,IAAuB,mBAIvBC,IAA8B;AAAA,EACzC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GASaC,IAAyB,CAAC,QAAQ,OAAO,SAAS,QAAQ,UAAU,GAEpEC,IAAY,CAAC,eAAe,UAAU,GAItCC,IAAwC;AAAA,EACnD,eAAe;AAAA,EACf,UAAU;AACZ,GA8eaC,IAAyD;AAAA,EACpE,eAAe;AAAA,IACb,OAAO,CAAC,QAAQ,UAAU,SAAS,OAAO;AAAA,IAC1C,QAAQ,CAAC,OAAO,UAAU,QAAQ,SAAS,KAAK;AAAA,EAAA;AAAA,EAElD,UAAU;AAAA,IACR,OAAO;AAAA,EAAA;AAEX;AAEO,SAASC,EAAkBC,GAAyB;AACzD,QAAMC,IAAU,CAACC,MACfN,EAAU,SAASM,CAAY,IAAKA,IAAgB;AACtD,MAAI,OAAOF,KAAM,SAAU,QAAO,EAAE,OAAOC,EAAQD,CAAC,EAAA;AAEpD,QAAMG,IAAOH,KAAK,CAAA;AAClB,SAAO;AAAA,IACL,OAAOC,EAAQE,EAAI,KAAK;AAAA,IACxB,GAAI,OAAOA,EAAI,SAAU,YAAY,EAAE,OAAOA,EAAI,MAAA;AAAA,IAClD,GAAI,OAAOA,EAAI,UAAW,YAAY,EAAE,QAAQA,EAAI,OAAA;AAAA,EAAO;AAE/D;AAWO,MAAMC,IAAmC;AAAA,EAC9C,OAAO,EAAE,OAAO,WAAA;AAAA,EAChB,WAAW,EAAE,OAAO,cAAA;AAAA,EACpB,YAAY,EAAE,OAAO,cAAA;AAAA,EACrB,eAAe,EAAE,OAAO,cAAA;AAAA,EACxB,UAAU,EAAE,OAAO,eAAe,OAAO,UAAU,QAAQ,SAAA;AAAA,EAC3D,YAAY,EAAE,OAAO,eAAe,OAAO,QAAQ,QAAQ,OAAA;AAC7D;AAGO,SAASC,EAAkBH,GAAgBI,GAAyB;AACzE,SAAOJ,EAAE,UAAUI,EAAE,UAAUJ,EAAE,SAAS,SAASI,EAAE,SAAS;AAChE;AAGO,MAAMC,IAAsB,CAAC,SAAS,SAAS,MAAM;"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dendelion/paper-camp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.21.1",
|
|
4
4
|
"description": "Local-first, AI-native project management with retro aesthetics",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"packageManager": "pnpm@10.12.1",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"dev:lib": "vite",
|
|
40
40
|
"cli": "bun src/cli/index.ts",
|
|
41
41
|
"check-types": "tsc --noEmit --incremental",
|
|
42
|
-
"lint": "biome check .",
|
|
42
|
+
"lint": "biome check . && node scripts/comment-stats.mjs",
|
|
43
43
|
"lint:write": "biome check --write .",
|
|
44
44
|
"test": "vitest run --coverage",
|
|
45
45
|
"test:watch": "vitest",
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import{j as a,ah as C,al as S,J as P,a8 as A,r as u,u as i,a5 as K,a as D,P as M,N as L,ao as z}from"./main-DP_okYxh.js";const k=(n,t)=>`${n}:${t}`;function q(n){return n.filter(t=>t.outcome==="error").map(t=>{const o=t.planId??t.id;return{id:k("agent-run",o),sourceKind:"agent-run",sourceKey:o,entityId:t.planId,entityTitle:t.planTitle,title:`${t.planTitle} — ${t.taskKind} failed`,reason:t.reason??"The run ended in error with no reason recorded.",occurredAt:t.endedAt,thread:[],status:"open"}})}function O(n){return n.filter(t=>t.status==="fail").map(t=>({id:k("check",t.name),sourceKind:"check",sourceKey:t.name,title:`"${t.name}" check is failing`,reason:`The command was \`${t.cmd}\`.`,output:t.output||void 0,occurredAt:t.lastRun??void 0,thread:[],status:"open"}))}function J(n){return n.filter(t=>{var o;return((o=t.pr)==null?void 0:o.reviewDecision)==="changes-requested"}).map(t=>{const o=t.pr,r=`${o.number}:${o.headSha??"unknown"}`;return{id:k("pr-review",r),sourceKind:"pr-review",sourceKey:r,entityId:t.id,entityTitle:t.title,title:`${t.title} — PR review requested changes`,reason:`PR #${o.number} has unresolved change requests.`,thread:[],status:"open"}})}function B(n,t){return n.map(o=>{var c;const r=t.find(s=>s.issueSource===o.id);if(r!=null&&r.id)return{...o,promotedFixId:r.id};const d=o.entityId?t.find(s=>s.id===o.entityId):void 0;return((c=d==null?void 0:d.fixes)==null?void 0:c.some(s=>s.source==="issue"&&s.text===o.title))&&(d!=null&&d.id)?{...o,promotedFixId:d.id}:o}).filter(o=>{const r=o.promotedFixId&&t.find(d=>d.id===o.promotedFixId);return!(r&&r.status==="done")})}function U(n,t){return t.filter(o=>o.issueId===n.id).sort((o,r)=>o.endedAt.localeCompare(r.endedAt)).map(o=>({kind:"log",date:o.endedAt.slice(0,10),from:"agent",text:o.outcome==="done"?"Ran a fix agent — finished.":o.outcome==="error"?`Ran a fix agent — failed: ${o.reason??"no reason recorded"}`:"Ran a fix agent — superseded by a newer run."}))}function V(n){return n.archived===!0||n.status==="done"}const Z=n=>{if(!n)return"";const t=Math.floor((Date.now()-Date.parse(n))/864e5);return Number.isFinite(t)?t<=0?"today":t===1?"1 day ago":`${t} days ago`:""},R="9999-12-31T23:59:59.999Z",_=n=>[...n].sort((t,o)=>(t.occurredAt??R).localeCompare(o.occurredAt??R)),G=({issue:n,expanded:t,onToggle:o,onOpen:r,fixing:d,fixDisabled:m,onFix:c,promoteLabel:s,promoting:x,promoteDisabled:b,onPromote:y,onOpenPromoted:j})=>{const v=async()=>{try{await c()}catch(p){alert(p.message)}},h=async()=>{try{await y()}catch(p){alert(p.message)}};return a.jsxs("div",{className:"border-b border-black/10 last:border-b-0",children:[a.jsxs("div",{className:"flex items-center gap-3 py-2.5",children:[r&&a.jsx("button",{type:"button",onClick:r,"aria-label":`Open ${n.entityTitle}`,className:"bg-transparent border-none p-0 cursor-pointer",children:a.jsx(C,{id:n.entityId})}),a.jsx("button",{type:"button",onClick:o,"aria-expanded":t,className:"flex-1 min-w-0 truncate text-left bg-transparent border-none p-0 cursor-pointer",children:n.title}),a.jsx("span",{className:"opacity-50 text-xs whitespace-nowrap",children:Z(n.occurredAt)})]}),t&&a.jsxs("div",{className:"flex flex-col gap-3 pb-4",children:[a.jsx("p",{children:n.reason}),n.output&&a.jsx("pre",{className:"max-h-48 overflow-auto whitespace-pre-wrap text-left font-mono text-2xs opacity-70",children:n.output}),n.thread.length>0&&a.jsx(S,{messages:n.thread,undo:null,undoing:!1,onUndo:()=>{}}),a.jsx("div",{className:"flex items-center gap-3",children:n.promotedFixId?a.jsxs(P,{size:"small",onClick:j,children:["View ",n.promotedFixId]}):a.jsxs(a.Fragment,{children:[a.jsx(P,{size:"small",onClick:v,disabled:m,children:d?"Fixing…":"Fix it here"}),d&&a.jsx(A,{size:"small",label:"Agent fixing…"}),a.jsx(P,{size:"small",onClick:h,disabled:b,children:x?"Promoting…":s}),x&&a.jsx(A,{size:"small",label:"Promoting…"})]})})]})]})},H=(n,t)=>{const o=n.entityId?t.find(r=>r.id===n.entityId):void 0;return o?V(o)?"Promote to fix":`Add to ${o.id}'s fixes`:"Promote to idea"},W=()=>{var $;const[n,t]=u.useState(null),[o,r]=u.useState(null),d=i(e=>e.taskLog),m=i(e=>e.taskLogLoading),c=i(e=>e.loadTaskLog),s=i(e=>e.plans),x=i(e=>e.ideaEntries),b=i(e=>e.agentStatus),y=i(e=>e.launchIssueFix),j=i(e=>e.loadPlans),v=i(e=>e.loadIdeas),{checks:h}=K(),p=D();u.useEffect(()=>{c()},[c]);const F=u.useMemo(()=>((s==null?void 0:s.entries)??[]).filter(e=>e.id!=null),[s]),g=u.useMemo(()=>[...F,...x].filter(e=>e.id!=null),[F,x]),I=u.useMemo(()=>{const e=_([...q(d),...O(h),...J(g)]);return B(e,g).map(l=>({...l,thread:U(l,d)}))},[d,h,g]),T=(e,l)=>{s!=null&&s.entries.some(f=>f.id===e||f.title===l)?p({to:"/plans/$planId",params:{planId:L(e,l)}}):p({to:"/ideas/$ideaId",params:{ideaId:L(e,l)}})},E=async e=>{r(e.id);try{await z(e),await Promise.all([j(),v()])}finally{r(null)}},N=($=b.find(e=>e.taskKind==="issue-fix"&&e.status!=="done"&&e.status!=="error"&&e.status!=="superseded"))==null?void 0:$.issueId;return a.jsxs("div",{children:[a.jsx(M,{children:"Issues"}),a.jsx("p",{className:"opacity-50 mb-6",children:"Every failing agent run, red check, and PR review, oldest first."}),m&&I.length===0&&a.jsx("p",{className:"opacity-50",children:"Loading…"}),!m&&I.length===0&&a.jsx("p",{className:"opacity-50",children:"Nothing broken right now."}),I.length>0&&a.jsx("div",{className:"flex flex-col",children:I.map(e=>{var f;const l=e.promotedFixId?((f=g.find(w=>w.id===e.promotedFixId))==null?void 0:f.title)??e.promotedFixId:void 0;return a.jsx(G,{issue:e,expanded:n===e.id,onToggle:()=>t(w=>w===e.id?null:e.id),onOpen:e.entityId&&e.entityTitle?()=>T(e.entityId,e.entityTitle):void 0,fixing:N===e.id,fixDisabled:N!==void 0,onFix:()=>y(e.id,e.title,e.reason,e.output),promoteLabel:H(e,g),promoting:o===e.id,promoteDisabled:o!==null,onPromote:()=>E(e),onOpenPromoted:e.promotedFixId?()=>T(e.promotedFixId,l):void 0},e.id)})})]})};export{W as IssuesPage};
|
|
2
|
-
//# sourceMappingURL=index-3pzg7Iyw.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index-3pzg7Iyw.js","sources":["../../../src/core/issues.ts","../../../src/core/status/status.ts","../../../src/app/features/issues/helpers.ts","../../../src/app/features/issues/issue-row.tsx","../../../src/app/features/issues/issues-page.tsx"],"sourcesContent":["import type {\n DeskCheckState,\n EntityStatus,\n GitSyncFailure,\n Issue,\n PhaseItem,\n PrInfo,\n TaskLogEntry,\n ThreadMessage,\n} from '../types/index';\n\nconst issueId = (sourceKind: Issue['sourceKind'], sourceKey: string): string =>\n `${sourceKind}:${sourceKey}`;\n\n/** A failed agent run (`outcome: 'error'` in tasks.log), keyed by the plan it ran\n * against so a repeat failure on the same plan continues one issue. Runs with no\n * `planId` (e.g. a batch task) key off their own task id instead. */\nexport function collectAgentRunIssues(taskLog: TaskLogEntry[]): Issue[] {\n return taskLog\n .filter((entry) => entry.outcome === 'error')\n .map((entry) => {\n const sourceKey = entry.planId ?? entry.id;\n return {\n id: issueId('agent-run', sourceKey),\n sourceKind: 'agent-run',\n sourceKey,\n entityId: entry.planId,\n entityTitle: entry.planTitle,\n title: `${entry.planTitle} — ${entry.taskKind} failed`,\n reason: entry.reason ?? 'The run ended in error with no reason recorded.',\n occurredAt: entry.endedAt,\n thread: [],\n status: 'open',\n };\n });\n}\n\n/** A red project check (Quality/Tests/Consistency — Docs keeps its own browse flow\n * and is excluded, same carve-out as `buildCheckFixes`), keyed by check name. */\nexport function collectCheckIssues(checks: DeskCheckState[]): Issue[] {\n return checks\n .filter((check) => check.status === 'fail')\n .map((check) => ({\n id: issueId('check', check.name),\n sourceKind: 'check',\n sourceKey: check.name,\n title: `\"${check.name}\" check is failing`,\n reason: `The command was \\`${check.cmd}\\`.`,\n output: check.output || undefined,\n occurredAt: check.lastRun ?? undefined,\n thread: [],\n status: 'open',\n }));\n}\n\n/** A PR review that requested changes, keyed by PR number + head sha so a fresh\n * push that gets re-reviewed continues the same issue rather than a new one. */\nexport function collectPrReviewIssues(\n entities: { id: string; title: string; pr?: PrInfo }[],\n): Issue[] {\n return entities\n .filter((entity) => entity.pr?.reviewDecision === 'changes-requested')\n .map((entity) => {\n const pr = entity.pr as PrInfo;\n const sourceKey = `${pr.number}:${pr.headSha ?? 'unknown'}`;\n return {\n id: issueId('pr-review', sourceKey),\n sourceKind: 'pr-review',\n sourceKey,\n entityId: entity.id,\n entityTitle: entity.title,\n title: `${entity.title} — PR review requested changes`,\n reason: `PR #${pr.number} has unresolved change requests.`,\n thread: [],\n status: 'open',\n };\n });\n}\n\n/** A git rebase/sync failure on the currently checked-out branch, keyed by branch +\n * the ref it failed to reconcile against so a repeat failure of the same rebase\n * continues one issue. */\nexport function collectSyncIssues(\n failure: GitSyncFailure | undefined,\n branch: string,\n entity?: { id: string; title: string },\n): Issue[] {\n if (!failure) return [];\n const sourceKey = `${branch}:${failure.conflictRef ?? failure.stage}`;\n return [\n {\n id: issueId('sync', sourceKey),\n sourceKind: 'sync',\n sourceKey,\n entityId: entity?.id,\n entityTitle: entity?.title,\n title: `${branch} failed to sync`,\n reason: failure.message,\n output: failure.conflictedFiles?.length\n ? `Conflicted files: ${failure.conflictedFiles.join(', ')}`\n : undefined,\n thread: [],\n status: 'open',\n },\n ];\n}\n\n/** Reconciles freshly-collected issues (every one currently detected as failing, so\n * always 'open') against the existing set, keyed by `id`. A repeat failure of the\n * same thing replaces that issue's title/reason/output in place, preserving its\n * position and its thread. An existing issue absent from `fresh` has stopped\n * failing, so it closes — unless it was promoted to a fix entity that hasn't\n * shipped yet (`fixEntityStatus` reports its status), which keeps it open until\n * that entity reaches 'done'. Closure is derived here, never a mark-read button. */\nexport function reconcileIssues(\n existing: Issue[],\n fresh: Issue[],\n fixEntityStatus: (fixId: string) => EntityStatus | undefined = () => undefined,\n): Issue[] {\n const freshById = new Map(fresh.map((issue) => [issue.id, issue]));\n const merged = existing.map((issue): Issue => {\n if (issue.promotedFixId && fixEntityStatus(issue.promotedFixId) === 'done') {\n freshById.delete(issue.id);\n return { ...issue, status: 'closed' };\n }\n const next = freshById.get(issue.id);\n if (!next) return { ...issue, status: 'closed' };\n freshById.delete(issue.id);\n return { ...next, thread: issue.thread, promotedFixId: issue.promotedFixId };\n });\n return [...merged, ...freshById.values()];\n}\n\n/** An entity Promote (IDEA-192 phase 6) may have created or amended — a spawned\n * fix/idea entity stamps `issueSource`; an open parent gets a `source: 'issue'`\n * item appended to its inline Fixes list instead. */\ntype PromotionTarget = {\n id?: string;\n status?: EntityStatus;\n issueSource?: string;\n fixes?: PhaseItem[];\n};\n\n/** Matches every issue against the corpus for a prior Promote (IDEA-192 phase 6),\n * re-derived from disk on every read since issues carry no store of their own —\n * the promotion survives a reload because git holds it, not a stored issue. Once\n * matched, an issue whose target has shipped (`status: 'done'`) closes, even if\n * its original source is still detected as failing. */\nexport function applyPromotions(issues: Issue[], entities: PromotionTarget[]): Issue[] {\n return issues\n .map((issue): Issue => {\n const spawned = entities.find((e) => e.issueSource === issue.id);\n if (spawned?.id) return { ...issue, promotedFixId: spawned.id };\n const parent = issue.entityId ? entities.find((e) => e.id === issue.entityId) : undefined;\n const appended = parent?.fixes?.some((f) => f.source === 'issue' && f.text === issue.title);\n return appended && parent?.id ? { ...issue, promotedFixId: parent.id } : issue;\n })\n .filter((issue) => {\n const target = issue.promotedFixId && entities.find((e) => e.id === issue.promotedFixId);\n return !(target && target.status === 'done');\n });\n}\n\n/** \"Fix it here\" (IDEA-192) launches an `issue-fix` task carrying the issue's id;\n * this reconstructs that issue's thread from every tasks.log entry carrying it,\n * oldest first, so a repeat fix attempt reads as a continuation, not a fresh mystery. */\nexport function issueThreadFromTaskLog(issue: Issue, taskLog: TaskLogEntry[]): ThreadMessage[] {\n return taskLog\n .filter((entry) => entry.issueId === issue.id)\n .sort((a, b) => a.endedAt.localeCompare(b.endedAt))\n .map((entry) => ({\n kind: 'log',\n date: entry.endedAt.slice(0, 10),\n from: 'agent',\n text:\n entry.outcome === 'done'\n ? 'Ran a fix agent — finished.'\n : entry.outcome === 'error'\n ? `Ran a fix agent — failed: ${entry.reason ?? 'no reason recorded'}`\n : 'Ran a fix agent — superseded by a newer run.',\n }));\n}\n","import type { EntityKind, EntityStatus, PhaseItem, PrInfo } from '../../types/index';\n\nexport interface StatusDerivationInput {\n kind?: EntityKind;\n status?: EntityStatus;\n phases: PhaseItem[];\n fixes?: PhaseItem[];\n archived?: boolean;\n}\n\nexport type ArchivabilityInput = StatusDerivationInput;\n\n// The one predicate the fix/reopen boundary (IDEA-187) keys off everywhere: a done\n// or archived entity's own file is read-only, so new work always spawns its own\n// linked fix entity instead. Reads the stored status, not the derived one — a\n// merged-but-not-yet-archived idea only counts once something has actually stamped\n// `status: done` on it (readEntities sets `archived` from the file's directory).\nexport function isClosedEntity(entity: { status?: EntityStatus; archived?: boolean }): boolean {\n return entity.archived === true || entity.status === 'done';\n}\n\nfunction allChecked(entity: StatusDerivationInput): boolean {\n const phasesDone = entity.phases.length > 0 && entity.phases.every((p) => p.done);\n const fixesDone = (entity.fixes ?? []).every((f) => f.done);\n return phasesDone && fixesDone;\n}\n\n// Keys off the PR (matched by id), not a local branch: canonical across clones\n// and survives the branch being deleted after merge.\nexport function deriveStatus(\n entity: StatusDerivationInput,\n pr: PrInfo | undefined,\n prLookupResolved: boolean,\n hasMainActivity = false,\n): EntityStatus | undefined {\n if (entity.kind === 'note') return entity.status;\n // `dropped` can't be derived (abandonment leaves no trace), so a stored one always wins.\n if (entity.status === 'dropped') return entity.status;\n // An entity in archive/ is closed by definition, regardless of PR lookup state.\n if (entity.archived) return 'done';\n if (pr) {\n if (pr.state === 'merged') return allChecked(entity) ? 'done' : 'planned';\n if (pr.state === 'closed') return 'dropped';\n return allChecked(entity) ? 'review' : 'in-progress';\n }\n if (!prLookupResolved) {\n // GitHub unreachable — trust the stored override, else a phases-only guess.\n return entity.status ?? (entity.phases.length > 0 ? 'planned' : 'idea');\n }\n // Confirmed no PR, but a stored `review`/`done` (e.g. direct-to-main work, or an\n // unmatchable legacy PR) still wins over a planned/in-progress guess.\n if (entity.status === 'review' || entity.status === 'done') return entity.status;\n if (hasMainActivity && entity.phases.length > 0) {\n return allChecked(entity) ? 'review' : 'in-progress';\n }\n return entity.phases.length > 0 ? 'planned' : 'idea';\n}\n\n// Mirrors deriveStatus's guards up to its `!prLookupResolved` branch: true exactly when\n// that branch is the one producing the status, i.e. GitHub was unreachable and nothing\n// derivable (archived/dropped/a resolved PR) overrode the guess.\nexport function isStatusFallback(\n entity: StatusDerivationInput,\n pr: PrInfo | undefined,\n prLookupResolved: boolean,\n): boolean {\n if (entity.kind === 'note') return false;\n if (entity.status === 'dropped') return false;\n if (entity.archived) return false;\n if (pr) return false;\n return !prLookupResolved;\n}\n\n// A merged PR can still derive to `dropped` (a stored override wins over the PR — see\n// deriveStatus), so this re-derives rather than trusting `pr.state === 'merged'` alone.\nexport function isArchivable(entity: ArchivabilityInput, pr: PrInfo | undefined): boolean {\n if (entity.kind === 'note' || entity.archived || pr?.state !== 'merged') return false;\n const status = deriveStatus(entity, pr, true);\n return status === 'review' || status === 'done';\n}\n","export const formatIssueAge = (occurredAt: string | undefined): string => {\n if (!occurredAt) return '';\n const ageDays = Math.floor((Date.now() - Date.parse(occurredAt)) / 86_400_000);\n if (!Number.isFinite(ageDays)) return '';\n if (ageDays <= 0) return 'today';\n return ageDays === 1 ? '1 day ago' : `${ageDays} days ago`;\n};\n\n// Missing timestamps (a live PR review decision) sort last, not first.\nconst NO_TIMESTAMP = '9999-12-31T23:59:59.999Z';\nexport const sortIssuesByAge = <T extends { occurredAt?: string }>(issues: T[]): T[] =>\n [...issues].sort((a, b) =>\n (a.occurredAt ?? NO_TIMESTAMP).localeCompare(b.occurredAt ?? NO_TIMESTAMP),\n );\n","import { FeedbackThread, PlanIdStamp } from '@/app/features/plans/components';\nimport type { Issue } from '@/types/index';\nimport { Button, Spinner } from '@dendelion/paper-ui';\nimport { formatIssueAge } from './helpers';\n\ninterface IssueRowProps {\n issue: Issue;\n expanded: boolean;\n onToggle: () => void;\n onOpen?: () => void;\n fixing: boolean;\n fixDisabled: boolean;\n onFix: () => Promise<void>;\n promoteLabel: string;\n promoting: boolean;\n promoteDisabled: boolean;\n onPromote: () => Promise<void>;\n onOpenPromoted?: () => void;\n}\n\nexport const IssueRow = ({\n issue,\n expanded,\n onToggle,\n onOpen,\n fixing,\n fixDisabled,\n onFix,\n promoteLabel,\n promoting,\n promoteDisabled,\n onPromote,\n onOpenPromoted,\n}: IssueRowProps) => {\n const handleFix = async () => {\n try {\n await onFix();\n } catch (err) {\n alert((err as Error).message);\n }\n };\n\n const handlePromote = async () => {\n try {\n await onPromote();\n } catch (err) {\n alert((err as Error).message);\n }\n };\n\n return (\n <div className=\"border-b border-black/10 last:border-b-0\">\n <div className=\"flex items-center gap-3 py-2.5\">\n {onOpen && (\n <button\n type=\"button\"\n onClick={onOpen}\n aria-label={`Open ${issue.entityTitle}`}\n className=\"bg-transparent border-none p-0 cursor-pointer\"\n >\n <PlanIdStamp id={issue.entityId} />\n </button>\n )}\n <button\n type=\"button\"\n onClick={onToggle}\n aria-expanded={expanded}\n className=\"flex-1 min-w-0 truncate text-left bg-transparent border-none p-0 cursor-pointer\"\n >\n {issue.title}\n </button>\n <span className=\"opacity-50 text-xs whitespace-nowrap\">\n {formatIssueAge(issue.occurredAt)}\n </span>\n </div>\n {expanded && (\n <div className=\"flex flex-col gap-3 pb-4\">\n <p>{issue.reason}</p>\n {issue.output && (\n <pre className=\"max-h-48 overflow-auto whitespace-pre-wrap text-left font-mono text-2xs opacity-70\">\n {issue.output}\n </pre>\n )}\n {issue.thread.length > 0 && (\n <FeedbackThread messages={issue.thread} undo={null} undoing={false} onUndo={() => {}} />\n )}\n <div className=\"flex items-center gap-3\">\n {issue.promotedFixId ? (\n <Button size=\"small\" onClick={onOpenPromoted}>\n View {issue.promotedFixId}\n </Button>\n ) : (\n <>\n <Button size=\"small\" onClick={handleFix} disabled={fixDisabled}>\n {fixing ? 'Fixing…' : 'Fix it here'}\n </Button>\n {fixing && <Spinner size=\"small\" label=\"Agent fixing…\" />}\n <Button size=\"small\" onClick={handlePromote} disabled={promoteDisabled}>\n {promoting ? 'Promoting…' : promoteLabel}\n </Button>\n {promoting && <Spinner size=\"small\" label=\"Promoting…\" />}\n </>\n )}\n </div>\n </div>\n )}\n </div>\n );\n};\n","import { PageTitle } from '@/app/components/page-title';\nimport { entityRouteParam } from '@/app/hooks';\nimport { useDeskChecks } from '@/app/hooks/use-desk-checks';\nimport { promoteIssue } from '@/app/services/agent-api';\nimport { useAppStore } from '@/app/stores/app-store';\nimport {\n applyPromotions,\n collectAgentRunIssues,\n collectCheckIssues,\n collectPrReviewIssues,\n issueThreadFromTaskLog,\n} from '@/core/issues';\nimport { isClosedEntity } from '@/core/status';\nimport type { EntityStatus, Issue, PlanEntry } from '@/types/index';\nimport { useNavigate } from '@tanstack/react-router';\nimport { useEffect, useMemo, useState } from 'react';\nimport { sortIssuesByAge } from './helpers';\nimport { IssueRow } from './issue-row';\n\nconst promoteLabel = (\n issue: Issue,\n entities: { id: string; status?: EntityStatus; archived?: boolean }[],\n): string => {\n const parent = issue.entityId ? entities.find((p) => p.id === issue.entityId) : undefined;\n if (!parent) return 'Promote to idea';\n return isClosedEntity(parent) ? 'Promote to fix' : `Add to ${parent.id}'s fixes`;\n};\n\nexport const IssuesPage = () => {\n const [expandedId, setExpandedId] = useState<string | null>(null);\n const [promotingId, setPromotingId] = useState<string | null>(null);\n const taskLog = useAppStore((s) => s.taskLog);\n const taskLogLoading = useAppStore((s) => s.taskLogLoading);\n const loadTaskLog = useAppStore((s) => s.loadTaskLog);\n const plans = useAppStore((s) => s.plans);\n const ideaEntries = useAppStore((s) => s.ideaEntries);\n const agentStatus = useAppStore((s) => s.agentStatus);\n const launchIssueFix = useAppStore((s) => s.launchIssueFix);\n const loadPlans = useAppStore((s) => s.loadPlans);\n const loadIdeas = useAppStore((s) => s.loadIdeas);\n const { checks } = useDeskChecks();\n const navigate = useNavigate();\n\n useEffect(() => {\n loadTaskLog();\n }, [loadTaskLog]);\n\n const planEntities = useMemo(\n () => (plans?.entries ?? []).filter((p): p is PlanEntry & { id: string } => p.id != null),\n [plans],\n );\n\n const entities = useMemo(\n () =>\n [...planEntities, ...ideaEntries].filter(\n (entity): entity is typeof entity & { id: string } => entity.id != null,\n ),\n [planEntities, ideaEntries],\n );\n\n const issues = useMemo(() => {\n const collected = sortIssuesByAge([\n ...collectAgentRunIssues(taskLog),\n ...collectCheckIssues(checks),\n ...collectPrReviewIssues(entities),\n ]);\n return applyPromotions(collected, entities).map((issue) => ({\n ...issue,\n thread: issueThreadFromTaskLog(issue, taskLog),\n }));\n }, [taskLog, checks, entities]);\n\n const openEntity = (id: string, title: string) => {\n if (plans?.entries.some((p) => p.id === id || p.title === title)) {\n navigate({ to: '/plans/$planId', params: { planId: entityRouteParam(id, title) } });\n } else {\n navigate({ to: '/ideas/$ideaId', params: { ideaId: entityRouteParam(id, title) } });\n }\n };\n\n const handlePromote = async (issue: Issue) => {\n setPromotingId(issue.id);\n try {\n await promoteIssue(issue);\n await Promise.all([loadPlans(), loadIdeas()]);\n } finally {\n setPromotingId(null);\n }\n };\n\n const fixingIssueId = agentStatus.find(\n (t) =>\n t.taskKind === 'issue-fix' &&\n t.status !== 'done' &&\n t.status !== 'error' &&\n t.status !== 'superseded',\n )?.issueId;\n\n return (\n <div>\n <PageTitle>Issues</PageTitle>\n <p className=\"opacity-50 mb-6\">\n Every failing agent run, red check, and PR review, oldest first.\n </p>\n {taskLogLoading && issues.length === 0 && <p className=\"opacity-50\">Loading…</p>}\n {!taskLogLoading && issues.length === 0 && (\n <p className=\"opacity-50\">Nothing broken right now.</p>\n )}\n {issues.length > 0 && (\n <div className=\"flex flex-col\">\n {issues.map((issue) => {\n const promotedTitle = issue.promotedFixId\n ? (entities.find((p) => p.id === issue.promotedFixId)?.title ?? issue.promotedFixId)\n : undefined;\n return (\n <IssueRow\n key={issue.id}\n issue={issue}\n expanded={expandedId === issue.id}\n onToggle={() => setExpandedId((cur) => (cur === issue.id ? null : issue.id))}\n onOpen={\n issue.entityId && issue.entityTitle\n ? () => openEntity(issue.entityId as string, issue.entityTitle as string)\n : undefined\n }\n fixing={fixingIssueId === issue.id}\n fixDisabled={fixingIssueId !== undefined}\n onFix={() => launchIssueFix(issue.id, issue.title, issue.reason, issue.output)}\n promoteLabel={promoteLabel(issue, entities)}\n promoting={promotingId === issue.id}\n promoteDisabled={promotingId !== null}\n onPromote={() => handlePromote(issue)}\n onOpenPromoted={\n issue.promotedFixId\n ? () => openEntity(issue.promotedFixId as string, promotedTitle as string)\n : undefined\n }\n />\n );\n })}\n </div>\n )}\n </div>\n );\n};\n"],"names":["issueId","sourceKind","sourceKey","collectAgentRunIssues","taskLog","entry","collectCheckIssues","checks","check","collectPrReviewIssues","entities","entity","_a","pr","applyPromotions","issues","issue","spawned","e","parent","f","target","issueThreadFromTaskLog","a","b","isClosedEntity","formatIssueAge","occurredAt","ageDays","NO_TIMESTAMP","sortIssuesByAge","IssueRow","expanded","onToggle","onOpen","fixing","fixDisabled","onFix","promoteLabel","promoting","promoteDisabled","onPromote","onOpenPromoted","handleFix","err","handlePromote","jsxs","jsx","PlanIdStamp","FeedbackThread","Button","Fragment","Spinner","p","IssuesPage","expandedId","setExpandedId","useState","promotingId","setPromotingId","useAppStore","s","taskLogLoading","loadTaskLog","plans","ideaEntries","agentStatus","launchIssueFix","loadPlans","loadIdeas","useDeskChecks","navigate","useNavigate","useEffect","planEntities","useMemo","collected","openEntity","id","title","entityRouteParam","promoteIssue","fixingIssueId","t","PageTitle","promotedTitle","cur"],"mappings":"yHAWA,MAAMA,EAAU,CAACC,EAAiCC,IAChD,GAAGD,CAAU,IAAIC,CAAS,GAKrB,SAASC,EAAsBC,EAAkC,CACtE,OAAOA,EACJ,OAAQC,GAAUA,EAAM,UAAY,OAAO,EAC3C,IAAKA,GAAU,CACd,MAAMH,EAAYG,EAAM,QAAUA,EAAM,GACxC,MAAO,CACL,GAAIL,EAAQ,YAAaE,CAAS,EAClC,WAAY,YACZ,UAAAA,EACA,SAAUG,EAAM,OAChB,YAAaA,EAAM,UACnB,MAAO,GAAGA,EAAM,SAAS,MAAMA,EAAM,QAAQ,UAC7C,OAAQA,EAAM,QAAU,kDACxB,WAAYA,EAAM,QAClB,OAAQ,CAAA,EACR,OAAQ,MAAA,CAEZ,CAAC,CACL,CAIO,SAASC,EAAmBC,EAAmC,CACpE,OAAOA,EACJ,OAAQC,GAAUA,EAAM,SAAW,MAAM,EACzC,IAAKA,IAAW,CACf,GAAIR,EAAQ,QAASQ,EAAM,IAAI,EAC/B,WAAY,QACZ,UAAWA,EAAM,KACjB,MAAO,IAAIA,EAAM,IAAI,qBACrB,OAAQ,qBAAqBA,EAAM,GAAG,MACtC,OAAQA,EAAM,QAAU,OACxB,WAAYA,EAAM,SAAW,OAC7B,OAAQ,CAAA,EACR,OAAQ,MAAA,EACR,CACN,CAIO,SAASC,EACdC,EACS,CACT,OAAOA,EACJ,OAAQC,UAAW,QAAAC,EAAAD,EAAO,KAAP,YAAAC,EAAW,kBAAmB,oBAAmB,EACpE,IAAKD,GAAW,CACf,MAAME,EAAKF,EAAO,GACZT,EAAY,GAAGW,EAAG,MAAM,IAAIA,EAAG,SAAW,SAAS,GACzD,MAAO,CACL,GAAIb,EAAQ,YAAaE,CAAS,EAClC,WAAY,YACZ,UAAAA,EACA,SAAUS,EAAO,GACjB,YAAaA,EAAO,MACpB,MAAO,GAAGA,EAAO,KAAK,iCACtB,OAAQ,OAAOE,EAAG,MAAM,mCACxB,OAAQ,CAAA,EACR,OAAQ,MAAA,CAEZ,CAAC,CACL,CAuEO,SAASC,EAAgBC,EAAiBL,EAAsC,CACrF,OAAOK,EACJ,IAAKC,GAAiB,OACrB,MAAMC,EAAUP,EAAS,KAAMQ,GAAMA,EAAE,cAAgBF,EAAM,EAAE,EAC/D,GAAIC,GAAA,MAAAA,EAAS,GAAI,MAAO,CAAE,GAAGD,EAAO,cAAeC,EAAQ,EAAA,EAC3D,MAAME,EAASH,EAAM,SAAWN,EAAS,KAAMQ,GAAMA,EAAE,KAAOF,EAAM,QAAQ,EAAI,OAEhF,QADiBJ,EAAAO,GAAA,YAAAA,EAAQ,QAAR,YAAAP,EAAe,KAAMQ,GAAMA,EAAE,SAAW,SAAWA,EAAE,OAASJ,EAAM,UAClEG,GAAA,MAAAA,EAAQ,IAAK,CAAE,GAAGH,EAAO,cAAeG,EAAO,EAAA,EAAOH,CAC3E,CAAC,EACA,OAAQA,GAAU,CACjB,MAAMK,EAASL,EAAM,eAAiBN,EAAS,KAAMQ,GAAMA,EAAE,KAAOF,EAAM,aAAa,EACvF,MAAO,EAAEK,GAAUA,EAAO,SAAW,OACvC,CAAC,CACL,CAKO,SAASC,EAAuBN,EAAcZ,EAA0C,CAC7F,OAAOA,EACJ,OAAQC,GAAUA,EAAM,UAAYW,EAAM,EAAE,EAC5C,KAAK,CAACO,EAAGC,IAAMD,EAAE,QAAQ,cAAcC,EAAE,OAAO,CAAC,EACjD,IAAKnB,IAAW,CACf,KAAM,MACN,KAAMA,EAAM,QAAQ,MAAM,EAAG,EAAE,EAC/B,KAAM,QACN,KACEA,EAAM,UAAY,OACd,8BACAA,EAAM,UAAY,QAChB,6BAA6BA,EAAM,QAAU,oBAAoB,GACjE,8CAAA,EACR,CACN,CCpKO,SAASoB,EAAed,EAAgE,CAC7F,OAAOA,EAAO,WAAa,IAAQA,EAAO,SAAW,MACvD,CCnBO,MAAMe,EAAkBC,GAA2C,CACxE,GAAI,CAACA,EAAY,MAAO,GACxB,MAAMC,EAAU,KAAK,OAAO,KAAK,IAAA,EAAQ,KAAK,MAAMD,CAAU,GAAK,KAAU,EAC7E,OAAK,OAAO,SAASC,CAAO,EACxBA,GAAW,EAAU,QAClBA,IAAY,EAAI,YAAc,GAAGA,CAAO,YAFT,EAGxC,EAGMC,EAAe,2BACRC,EAAsDf,GACjE,CAAC,GAAGA,CAAM,EAAE,KAAK,CAACQ,EAAGC,KAClBD,EAAE,YAAcM,GAAc,cAAcL,EAAE,YAAcK,CAAY,CAC3E,ECOWE,EAAW,CAAC,CACvB,MAAAf,EACA,SAAAgB,EACA,SAAAC,EACA,OAAAC,EACA,OAAAC,EACA,YAAAC,EACA,MAAAC,EACA,aAAAC,EACA,UAAAC,EACA,gBAAAC,EACA,UAAAC,EACA,eAAAC,CACF,IAAqB,CACnB,MAAMC,EAAY,SAAY,CAC5B,GAAI,CACF,MAAMN,EAAA,CACR,OAASO,EAAK,CACZ,MAAOA,EAAc,OAAO,CAC9B,CACF,EAEMC,EAAgB,SAAY,CAChC,GAAI,CACF,MAAMJ,EAAA,CACR,OAASG,EAAK,CACZ,MAAOA,EAAc,OAAO,CAC9B,CACF,EAEA,OACEE,EAAAA,KAAC,MAAA,CAAI,UAAU,2CACb,SAAA,CAAAA,EAAAA,KAAC,MAAA,CAAI,UAAU,iCACZ,SAAA,CAAAZ,GACCa,EAAAA,IAAC,SAAA,CACC,KAAK,SACL,QAASb,EACT,aAAY,QAAQlB,EAAM,WAAW,GACrC,UAAU,gDAEV,SAAA+B,EAAAA,IAACC,EAAA,CAAY,GAAIhC,EAAM,QAAA,CAAU,CAAA,CAAA,EAGrC+B,EAAAA,IAAC,SAAA,CACC,KAAK,SACL,QAASd,EACT,gBAAeD,EACf,UAAU,kFAET,SAAAhB,EAAM,KAAA,CAAA,QAER,OAAA,CAAK,UAAU,uCACb,SAAAU,EAAeV,EAAM,UAAU,CAAA,CAClC,CAAA,EACF,EACCgB,GACCc,EAAAA,KAAC,MAAA,CAAI,UAAU,2BACb,SAAA,CAAAC,EAAAA,IAAC,IAAA,CAAG,WAAM,MAAA,CAAO,EAChB/B,EAAM,QACL+B,EAAAA,IAAC,OAAI,UAAU,qFACZ,WAAM,OACT,EAED/B,EAAM,OAAO,OAAS,SACpBiC,EAAA,CAAe,SAAUjC,EAAM,OAAQ,KAAM,KAAM,QAAS,GAAO,OAAQ,IAAM,CAAC,EAAG,EAExF+B,EAAAA,IAAC,MAAA,CAAI,UAAU,0BACZ,SAAA/B,EAAM,cACL8B,EAAAA,KAACI,EAAA,CAAO,KAAK,QAAQ,QAASR,EAAgB,SAAA,CAAA,QACtC1B,EAAM,aAAA,CAAA,CACd,EAEA8B,EAAAA,KAAAK,EAAAA,SAAA,CACE,SAAA,CAAAJ,EAAAA,IAACG,EAAA,CAAO,KAAK,QAAQ,QAASP,EAAW,SAAUP,EAChD,SAAAD,EAAS,UAAY,aAAA,CACxB,EACCA,GAAUY,EAAAA,IAACK,EAAA,CAAQ,KAAK,QAAQ,MAAM,gBAAgB,EACvDL,EAAAA,IAACG,EAAA,CAAO,KAAK,QAAQ,QAASL,EAAe,SAAUL,EACpD,SAAAD,EAAY,aAAeD,CAAA,CAC9B,EACCC,GAAaQ,EAAAA,IAACK,EAAA,CAAQ,KAAK,QAAQ,MAAM,YAAA,CAAa,CAAA,CAAA,CACzD,CAAA,CAEJ,CAAA,CAAA,CACF,CAAA,EAEJ,CAEJ,ECzFMd,EAAe,CACnBtB,EACAN,IACW,CACX,MAAMS,EAASH,EAAM,SAAWN,EAAS,KAAM2C,GAAMA,EAAE,KAAOrC,EAAM,QAAQ,EAAI,OAChF,OAAKG,EACEM,EAAeN,CAAM,EAAI,iBAAmB,UAAUA,EAAO,EAAE,WADlD,iBAEtB,EAEamC,EAAa,IAAM,OAC9B,KAAM,CAACC,EAAYC,CAAa,EAAIC,EAAAA,SAAwB,IAAI,EAC1D,CAACC,EAAaC,CAAc,EAAIF,EAAAA,SAAwB,IAAI,EAC5DrD,EAAUwD,EAAaC,GAAMA,EAAE,OAAO,EACtCC,EAAiBF,EAAaC,GAAMA,EAAE,cAAc,EACpDE,EAAcH,EAAaC,GAAMA,EAAE,WAAW,EAC9CG,EAAQJ,EAAaC,GAAMA,EAAE,KAAK,EAClCI,EAAcL,EAAaC,GAAMA,EAAE,WAAW,EAC9CK,EAAcN,EAAaC,GAAMA,EAAE,WAAW,EAC9CM,EAAiBP,EAAaC,GAAMA,EAAE,cAAc,EACpDO,EAAYR,EAAaC,GAAMA,EAAE,SAAS,EAC1CQ,EAAYT,EAAaC,GAAMA,EAAE,SAAS,EAC1C,CAAE,OAAAtD,CAAA,EAAW+D,EAAA,EACbC,EAAWC,EAAA,EAEjBC,EAAAA,UAAU,IAAM,CACdV,EAAA,CACF,EAAG,CAACA,CAAW,CAAC,EAEhB,MAAMW,EAAeC,EAAAA,QACnB,MAAOX,GAAA,YAAAA,EAAO,UAAW,IAAI,OAAQX,GAAuCA,EAAE,IAAM,IAAI,EACxF,CAACW,CAAK,CAAA,EAGFtD,EAAWiE,EAAAA,QACf,IACE,CAAC,GAAGD,EAAc,GAAGT,CAAW,EAAE,OAC/BtD,GAAqDA,EAAO,IAAM,IAAA,EAEvE,CAAC+D,EAAcT,CAAW,CAAA,EAGtBlD,EAAS4D,EAAAA,QAAQ,IAAM,CAC3B,MAAMC,EAAY9C,EAAgB,CAChC,GAAG3B,EAAsBC,CAAO,EAChC,GAAGE,EAAmBC,CAAM,EAC5B,GAAGE,EAAsBC,CAAQ,CAAA,CAClC,EACD,OAAOI,EAAgB8D,EAAWlE,CAAQ,EAAE,IAAKM,IAAW,CAC1D,GAAGA,EACH,OAAQM,EAAuBN,EAAOZ,CAAO,CAAA,EAC7C,CACJ,EAAG,CAACA,EAASG,EAAQG,CAAQ,CAAC,EAExBmE,EAAa,CAACC,EAAYC,IAAkB,CAC5Cf,GAAA,MAAAA,EAAO,QAAQ,KAAMX,GAAMA,EAAE,KAAOyB,GAAMzB,EAAE,QAAU0B,GACxDR,EAAS,CAAE,GAAI,iBAAkB,OAAQ,CAAE,OAAQS,EAAiBF,EAAIC,CAAK,CAAA,EAAK,EAElFR,EAAS,CAAE,GAAI,iBAAkB,OAAQ,CAAE,OAAQS,EAAiBF,EAAIC,CAAK,CAAA,EAAK,CAEtF,EAEMlC,EAAgB,MAAO7B,GAAiB,CAC5C2C,EAAe3C,EAAM,EAAE,EACvB,GAAI,CACF,MAAMiE,EAAajE,CAAK,EACxB,MAAM,QAAQ,IAAI,CAACoD,IAAaC,EAAA,CAAW,CAAC,CAC9C,QAAA,CACEV,EAAe,IAAI,CACrB,CACF,EAEMuB,GAAgBtE,EAAAsD,EAAY,KAC/BiB,GACCA,EAAE,WAAa,aACfA,EAAE,SAAW,QACbA,EAAE,SAAW,SACbA,EAAE,SAAW,YAAA,IALK,YAAAvE,EAMnB,QAEH,cACG,MAAA,CACC,SAAA,CAAAmC,EAAAA,IAACqC,GAAU,SAAA,QAAA,CAAM,EACjBrC,EAAAA,IAAC,IAAA,CAAE,UAAU,kBAAkB,SAAA,mEAE/B,EACCe,GAAkB/C,EAAO,SAAW,SAAM,IAAA,CAAE,UAAU,aAAa,SAAA,UAAA,CAAQ,EAC3E,CAAC+C,GAAkB/C,EAAO,SAAW,GACpCgC,EAAAA,IAAC,IAAA,CAAE,UAAU,aAAa,SAAA,2BAAA,CAAyB,EAEpDhC,EAAO,OAAS,GACfgC,EAAAA,IAAC,MAAA,CAAI,UAAU,gBACZ,SAAAhC,EAAO,IAAKC,GAAU,OACrB,MAAMqE,EAAgBrE,EAAM,gBACvBJ,EAAAF,EAAS,KAAM2C,GAAMA,EAAE,KAAOrC,EAAM,aAAa,IAAjD,YAAAJ,EAAoD,QAASI,EAAM,cACpE,OACJ,OACE+B,EAAAA,IAAChB,EAAA,CAEC,MAAAf,EACA,SAAUuC,IAAevC,EAAM,GAC/B,SAAU,IAAMwC,EAAe8B,GAASA,IAAQtE,EAAM,GAAK,KAAOA,EAAM,EAAG,EAC3E,OACEA,EAAM,UAAYA,EAAM,YACpB,IAAM6D,EAAW7D,EAAM,SAAoBA,EAAM,WAAqB,EACtE,OAEN,OAAQkE,IAAkBlE,EAAM,GAChC,YAAakE,IAAkB,OAC/B,MAAO,IAAMf,EAAenD,EAAM,GAAIA,EAAM,MAAOA,EAAM,OAAQA,EAAM,MAAM,EAC7E,aAAcsB,EAAatB,EAAON,CAAQ,EAC1C,UAAWgD,IAAgB1C,EAAM,GACjC,gBAAiB0C,IAAgB,KACjC,UAAW,IAAMb,EAAc7B,CAAK,EACpC,eACEA,EAAM,cACF,IAAM6D,EAAW7D,EAAM,cAAyBqE,CAAuB,EACvE,MAAA,EAnBDrE,EAAM,EAAA,CAuBjB,CAAC,CAAA,CACH,CAAA,EAEJ,CAEJ"}
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import{c as m,r as o,j as e,P as u,Y as i,q as c,Z as h,T as j,_ as r,a0 as p,g as d,a1 as g,a2 as v,R as N}from"./main-DP_okYxh.js";const f=async()=>{const s=await fetch(m("/api/stats"));if(!s.ok)throw new Error(`Failed to fetch stats: ${s.status}`);return s.json()},y=["open",...h],T={...j,open:"Open"},b=s=>`${Math.round(s*100)}%`,l=({title:s,children:a})=>e.jsx("div",{className:"flex-[1_1_260px] max-w-[360px] max-[480px]:flex-[1_1_100%] max-[480px]:max-w-none",children:e.jsx(N,{size:"small",texture:"kraft",className:"h-full",children:e.jsxs("div",{className:"flex flex-col gap-3",children:[e.jsx("span",{className:"font-handwritten text-xs font-semibold opacity-[0.55]",children:s}),a]})})}),n=({label:s,value:a})=>e.jsxs("div",{className:"flex items-center justify-between gap-2",children:[e.jsx("span",{className:"opacity-70",children:s}),e.jsx("span",{className:"font-semibold",children:a})]}),S=({comments:s})=>e.jsxs(l,{title:"Comment ratio",children:[e.jsx(i,{value:s.ratio*100,color:c.accentSlate}),e.jsx(n,{label:"Ratio",value:b(s.ratio)}),e.jsx(n,{label:"Comment lines",value:s.commentLines}),e.jsx(n,{label:"Source lines",value:s.sourceLines})]}),C=({testCoveragePct:s})=>e.jsx(l,{title:"Test coverage",children:s===null?e.jsx("p",{className:"opacity-50 m-0",children:"Run `pnpm test` to generate a coverage report."}):e.jsxs(e.Fragment,{children:[e.jsx(i,{value:s,color:c.accentGreen}),e.jsx(n,{label:"Line coverage",value:`${Math.round(s)}%`})]})}),L=({sourceLines:s,testLines:a})=>e.jsxs(l,{title:"Codebase size",children:[e.jsx(n,{label:"Source lines",value:s}),e.jsx(n,{label:"Test lines",value:a})]}),w=({entitiesByStatus:s})=>e.jsxs(l,{title:"Entities by status",children:[y.filter(a=>s[a]).map(a=>e.jsx(n,{label:T[a],value:s[a]},a)),Object.keys(s).length===0&&e.jsx("p",{className:"opacity-50 m-0",children:"No entities yet."})]}),A=({openQuestions:s,decisions:a})=>e.jsxs(l,{title:"Notes",children:[e.jsx(n,{label:"Open questions",value:s}),e.jsx(n,{label:"Decisions",value:a})]}),M=({tasksPerWeek:s})=>{const a=Math.max(1,...s.map(t=>t.count));return e.jsxs(l,{title:"Tasks per week",children:[s.length===0&&e.jsx("p",{className:"opacity-50 m-0",children:"No tasks run yet."}),s.map(t=>e.jsxs("div",{className:"flex flex-col gap-1",children:[e.jsx(n,{label:t.week,value:t.count}),e.jsx(i,{value:t.count,max:a,color:c.accentAmber,height:4})]},t.week))]})},E=({usagePerWeek:s})=>{const a=Math.max(1,...s.map(t=>t.agentMinutes));return e.jsxs(l,{title:"Usage per week",children:[s.length===0&&e.jsx("p",{className:"opacity-50 m-0",children:"No usage recorded yet."}),s.map(t=>e.jsxs("div",{className:"flex flex-col gap-1",children:[e.jsx(n,{label:t.week,value:`${t.agentMinutes}m`}),e.jsx(i,{value:t.agentMinutes,max:a,color:c.accentAmber,height:4}),e.jsxs("span",{className:"text-2xs opacity-50",children:[r(t.inputTokens)," in · ",r(t.outputTokens)," out"]})]},t.week))]})},k=({medianPhaseDurationMs:s})=>e.jsx(l,{title:"Median phase duration",children:s===null?e.jsx("p",{className:"opacity-50 m-0",children:"No phase runs recorded yet."}):e.jsx(n,{label:"Median",value:p(s)})}),_=({mostExpensiveIdeas:s})=>e.jsxs(l,{title:"Most expensive ideas",children:[s.length===0&&e.jsx("p",{className:"opacity-50 m-0",children:"No usage recorded yet."}),s.map(a=>e.jsxs("div",{className:"flex flex-col gap-0.5",children:[e.jsx(n,{label:a.planId,value:`${r(a.inputTokens)} in · ${r(a.outputTokens)} out`}),e.jsx("span",{className:"text-2xs opacity-50 truncate",children:a.planTitle})]},a.planId))]}),D={allowed:"success",warning:"warning",rejected:"error"},P=({capacity:s})=>{const[a,t]=o.useState(()=>Date.now());return o.useEffect(()=>{const x=setInterval(()=>t(Date.now()),3e4);return()=>clearInterval(x)},[]),e.jsx(l,{title:"Claude capacity",children:s===null?e.jsx("p",{className:"opacity-50 m-0",children:"No agent run has reported capacity yet."}):e.jsxs(e.Fragment,{children:[e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx(d,{size:"small",variant:D[g(s.snapshot.status)],children:s.snapshot.status}),s.snapshot.overage&&e.jsx(d,{size:"small",variant:"warning",children:"overage"})]}),s.snapshot.rateLimitType&&e.jsx(n,{label:"Window",value:s.snapshot.rateLimitType}),s.snapshot.resetsAt!==void 0&&e.jsx(n,{label:"Resets",value:new Date(v(s.snapshot.resetsAt)).toLocaleTimeString()}),e.jsxs("span",{className:"text-2xs opacity-50",children:["as of last agent run,"," ",p(Math.max(0,a-Date.parse(s.capturedAt)))," ago"]})]})})},I=()=>{const[s,a]=o.useState(null),[t,x]=o.useState(!1);return o.useEffect(()=>{f().then(a).catch(()=>x(!0))},[]),e.jsxs("div",{children:[e.jsx(u,{children:"Stats"}),e.jsx("p",{className:"opacity-50 mb-6",children:"A read-only look at project health — every number here is informational, none of them gate anything."}),t&&e.jsx("p",{className:"opacity-50",children:"Couldn't load stats."}),!t&&!s&&e.jsx("p",{className:"opacity-50",children:"Loading…"}),s&&e.jsxs(e.Fragment,{children:[e.jsxs("div",{className:"flex flex-wrap items-start gap-2.5",children:[e.jsx(S,{comments:s.comments}),e.jsx(C,{testCoveragePct:s.testCoveragePct}),e.jsx(L,{sourceLines:s.comments.sourceLines,testLines:s.testLines}),e.jsx(w,{entitiesByStatus:s.entitiesByStatus}),e.jsx(A,{openQuestions:s.openQuestions,decisions:s.decisions}),e.jsx(M,{tasksPerWeek:s.tasksPerWeek}),e.jsx(E,{usagePerWeek:s.usagePerWeek}),e.jsx(k,{medianPhaseDurationMs:s.medianPhaseDurationMs}),e.jsx(_,{mostExpensiveIdeas:s.mostExpensiveIdeas}),e.jsx(P,{capacity:s.capacity})]}),e.jsxs("p",{className:"opacity-[0.4] text-2xs mt-6",children:["Generated ",new Date(s.generatedAt).toLocaleString()]})]})]})};export{I as StatsPage};
|
|
2
|
-
//# sourceMappingURL=index-BHmWkgBW.js.map
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import{j as a,ah as g,C as w,r as u,ai as v,aj as I,ak as E,al as $,am as C,a8 as R,J as D,an as P,u as x,a as T,P as S,N as k}from"./main-DP_okYxh.js";function L(t){return Math.floor((Date.now()-Date.parse(t))/864e5)}function A(t){return t.kind==="question"?t.ageDays:L(t.date)}const N=t=>Number.isFinite(t)?t<=0?"today":t===1?"1 day ago":`${t} days ago`:"age unknown",F={completed:"Completed",reply:"Reply"},K=({notification:t,ageDays:d,onOpen:s})=>a.jsxs("div",{className:"flex items-center gap-3 py-2.5 border-b border-black/10 last:border-b-0",children:[a.jsx("button",{type:"button",onClick:s,"aria-label":`Open ${t.entityTitle}`,className:"bg-transparent border-none p-0 cursor-pointer",children:a.jsx(g,{id:t.entityId})}),a.jsx("span",{className:"text-xs uppercase tracking-wide opacity-40 whitespace-nowrap",children:F[t.kind]}),a.jsx("span",{className:"flex-1 min-w-0 truncate",children:t.text}),a.jsx("span",{className:"opacity-50 text-xs whitespace-nowrap",children:N(d)})]}),O=({question:t,plan:d,expanded:s,onToggle:f,onOpen:o,reload:h})=>{const{toast:m}=w(),[r,p]=u.useState(""),{sending:y,send:j,undo:e,undoing:c,undoEdit:n}=v(d,{reload:h,notify:m}),i=`question-reply:${t.entityId}`;u.useEffect(()=>{const l=I(i);l&&p(l)},[i]),u.useEffect(()=>{r&&E(i,r)},[i,r]);const b=async()=>{r.trim()&&await j(r.trim())&&(p(""),P(i))};return a.jsxs("div",{className:"border-b border-black/10 last:border-b-0",children:[a.jsxs("div",{className:"flex items-center gap-3 py-2.5",children:[a.jsx("button",{type:"button",onClick:o,"aria-label":`Open ${t.entityTitle}`,className:"bg-transparent border-none p-0 cursor-pointer",children:a.jsx(g,{id:t.entityId})}),a.jsx("button",{type:"button",onClick:f,"aria-expanded":s,className:"flex-1 min-w-0 truncate text-left bg-transparent border-none p-0 cursor-pointer",children:t.text}),a.jsx("span",{className:"opacity-50 text-xs whitespace-nowrap",children:N(t.ageDays)})]}),s&&a.jsxs("div",{className:"flex flex-col gap-3 pb-4",children:[a.jsx($,{messages:d.thread??[],undo:e,undoing:c,onUndo:n}),a.jsx(C,{value:r,onChange:l=>p(l.target.value),"aria-label":`Reply to ${t.entityTitle}`,placeholder:"Reply to resolve and resume…",rows:2,disabled:y}),a.jsxs("div",{className:"flex justify-end items-center gap-3",children:[a.jsx("span",{className:y?"visible":"invisible",children:a.jsx(R,{size:"small",label:"Agent replying…"})}),a.jsx(D,{size:"small",onClick:b,disabled:y||!r.trim(),children:"Reply"})]})]})]})},z=()=>{const[t,d]=u.useState(null),s=x(e=>e.notifications),f=x(e=>e.notificationsError!==null),o=x(e=>e.plans),h=x(e=>e.loadPlans),m=x(e=>e.loadNotifications),r=x(e=>e.markRead),p=T();u.useEffect(()=>{m()},[m]),u.useEffect(()=>{if(s)for(const e of s)e.kind!=="question"&&!e.read&&r(e.id)},[s,r]);const y=async()=>{await Promise.all([h(),m()])},j=(e,c)=>{o!=null&&o.entries.some(n=>n.id===e||n.title===c)?p({to:"/plans/$planId",params:{planId:k(e,c)}}):p({to:"/ideas/$ideaId",params:{ideaId:k(e,c)}})};return a.jsxs("div",{children:[a.jsx(S,{children:"Inbox"}),a.jsx("p",{className:"opacity-50 mb-6",children:"Every parked question, completed run, and agent reply, oldest first."}),f&&a.jsx("p",{className:"opacity-50",children:"Couldn't load notifications."}),!f&&!s&&a.jsx("p",{className:"opacity-50",children:"Loading…"}),s&&s.length===0&&a.jsx("p",{className:"opacity-50",children:"Nothing here — no parked questions or recent activity."}),s&&s.length>0&&a.jsx("div",{className:"flex flex-col",children:s.map((e,c)=>{const n=`${e.entityId}-${e.kind}-${c}`,i=()=>j(e.entityId,e.entityTitle);if(e.kind!=="question")return a.jsx(K,{notification:e,ageDays:A(e),onOpen:i},n);const b=o==null?void 0:o.entries.find(l=>l.id===e.entityId);return b?a.jsx(O,{question:e,plan:b,expanded:t===n,onToggle:()=>d(l=>l===n?null:n),onOpen:i,reload:y},n):a.jsxs("div",{className:"flex items-center gap-3 py-2.5 border-b border-black/10 last:border-b-0",children:[a.jsx("button",{type:"button",onClick:i,"aria-label":`Open ${e.entityTitle}`,className:"bg-transparent border-none p-0 cursor-pointer",children:a.jsx(g,{id:e.entityId})}),a.jsx("span",{className:"flex-1 min-w-0 truncate",children:e.text}),a.jsx("span",{className:"opacity-50 text-xs whitespace-nowrap",children:N(e.ageDays)})]},n)})})]})};export{z as InboxPage};
|
|
2
|
-
//# sourceMappingURL=index-BuzzfADD.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index-BuzzfADD.js","sources":["../../../src/core/notifications.ts","../../../src/app/features/inbox/helpers.ts","../../../src/app/features/inbox/notification-row.tsx","../../../src/app/features/inbox/question-row.tsx","../../../src/app/features/inbox/inbox-page.tsx"],"sourcesContent":["import type { Notification, ParkedQuestion, StoredNotification } from '../types/index';\n\nfunction ageInDays(date: string): number {\n return Math.floor((Date.now() - Date.parse(date)) / 86_400_000);\n}\n\nexport function notificationAgeDays(notification: Notification): number {\n return notification.kind === 'question' ? notification.ageDays : ageInDays(notification.date);\n}\n\n/** Every parked question alongside completed/reply notifications, oldest-first — the\n * unified feed (IDEA-153) replacing the questions-only Inbox. */\nexport function mergeNotifications(\n parkedQuestions: ParkedQuestion[],\n stored: StoredNotification[],\n): Notification[] {\n const entries: Notification[] = [\n ...parkedQuestions.map((q): Notification => ({ ...q, kind: 'question' })),\n ...stored,\n ];\n return entries.sort((a, b) => notificationAgeDays(b) - notificationAgeDays(a));\n}\n","export const formatAge = (ageDays: number): string => {\n if (!Number.isFinite(ageDays)) return 'age unknown';\n if (ageDays <= 0) return 'today';\n return ageDays === 1 ? '1 day ago' : `${ageDays} days ago`;\n};\n","import { PlanIdStamp } from '@/app/features/plans/components';\nimport type { StoredNotification } from '@/types/index';\nimport { formatAge } from './helpers';\n\nconst KIND_LABELS: Record<StoredNotification['kind'], string> = {\n completed: 'Completed',\n reply: 'Reply',\n};\n\ninterface NotificationRowProps {\n notification: StoredNotification;\n ageDays: number;\n onOpen: () => void;\n}\n\nexport const NotificationRow = ({ notification, ageDays, onOpen }: NotificationRowProps) => (\n <div className=\"flex items-center gap-3 py-2.5 border-b border-black/10 last:border-b-0\">\n <button\n type=\"button\"\n onClick={onOpen}\n aria-label={`Open ${notification.entityTitle}`}\n className=\"bg-transparent border-none p-0 cursor-pointer\"\n >\n <PlanIdStamp id={notification.entityId} />\n </button>\n <span className=\"text-xs uppercase tracking-wide opacity-40 whitespace-nowrap\">\n {KIND_LABELS[notification.kind]}\n </span>\n <span className=\"flex-1 min-w-0 truncate\">{notification.text}</span>\n <span className=\"opacity-50 text-xs whitespace-nowrap\">{formatAge(ageDays)}</span>\n </div>\n);\n","import { FeedbackThread, PlanIdStamp } from '@/app/features/plans/components';\nimport { useSendFeedbackMessage } from '@/app/features/plans/hooks';\nimport { readLocalDraft, removeLocalDraft, writeLocalDraft } from '@/app/utils/local-draft-store';\nimport type { ParkedQuestion, PlanEntry } from '@/types/index';\nimport { Button, Spinner, Textarea, useToast } from '@dendelion/paper-ui';\nimport { useEffect, useState } from 'react';\nimport { formatAge } from './helpers';\n\ninterface QuestionRowProps {\n question: ParkedQuestion;\n plan: PlanEntry;\n expanded: boolean;\n onToggle: () => void;\n onOpen: () => void;\n reload: () => Promise<void>;\n}\n\nexport const QuestionRow = ({\n question,\n plan,\n expanded,\n onToggle,\n onOpen,\n reload,\n}: QuestionRowProps) => {\n const { toast } = useToast();\n const [input, setInput] = useState('');\n const { sending, send, undo, undoing, undoEdit } = useSendFeedbackMessage(plan, {\n reload,\n notify: toast,\n });\n\n const draftKey = `question-reply:${question.entityId}`;\n\n useEffect(() => {\n const draft = readLocalDraft<string>(draftKey);\n if (draft) setInput(draft);\n }, [draftKey]);\n\n useEffect(() => {\n if (!input) return;\n writeLocalDraft(draftKey, input);\n }, [draftKey, input]);\n\n const handleSend = async () => {\n if (!input.trim()) return;\n if (await send(input.trim())) {\n setInput('');\n removeLocalDraft(draftKey);\n }\n };\n\n return (\n <div className=\"border-b border-black/10 last:border-b-0\">\n <div className=\"flex items-center gap-3 py-2.5\">\n <button\n type=\"button\"\n onClick={onOpen}\n aria-label={`Open ${question.entityTitle}`}\n className=\"bg-transparent border-none p-0 cursor-pointer\"\n >\n <PlanIdStamp id={question.entityId} />\n </button>\n <button\n type=\"button\"\n onClick={onToggle}\n aria-expanded={expanded}\n className=\"flex-1 min-w-0 truncate text-left bg-transparent border-none p-0 cursor-pointer\"\n >\n {question.text}\n </button>\n <span className=\"opacity-50 text-xs whitespace-nowrap\">{formatAge(question.ageDays)}</span>\n </div>\n {expanded && (\n <div className=\"flex flex-col gap-3 pb-4\">\n <FeedbackThread\n messages={plan.thread ?? []}\n undo={undo}\n undoing={undoing}\n onUndo={undoEdit}\n />\n <Textarea\n value={input}\n onChange={(e) => setInput(e.target.value)}\n aria-label={`Reply to ${question.entityTitle}`}\n placeholder=\"Reply to resolve and resume…\"\n rows={2}\n disabled={sending}\n />\n <div className=\"flex justify-end items-center gap-3\">\n <span className={sending ? 'visible' : 'invisible'}>\n <Spinner size=\"small\" label=\"Agent replying…\" />\n </span>\n <Button size=\"small\" onClick={handleSend} disabled={sending || !input.trim()}>\n Reply\n </Button>\n </div>\n </div>\n )}\n </div>\n );\n};\n","import { PageTitle } from '@/app/components/page-title';\nimport { PlanIdStamp } from '@/app/features/plans/components';\nimport { entityRouteParam } from '@/app/hooks';\nimport { useAppStore } from '@/app/stores/app-store';\nimport { notificationAgeDays } from '@/core/notifications';\nimport { useNavigate } from '@tanstack/react-router';\nimport { useEffect, useState } from 'react';\nimport { formatAge } from './helpers';\nimport { NotificationRow } from './notification-row';\nimport { QuestionRow } from './question-row';\n\nexport const InboxPage = () => {\n const [expandedKey, setExpandedKey] = useState<string | null>(null);\n const notifications = useAppStore((s) => s.notifications);\n const loadFailed = useAppStore((s) => s.notificationsError !== null);\n const plans = useAppStore((s) => s.plans);\n const loadPlans = useAppStore((s) => s.loadPlans);\n const loadNotifications = useAppStore((s) => s.loadNotifications);\n const markRead = useAppStore((s) => s.markRead);\n const navigate = useNavigate();\n\n useEffect(() => {\n loadNotifications();\n }, [loadNotifications]);\n\n useEffect(() => {\n if (!notifications) return;\n for (const n of notifications) {\n if (n.kind !== 'question' && !n.read) markRead(n.id);\n }\n }, [notifications, markRead]);\n\n const reload = async () => {\n await Promise.all([loadPlans(), loadNotifications()]);\n };\n\n const openEntity = (id: string, title: string) => {\n if (plans?.entries.some((p) => p.id === id || p.title === title)) {\n navigate({ to: '/plans/$planId', params: { planId: entityRouteParam(id, title) } });\n } else {\n navigate({ to: '/ideas/$ideaId', params: { ideaId: entityRouteParam(id, title) } });\n }\n };\n\n return (\n <div>\n <PageTitle>Inbox</PageTitle>\n <p className=\"opacity-50 mb-6\">\n Every parked question, completed run, and agent reply, oldest first.\n </p>\n {loadFailed && <p className=\"opacity-50\">Couldn't load notifications.</p>}\n {!loadFailed && !notifications && <p className=\"opacity-50\">Loading…</p>}\n {notifications && notifications.length === 0 && (\n <p className=\"opacity-50\">Nothing here — no parked questions or recent activity.</p>\n )}\n {notifications && notifications.length > 0 && (\n <div className=\"flex flex-col\">\n {notifications.map((n, i) => {\n const key = `${n.entityId}-${n.kind}-${i}`;\n const onOpen = () => openEntity(n.entityId, n.entityTitle);\n\n if (n.kind !== 'question') {\n return (\n <NotificationRow\n key={key}\n notification={n}\n ageDays={notificationAgeDays(n)}\n onOpen={onOpen}\n />\n );\n }\n\n const plan = plans?.entries.find((p) => p.id === n.entityId);\n if (!plan) {\n return (\n <div\n key={key}\n className=\"flex items-center gap-3 py-2.5 border-b border-black/10 last:border-b-0\"\n >\n <button\n type=\"button\"\n onClick={onOpen}\n aria-label={`Open ${n.entityTitle}`}\n className=\"bg-transparent border-none p-0 cursor-pointer\"\n >\n <PlanIdStamp id={n.entityId} />\n </button>\n <span className=\"flex-1 min-w-0 truncate\">{n.text}</span>\n <span className=\"opacity-50 text-xs whitespace-nowrap\">\n {formatAge(n.ageDays)}\n </span>\n </div>\n );\n }\n return (\n <QuestionRow\n key={key}\n question={n}\n plan={plan}\n expanded={expandedKey === key}\n onToggle={() => setExpandedKey((cur) => (cur === key ? null : key))}\n onOpen={onOpen}\n reload={reload}\n />\n );\n })}\n </div>\n )}\n </div>\n );\n};\n"],"names":["ageInDays","date","notificationAgeDays","notification","formatAge","ageDays","KIND_LABELS","NotificationRow","onOpen","jsxs","jsx","PlanIdStamp","QuestionRow","question","plan","expanded","onToggle","reload","toast","useToast","input","setInput","useState","sending","send","undo","undoing","undoEdit","useSendFeedbackMessage","draftKey","useEffect","draft","readLocalDraft","writeLocalDraft","handleSend","removeLocalDraft","FeedbackThread","Textarea","e","Spinner","Button","InboxPage","expandedKey","setExpandedKey","notifications","useAppStore","s","loadFailed","plans","loadPlans","loadNotifications","markRead","navigate","useNavigate","n","openEntity","id","title","p","entityRouteParam","PageTitle","i","key","cur"],"mappings":"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"}
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import{c as K,r as d,C as F,j as e,e as M,g as P,R as _,J as v,w as N,h as $,k as X,$ as te,l as ae,s as D,u as T,t as Q,m as ne,a as Z,n as ee,P as le,o as ie,i as B,q as oe,D as k,v as re,A as O,x as q,y as ce,z as de,B as me,p as L}from"./main-DP_okYxh.js";import{S as V}from"./sidebar-section-B-A20bg9.js";const pe=async()=>{try{const s=await fetch(K("/api/merge-policy"));return s.ok?await s.json():null}catch{return null}},he=async()=>{try{const s=await fetch(K("/api/merge-policy/apply"),{method:"POST"});return s.ok?await s.json():null}catch{return null}},se={allowSquashMerge:!0,allowMergeCommit:!1,allowRebaseMerge:!1,squashMergeCommitTitle:"PR_TITLE",squashMergeCommitMessage:"PR_BODY"},G=[{key:"allowSquashMerge",label:"Allow squash merge"},{key:"allowMergeCommit",label:"Allow merge commit"},{key:"allowRebaseMerge",label:"Allow rebase merge"},{key:"squashMergeCommitTitle",label:"Squash commit title"},{key:"squashMergeCommitMessage",label:"Squash commit message"}];function W(s){return typeof s=="boolean"?s?"Yes":"No":s}function ue(s){return G.every(t=>s[t.key]===se[t.key])}const xe=({label:s,current:t,recommended:o,isLast:l})=>{const p=t===o;return e.jsxs(e.Fragment,{children:[e.jsxs("div",{className:"flex items-center gap-3 pb-2 pt-2",children:[e.jsx("span",{className:"flex-1",children:s}),e.jsx("span",{className:p?"opacity-[0.65] font-normal":"font-semibold",children:W(t)}),!p&&e.jsxs("span",{className:"opacity-[0.45]",children:["→ ",W(o)]})]}),!l&&e.jsx(N,{})]})},je=()=>{const[s,t]=d.useState(void 0),[o,l]=d.useState(!1),{toast:p}=F();d.useEffect(()=>{pe().then(t)},[]);const u=async()=>{l(!0);const n=await he();l(!1),(n==null?void 0:n.status)==="ok"?(t(n),p({title:"Merge policy applied",variant:"success"})):p({title:"Failed to apply merge policy",description:(n==null?void 0:n.status)==="unavailable"?n.reason:void 0,variant:"error"})},x=(s==null?void 0:s.status)==="ok"&&ue(s.policy);return e.jsxs("div",{children:[e.jsxs("div",{className:"mb-6",children:[e.jsx("h2",{className:"m-0",children:"Merge Policy"}),e.jsx("p",{className:"opacity-50 mt-1",children:"Paper Camp's canonical policy: squash-only merges, with the commit title and body taken from the PR."})]}),s===void 0&&e.jsx("p",{children:"Loading…"}),s===null&&e.jsx(M,{variant:"warning",children:"Failed to load merge policy."}),(s==null?void 0:s.status)==="unavailable"&&e.jsx(M,{variant:"warning",children:s.reason}),(s==null?void 0:s.status)==="ok"&&e.jsxs(e.Fragment,{children:[e.jsxs("div",{className:"flex items-center gap-3 mb-3",children:[e.jsx("span",{className:"font-medium",children:s.repo}),e.jsx(P,{size:"small",fillColor:x?"rgba(143, 185, 150, 0.25)":"rgba(212, 163, 115, 0.25)",textColor:x?"#5E8A66":"#A67B4F",children:x?"Matches recommended policy":"Differs from recommended policy"})]}),e.jsx(_,{size:"small",texture:"kraft",children:G.map((n,r)=>e.jsx(xe,{label:n.label,current:s.policy[n.key],recommended:se[n.key],isLast:r===G.length-1},n.key))}),e.jsx("div",{className:"mt-4",children:e.jsx(v,{size:"small",onClick:u,disabled:o||x,children:o?"Applying…":"Apply recommended policy"})})]})]})},ge=2e3,fe="agent:claude-code",ve=["claude auth login","claude setup-token"],be=()=>e.jsxs("div",{className:"mt-2 flex flex-col gap-2",children:[e.jsx("p",{className:"opacity-[0.65] text-sm m-0",children:"The in-app relay couldn't complete. Run one of these in a terminal instead:"}),ve.map(s=>e.jsx(Q,{code:s},s)),e.jsxs("p",{className:"opacity-50 text-sm m-0",children:["Alternatively, set ",e.jsx("code",{children:"ANTHROPIC_API_KEY"})," in the server's environment — that bills as API usage rather than your Max subscription, so treat it as a fallback, not the default."]})]}),ye=({onSignedIn:s})=>{const t=T(r=>r.loginRelay),o=T(r=>r.startLoginRelay),l=T(r=>r.loadLoginRelayStatus),p=T(r=>r.cancelLoginRelay),{toast:u}=F(),x=d.useRef(null),n=t==null?void 0:t.phase;return d.useEffect(()=>{if(n!=="starting"&&n!=="awaiting-authorization")return;const r=window.setInterval(()=>l(),ge);return()=>window.clearInterval(r)},[n,l]),d.useEffect(()=>{const r=t==null?void 0:t.authorizeUrl;n!=="awaiting-authorization"||!r||x.current===r||(x.current=r,window.open(r,"_blank","noopener,noreferrer"))},[n,t==null?void 0:t.authorizeUrl]),d.useEffect(()=>{n==="success"?(u({title:"Signed in",variant:"success"}),s()):n==="error"&&u({title:"Sign-in failed",description:t==null?void 0:t.error,variant:"error"})},[n]),n==="starting"?e.jsx(v,{size:"small",disabled:!0,children:"Starting…"}):n==="awaiting-authorization"?e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx(v,{size:"small",onClick:()=>(t==null?void 0:t.authorizeUrl)&&window.open(t.authorizeUrl,"_blank","noopener,noreferrer"),children:"Reopen sign-in tab"}),e.jsx(v,{size:"small",onClick:()=>p(),children:"Cancel"})]}):e.jsxs("div",{children:[e.jsx(v,{size:"small",onClick:()=>o(),children:"Sign in"}),n==="error"&&e.jsx(be,{})]})},Ne={ok:{fill:"rgba(143, 185, 150, 0.25)",text:"#5E8A66",label:"Ready"},warn:{fill:"rgba(212, 163, 115, 0.25)",text:"#A67B4F",label:"Needs attention"},missing:{fill:"rgba(201, 139, 139, 0.25)",text:"#6E3A3A",label:"Missing"}},Se=({connection:s,onConnect:t,connecting:o})=>{const{connect:l}=s;return l?l.kind==="command"&&l.runnable?e.jsx(v,{size:"small",onClick:()=>t(s.id),disabled:o,children:o?"Running…":`Run \`${l.command}\``}):l.kind==="command"?e.jsx("div",{className:"mt-2",children:e.jsx(Q,{code:l.command})}):l.kind==="link"?e.jsx(v,{size:"small",onClick:()=>window.open(l.url,"_blank","noopener,noreferrer"),children:l.label}):e.jsx("p",{className:"opacity-[0.65] text-sm mt-2 mx-0 mb-0",children:l.message}):null},we=({connection:s,isLast:t,onRecheck:o,rechecking:l,onConnect:p,connecting:u})=>{const x=Ne[s.status];return e.jsxs(e.Fragment,{children:[e.jsxs("div",{className:"pb-3 pt-3",children:[e.jsxs("div",{className:"flex items-center gap-3",children:[e.jsx("span",{className:"flex-1 font-medium",children:s.label}),s.authenticated===!1&&e.jsx(te,{content:"Installed, but not signed in",children:e.jsx(P,{size:"small",variant:"warning",children:"Not signed in"})}),e.jsx(P,{size:"small",fillColor:x.fill,textColor:x.text,children:x.label}),e.jsx(v,{size:"small",onClick:()=>o(s.id),disabled:l,children:l?"Checking…":"Recheck"})]}),e.jsxs("p",{className:"opacity-[0.65] text-sm mt-1 mx-0 mb-0",children:["Unlocks: ",s.unlocks]}),e.jsx("p",{className:"opacity-50 text-sm mt-1 mx-0 mb-0",children:s.detail}),e.jsx("div",{className:"mt-2",children:s.id===fe&&s.authenticated===!1?e.jsx(ye,{onSignedIn:()=>o(s.id)}):e.jsx(Se,{connection:s,onConnect:p,connecting:u})})]}),!t&&e.jsx(N,{})]})},ke=()=>{const[s,t]=d.useState(null),[o,l]=d.useState(!1),[p,u]=d.useState(null),[x,n]=d.useState(null),[r,S]=d.useState(!1),{toast:y}=F(),w=d.useCallback(c=>{if(c===null){l(!0);return}l(!1),t(c)},[]);d.useEffect(()=>{$().then(w),X().then(c=>S((c==null?void 0:c.setupDismissed)??!1))},[w]);const E=async c=>{u(c),w(await $()),u(null)},A=async c=>{n(c);const f=await ae(c);f?t(b=>(b==null?void 0:b.map(C=>C.id===c?f:C))??b):y({title:"Failed to connect",variant:"error"}),n(null)},j=async()=>{const c=!r,{ok:f}=await D({setupDismissed:c});f?(S(c),y({title:"Saved",variant:"success"})):y({title:"Failed to save",variant:"error"})},m=(s==null?void 0:s.every(c=>c.status==="ok"))??!0;return e.jsxs("div",{children:[e.jsx("div",{className:"mb-6",children:e.jsx("h2",{className:"m-0",children:"Setup"})}),s===null&&!o&&e.jsx("p",{children:"Loading…"}),o&&e.jsx("div",{className:"mb-4",children:e.jsx(M,{variant:"warning",children:"Failed to load connections. Try refreshing."})}),s&&e.jsxs(e.Fragment,{children:[!m&&e.jsx("div",{className:"mb-4",children:e.jsx(M,{variant:"warning",children:"Some connections are incomplete — features that depend on them stay disabled until fixed. Run the connect command below, then recheck."})}),e.jsx(_,{size:"small",texture:"kraft",children:s.map((c,f)=>e.jsx(we,{connection:c,isLast:f===s.length-1,onRecheck:E,rechecking:p===c.id,onConnect:A,connecting:x===c.id},c.id))}),e.jsx("div",{className:"mt-4",children:e.jsx(v,{size:"small",onClick:j,children:r?"Show Setup on open again":"Don't show Setup on open"})})]})]})},Ae=()=>{const{subjects:s,loading:t,available:o}=ne(),l=Z();return e.jsxs("div",{children:[e.jsxs("div",{className:"mb-6",children:[e.jsx("h2",{className:"m-0",children:"Subjects"}),e.jsx("p",{className:"opacity-50 mt-1",children:"Subjects are the roadmap's bets and standing concerns. Edit the vocabulary on the Roadmap page."})]}),t&&e.jsx("p",{children:"Loading…"}),!t&&!o&&e.jsx("p",{className:"opacity-60 m-0",children:"Couldn't load subjects — check the server config."}),!t&&o&&e.jsxs(_,{size:"small",texture:"kraft",children:[s.length===0&&e.jsx("p",{className:"opacity-[0.45] m-0 pb-2",children:"No subjects yet."}),s.map((p,u)=>e.jsxs("div",{children:[e.jsx("div",{className:"pb-2 pt-2",children:p}),u<s.length-1&&e.jsx(N,{})]},p)),s.length>0&&e.jsx(N,{}),e.jsx("div",{className:"pt-3",children:e.jsx(v,{size:"small",onClick:()=>l({to:"/roadmap"}),children:"Go to Roadmap"})})]})]})},J=["phase","planDraft","ideaExtend","commitSuggest","feedback","codeReview"],Ce={phase:"Phase run",planDraft:"Plan draft",ideaExtend:"Idea extend",commitSuggest:"Commit suggest",feedback:"Scout chat",codeReview:"Code review"},Re=140,Ee=160,Ie=110,ze=()=>e.jsxs("div",{className:"flex items-center gap-3 pb-1",children:[e.jsx("span",{className:"w-[110px] shrink-0 text-sm opacity-[0.45]",children:"Task"}),e.jsx("span",{className:"w-[140px] shrink-0 text-sm opacity-[0.45]",children:"Agent"}),e.jsx("span",{className:"w-[160px] shrink-0 text-sm opacity-[0.45]",children:"Model"}),e.jsx("span",{className:"w-[110px] shrink-0 text-sm opacity-[0.45]",children:"Effort"})]}),De=({taskKey:s,agentConfig:t,isLast:o,onSave:l,authorConfig:p})=>{const u=O[t.agent]??O["claude-code"],x=p&&p.agent===t.agent?p.model??"":void 0,n=Array.isArray(u.model)?u.model.filter(m=>m!==x):u.model,r=u.effort,[S,y]=d.useState(t.model??"");d.useEffect(()=>{y(t.model??"")},[t.model]);const w=m=>{var C;const c=m,f=O[c],b={agent:c};t.model&&(f.model===null||(C=f.model)!=null&&C.includes(t.model))&&(b.model=t.model),t.effort&&Array.isArray(f.effort)&&(b.effort=t.effort),l(s,b)},E=m=>{l(s,{...t,model:m||void 0})},A=()=>{l(s,{...t,model:S||void 0})},j=m=>{l(s,{...t,effort:m||void 0})};return e.jsxs(e.Fragment,{children:[e.jsxs("div",{className:"flex items-center gap-3 pb-2 pt-2",children:[e.jsx("span",{className:"w-[110px] shrink-0 text-sm opacity-[0.65]",children:Ce[s]}),e.jsx(q,{size:"small",width:Re,value:t.agent,onChange:w,options:ce.map(m=>({value:m,label:de[m]}))}),Array.isArray(n)?e.jsx(q,{size:"small",width:Ee,value:t.model??"",onChange:E,options:[...x===""?[]:[{value:"",label:"Default"}],...n.map(m=>({value:m,label:m}))]}):n===null?e.jsx(B,{size:"small",className:"w-[160px]",value:S,placeholder:"Default model",onChange:m=>y(m.target.value),onBlur:A}):null,e.jsx("div",{className:Array.isArray(r)?"visible":"invisible",children:e.jsx(q,{size:"small",width:Ie,value:t.effort??"",onChange:j,options:[{value:"",label:"Default"},...(Array.isArray(r)?r:[]).map(m=>({value:m,label:m}))]})})]}),!o&&e.jsx(N,{})]})},Me=()=>{var Y,H;const s=d.useRef(null),[t,o]=d.useState(void 0),{iconDataUri:l,loading:p}=ie(),[u,x]=d.useState(null),n=u??l,[r,S]=d.useState(!1),[y,w]=d.useState(""),[E,A]=d.useState(""),{toast:j}=F();d.useEffect(()=>{X().then(a=>{o(a),(a==null?void 0:a.port)!==void 0&&w(String(a.port)),(a==null?void 0:a.projectName)!==void 0&&A(a.projectName)})},[]);const m=async(a,g)=>{const i=t==null?void 0:t.defaultAgents,h={phase:(i==null?void 0:i.phase)??k.phase,planDraft:(i==null?void 0:i.planDraft)??k.planDraft,ideaExtend:(i==null?void 0:i.ideaExtend)??k.ideaExtend,commitSuggest:(i==null?void 0:i.commitSuggest)??k.commitSuggest,feedback:(i==null?void 0:i.feedback)??k.feedback,codeReview:(i==null?void 0:i.codeReview)??k.codeReview,[a]:g};if(a==="codeReview"&&me(g,h.phase)){j({title:"Failed to save",description:"Code review must use a different model than Phase run.",variant:"error"});return}const{ok:R,error:I}=await D({defaultAgents:h});R?(o(z=>z&&{...z,defaultAgents:h}),j({title:"Saved",variant:"success"})):j({title:"Failed to save",description:I,variant:"error"})},c=async()=>{const a=Number(y);if(!t||!Number.isInteger(a)||a<=0||a===t.port)return;const{ok:g,error:i}=await D({port:a});g?(o(h=>h&&{...h,port:a}),j({title:"Saved",description:"Restart `paper-camp dev` to apply the new port.",variant:"success"})):j({title:"Failed to save",description:i,variant:"error"})},f=async()=>{var R,I,z;if(!t)return;const a=!(((I=(R=t.integration)==null?void 0:R.toolbar)==null?void 0:I.enabled)??!0),g={...t.integration,toolbar:{...(z=t.integration)==null?void 0:z.toolbar,enabled:a}},{ok:i,error:h}=await D({integration:g});i?(o(U=>U&&{...U,integration:g}),j({title:"Saved",variant:"success"})):j({title:"Failed to save",description:h,variant:"error"})},b=async()=>{const a=E.trim();if(!t||!a||a===t.projectName)return;const{ok:g,error:i}=await D({projectName:a});g?(o(h=>h&&{...h,projectName:a}),A(a),j({title:"Saved",variant:"success"})):j({title:"Failed to save",description:i,variant:"error"})},C=async a=>{var h;const g=(h=a.target.files)==null?void 0:h[0];if(!g)return;S(!0);const i=new FileReader;i.onloadend=async()=>{const R=i.result,I=await re(R);S(!1),I?(x(R),j({title:"Saved",variant:"success"})):j({title:"Failed to save",variant:"error"})},i.readAsDataURL(g)};return e.jsxs("div",{children:[e.jsx("div",{className:"mb-6",children:e.jsx("h2",{className:"m-0",children:"Project Info"})}),t===void 0&&e.jsx("p",{children:"Loading…"}),t===null&&e.jsxs(M,{variant:"warning",children:["No papercamp/config.json found — run ",e.jsx("code",{children:"paper-camp init"})," in this directory first."]}),t&&e.jsxs(_,{size:"small",texture:"kraft",children:[e.jsxs("div",{className:"flex items-end gap-3 pb-3",children:[e.jsx(B,{value:E,onChange:a=>A(a.target.value),onBlur:b,label:"Project Name"}),e.jsxs(P,{size:"small",fillColor:"rgba(143, 185, 150, 0.25)",textColor:oe.accentGreenDark,children:["v",t.version]})]}),e.jsx(N,{}),e.jsxs("div",{className:"flex items-center gap-3 pb-3 pt-3",children:[n&&e.jsx("img",{src:n,alt:"Project icon",className:"w-10 h-10 object-contain shrink-0 rounded"}),e.jsxs("div",{children:[e.jsx("input",{ref:s,type:"file",accept:".svg,.png,.jpg,.jpeg,.gif,.webp",onChange:C,className:"hidden"}),e.jsx(v,{size:"small",onClick:()=>{var a;return(a=s.current)==null?void 0:a.click()},disabled:r,children:r?"Uploading…":"Choose File"}),p&&e.jsx("p",{className:"text-sm opacity-50 mt-1 mx-0 mb-0",children:"Loading…"}),!p&&!n&&!r&&e.jsx("p",{className:"text-sm opacity-[0.45] mt-1 mx-0 mb-0",children:"No icon set."})]})]}),e.jsx(N,{}),e.jsx("div",{className:"flex items-end gap-3 pb-3 pt-3",children:e.jsx(B,{type:"number",value:y,onChange:a=>w(a.target.value),onBlur:c,label:"Port",helperText:"Default for `paper-camp dev`. Restart the server to apply a change."})}),e.jsx(N,{}),e.jsxs("div",{className:"flex items-center justify-between gap-3 pb-3 pt-3",children:[e.jsxs("div",{children:[e.jsx("p",{className:"m-0",children:"In-app dev toolbar"}),e.jsx("p",{className:"opacity-[0.45] text-sm mt-1 mx-0 mb-0",children:"Inject the paper-camp toolbar into this project's dev server via the Vite plugin."})]}),e.jsx(v,{size:"small",onClick:f,children:((H=(Y=t.integration)==null?void 0:Y.toolbar)==null?void 0:H.enabled)??!0?"Disable":"Enable"})]}),e.jsx(N,{}),e.jsx(ze,{}),J.map((a,g)=>{var i,h;return e.jsx(De,{taskKey:a,agentConfig:((i=t.defaultAgents)==null?void 0:i[a])??k[a],isLast:g===J.length-1,onSave:m,authorConfig:a==="codeReview"?((h=t.defaultAgents)==null?void 0:h.phase)??k.phase:void 0},a)})]}),e.jsxs("p",{className:"opacity-[0.45] text-sm mt-4",children:[e.jsx("strong",{children:"Initialized:"})," ",t?new Date(t.initializedAt).toLocaleString():"—"]})]})},Pe=()=>{const s=ee();return e.jsxs("div",{children:[e.jsx(le,{children:"Settings"}),s==="subjects"?e.jsx(Ae,{}):s==="setup"?e.jsx(ke,{}):s==="merge-policy"?e.jsx(je,{}):e.jsx(Me,{})]})},Fe=()=>{const s=ee(),t=Z();return e.jsxs(e.Fragment,{children:[e.jsxs(V,{label:"General",children:[e.jsx(L,{size:"small",className:"pc-row text-xs",active:s===null,onClick:()=>t({to:"/settings"}),children:"Project Info"}),e.jsx(L,{size:"small",className:"pc-row text-xs",active:s==="setup",onClick:()=>t({to:"/settings/$section",params:{section:"setup"}}),children:"Setup"}),e.jsx(L,{size:"small",className:"pc-row text-xs",active:s==="merge-policy",onClick:()=>t({to:"/settings/$section",params:{section:"merge-policy"}}),children:"Merge Policy"})]}),e.jsx(V,{label:"Ideas",children:e.jsx(L,{size:"small",className:"pc-row text-xs",active:s==="subjects",onClick:()=>t({to:"/settings/$section",params:{section:"subjects"}}),children:"Subjects"})})]})};export{Pe as SettingsPage,Fe as SettingsSidebar};
|
|
2
|
-
//# sourceMappingURL=index-CkLeljbk.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index-CkLeljbk.js","sources":["../../../src/app/services/system/merge-policy-api.ts","../../../src/app/features/settings/components/merge-policy-section.tsx","../../../src/app/features/settings/components/setup-section.tsx","../../../src/app/features/settings/components/subjects-section.tsx","../../../src/app/features/settings/settings-page.tsx","../../../src/app/features/settings/components/settings-sidebar.tsx"],"sourcesContent":["import type { MergePolicyResult } from '@/types/index';\nimport { apiUrl } from '../api-base';\n\nexport const fetchMergePolicy = async (): Promise<MergePolicyResult | null> => {\n try {\n const response = await fetch(apiUrl('/api/merge-policy'));\n if (!response.ok) return null;\n return (await response.json()) as MergePolicyResult;\n } catch {\n return null;\n }\n};\n\nexport const applyMergePolicy = async (): Promise<MergePolicyResult | null> => {\n try {\n const response = await fetch(apiUrl('/api/merge-policy/apply'), { method: 'POST' });\n if (!response.ok) return null;\n return (await response.json()) as MergePolicyResult;\n } catch {\n return null;\n }\n};\n","import { applyMergePolicy, fetchMergePolicy } from '@/app/services/system';\nimport type { MergePolicy, MergePolicyResult } from '@/types/index';\nimport { Alert, Button, Card, Divider, Stamp, useToast } from '@dendelion/paper-ui';\nimport { useEffect, useState } from 'react';\n\nconst RECOMMENDED: MergePolicy = {\n allowSquashMerge: true,\n allowMergeCommit: false,\n allowRebaseMerge: false,\n squashMergeCommitTitle: 'PR_TITLE',\n squashMergeCommitMessage: 'PR_BODY',\n};\n\nconst POLICY_ROWS: { key: keyof MergePolicy; label: string }[] = [\n { key: 'allowSquashMerge', label: 'Allow squash merge' },\n { key: 'allowMergeCommit', label: 'Allow merge commit' },\n { key: 'allowRebaseMerge', label: 'Allow rebase merge' },\n { key: 'squashMergeCommitTitle', label: 'Squash commit title' },\n { key: 'squashMergeCommitMessage', label: 'Squash commit message' },\n];\n\nfunction formatValue(value: boolean | string): string {\n return typeof value === 'boolean' ? (value ? 'Yes' : 'No') : value;\n}\n\nfunction matchesRecommended(policy: MergePolicy): boolean {\n return POLICY_ROWS.every((row) => policy[row.key] === RECOMMENDED[row.key]);\n}\n\ninterface PolicyRowProps {\n label: string;\n current: boolean | string;\n recommended: boolean | string;\n isLast: boolean;\n}\n\nconst PolicyRow = ({ label, current, recommended, isLast }: PolicyRowProps) => {\n const matches = current === recommended;\n return (\n <>\n <div className=\"flex items-center gap-3 pb-2 pt-2\">\n <span className=\"flex-1\">{label}</span>\n <span className={matches ? 'opacity-[0.65] font-normal' : 'font-semibold'}>\n {formatValue(current)}\n </span>\n {!matches && <span className=\"opacity-[0.45]\">→ {formatValue(recommended)}</span>}\n </div>\n {!isLast && <Divider />}\n </>\n );\n};\n\nexport const MergePolicySection = () => {\n const [result, setResult] = useState<MergePolicyResult | null | undefined>(undefined);\n const [applying, setApplying] = useState(false);\n const { toast } = useToast();\n\n useEffect(() => {\n fetchMergePolicy().then(setResult);\n }, []);\n\n const handleApply = async () => {\n setApplying(true);\n const applied = await applyMergePolicy();\n setApplying(false);\n if (applied?.status === 'ok') {\n setResult(applied);\n toast({ title: 'Merge policy applied', variant: 'success' });\n } else {\n toast({\n title: 'Failed to apply merge policy',\n description: applied?.status === 'unavailable' ? applied.reason : undefined,\n variant: 'error',\n });\n }\n };\n\n const upToDate = result?.status === 'ok' && matchesRecommended(result.policy);\n\n return (\n <div>\n <div className=\"mb-6\">\n <h2 className=\"m-0\">Merge Policy</h2>\n <p className=\"opacity-50 mt-1\">\n Paper Camp's canonical policy: squash-only merges, with the commit title and body taken\n from the PR.\n </p>\n </div>\n {result === undefined && <p>Loading…</p>}\n {result === null && <Alert variant=\"warning\">Failed to load merge policy.</Alert>}\n {result?.status === 'unavailable' && <Alert variant=\"warning\">{result.reason}</Alert>}\n {result?.status === 'ok' && (\n <>\n <div className=\"flex items-center gap-3 mb-3\">\n <span className=\"font-medium\">{result.repo}</span>\n <Stamp\n size=\"small\"\n fillColor={upToDate ? 'rgba(143, 185, 150, 0.25)' : 'rgba(212, 163, 115, 0.25)'}\n textColor={upToDate ? '#5E8A66' : '#A67B4F'}\n >\n {upToDate ? 'Matches recommended policy' : 'Differs from recommended policy'}\n </Stamp>\n </div>\n <Card size=\"small\" texture=\"kraft\">\n {POLICY_ROWS.map((row, idx) => (\n <PolicyRow\n key={row.key}\n label={row.label}\n current={result.policy[row.key]}\n recommended={RECOMMENDED[row.key]}\n isLast={idx === POLICY_ROWS.length - 1}\n />\n ))}\n </Card>\n <div className=\"mt-4\">\n <Button size=\"small\" onClick={handleApply} disabled={applying || upToDate}>\n {applying ? 'Applying…' : 'Apply recommended policy'}\n </Button>\n </div>\n </>\n )}\n </div>\n );\n};\n","import { connectService, fetchConfig, fetchConnections, saveConfig } from '@/app/services/system';\nimport { useAppStore } from '@/app/stores/app-store';\nimport type { CapabilityStatus, ConnectionResult } from '@/types/index';\nimport {\n Alert,\n Button,\n Card,\n CodeBlock,\n Divider,\n Stamp,\n Tooltip,\n useToast,\n} from '@dendelion/paper-ui';\nimport { useCallback, useEffect, useRef, useState } from 'react';\n\nconst RELAY_POLL_MS = 2000;\n\n// Only claude-code exposes `auth login`/`auth status` (see agentAuthenticated in server/services.ts).\nconst RELAY_CONNECTION_ID: ConnectionResult['id'] = 'agent:claude-code';\n\n// Same two commands the relay drives interactively — shown as copy-paste fallback\n// when the relay itself can't run (no PTY, offline, unsupported CLI version).\nconst AUTH_FIX_COMMANDS = ['claude auth login', 'claude setup-token'] as const;\n\nconst RelayFallbackGuide = () => (\n <div className=\"mt-2 flex flex-col gap-2\">\n <p className=\"opacity-[0.65] text-sm m-0\">\n The in-app relay couldn't complete. Run one of these in a terminal instead:\n </p>\n {AUTH_FIX_COMMANDS.map((cmd) => (\n <CodeBlock key={cmd} code={cmd} />\n ))}\n <p className=\"opacity-50 text-sm m-0\">\n {/* paper-ui has no inline-code component, only the block-level CodeBlock */}\n Alternatively, set <code>ANTHROPIC_API_KEY</code> in the server's environment — that bills as\n API usage rather than your Max subscription, so treat it as a fallback, not the default.\n </p>\n </div>\n);\n\nconst SignInAction = ({ onSignedIn }: { onSignedIn: () => void }) => {\n const loginRelay = useAppStore((s) => s.loginRelay);\n const startLoginRelay = useAppStore((s) => s.startLoginRelay);\n const loadLoginRelayStatus = useAppStore((s) => s.loadLoginRelayStatus);\n const cancelLoginRelay = useAppStore((s) => s.cancelLoginRelay);\n const { toast } = useToast();\n const openedUrlRef = useRef<string | null>(null);\n const phase = loginRelay?.phase;\n\n useEffect(() => {\n if (phase !== 'starting' && phase !== 'awaiting-authorization') return;\n const id = window.setInterval(() => loadLoginRelayStatus(), RELAY_POLL_MS);\n return () => window.clearInterval(id);\n }, [phase, loadLoginRelayStatus]);\n\n useEffect(() => {\n const url = loginRelay?.authorizeUrl;\n if (phase !== 'awaiting-authorization' || !url || openedUrlRef.current === url) return;\n openedUrlRef.current = url;\n window.open(url, '_blank', 'noopener,noreferrer');\n }, [phase, loginRelay?.authorizeUrl]);\n\n // biome-ignore lint/correctness/useExhaustiveDependencies: keyed on phase transitions only — toast/onSignedIn aren't stable across renders, and including them would refire on every relay poll\n useEffect(() => {\n if (phase === 'success') {\n toast({ title: 'Signed in', variant: 'success' });\n onSignedIn();\n } else if (phase === 'error') {\n toast({ title: 'Sign-in failed', description: loginRelay?.error, variant: 'error' });\n }\n }, [phase]);\n\n if (phase === 'starting') {\n return (\n <Button size=\"small\" disabled>\n Starting…\n </Button>\n );\n }\n if (phase === 'awaiting-authorization') {\n return (\n <div className=\"flex items-center gap-2\">\n <Button\n size=\"small\"\n onClick={() =>\n loginRelay?.authorizeUrl &&\n window.open(loginRelay.authorizeUrl, '_blank', 'noopener,noreferrer')\n }\n >\n Reopen sign-in tab\n </Button>\n <Button size=\"small\" onClick={() => cancelLoginRelay()}>\n Cancel\n </Button>\n </div>\n );\n }\n return (\n <div>\n <Button size=\"small\" onClick={() => startLoginRelay()}>\n Sign in\n </Button>\n {phase === 'error' && <RelayFallbackGuide />}\n </div>\n );\n};\n\nconst STATUS_STAMP: Record<CapabilityStatus, { fill: string; text: string; label: string }> = {\n ok: { fill: 'rgba(143, 185, 150, 0.25)', text: '#5E8A66', label: 'Ready' },\n warn: { fill: 'rgba(212, 163, 115, 0.25)', text: '#A67B4F', label: 'Needs attention' },\n missing: { fill: 'rgba(201, 139, 139, 0.25)', text: '#6E3A3A', label: 'Missing' },\n};\n\nconst ConnectActionView = ({\n connection,\n onConnect,\n connecting,\n}: {\n connection: ConnectionResult;\n onConnect: (id: string) => void;\n connecting: boolean;\n}) => {\n const { connect } = connection;\n if (!connect) return null;\n if (connect.kind === 'command' && connect.runnable) {\n return (\n <Button size=\"small\" onClick={() => onConnect(connection.id)} disabled={connecting}>\n {connecting ? 'Running…' : `Run \\`${connect.command}\\``}\n </Button>\n );\n }\n if (connect.kind === 'command') {\n return (\n <div className=\"mt-2\">\n <CodeBlock code={connect.command} />\n </div>\n );\n }\n if (connect.kind === 'link') {\n return (\n <Button\n size=\"small\"\n onClick={() => window.open(connect.url, '_blank', 'noopener,noreferrer')}\n >\n {connect.label}\n </Button>\n );\n }\n return <p className=\"opacity-[0.65] text-sm mt-2 mx-0 mb-0\">{connect.message}</p>;\n};\n\nconst ConnectionRow = ({\n connection,\n isLast,\n onRecheck,\n rechecking,\n onConnect,\n connecting,\n}: {\n connection: ConnectionResult;\n isLast: boolean;\n onRecheck: (id: string) => void;\n rechecking: boolean;\n onConnect: (id: string) => void;\n connecting: boolean;\n}) => {\n const stamp = STATUS_STAMP[connection.status];\n return (\n <>\n <div className=\"pb-3 pt-3\">\n <div className=\"flex items-center gap-3\">\n <span className=\"flex-1 font-medium\">{connection.label}</span>\n {connection.authenticated === false && (\n <Tooltip content=\"Installed, but not signed in\">\n <Stamp size=\"small\" variant=\"warning\">\n Not signed in\n </Stamp>\n </Tooltip>\n )}\n <Stamp size=\"small\" fillColor={stamp.fill} textColor={stamp.text}>\n {stamp.label}\n </Stamp>\n <Button size=\"small\" onClick={() => onRecheck(connection.id)} disabled={rechecking}>\n {rechecking ? 'Checking…' : 'Recheck'}\n </Button>\n </div>\n <p className=\"opacity-[0.65] text-sm mt-1 mx-0 mb-0\">Unlocks: {connection.unlocks}</p>\n <p className=\"opacity-50 text-sm mt-1 mx-0 mb-0\">{connection.detail}</p>\n <div className=\"mt-2\">\n {connection.id === RELAY_CONNECTION_ID && connection.authenticated === false ? (\n <SignInAction onSignedIn={() => onRecheck(connection.id)} />\n ) : (\n <ConnectActionView\n connection={connection}\n onConnect={onConnect}\n connecting={connecting}\n />\n )}\n </div>\n </div>\n {!isLast && <Divider />}\n </>\n );\n};\n\nexport const SetupSection = () => {\n const [connections, setConnections] = useState<ConnectionResult[] | null>(null);\n const [loadFailed, setLoadFailed] = useState(false);\n const [reloadingId, setReloadingId] = useState<string | null>(null);\n const [connectingId, setConnectingId] = useState<string | null>(null);\n const [setupDismissed, setSetupDismissed] = useState(false);\n const { toast } = useToast();\n\n const applyConnections = useCallback((result: ConnectionResult[] | null) => {\n if (result === null) {\n setLoadFailed(true);\n return;\n }\n setLoadFailed(false);\n setConnections(result);\n }, []);\n\n useEffect(() => {\n fetchConnections().then(applyConnections);\n fetchConfig().then((c) => setSetupDismissed(c?.setupDismissed ?? false));\n }, [applyConnections]);\n\n const handleRecheck = async (id: string) => {\n setReloadingId(id);\n applyConnections(await fetchConnections());\n setReloadingId(null);\n };\n\n const handleConnect = async (id: string) => {\n setConnectingId(id);\n const updated = await connectService(id);\n if (updated) {\n setConnections((prev) => prev?.map((c) => (c.id === id ? updated : c)) ?? prev);\n } else {\n toast({ title: 'Failed to connect', variant: 'error' });\n }\n setConnectingId(null);\n };\n\n const handleDismissToggle = async () => {\n const next = !setupDismissed;\n const { ok } = await saveConfig({ setupDismissed: next });\n if (ok) {\n setSetupDismissed(next);\n toast({ title: 'Saved', variant: 'success' });\n } else {\n toast({ title: 'Failed to save', variant: 'error' });\n }\n };\n\n const allOk = connections?.every((c) => c.status === 'ok') ?? true;\n\n return (\n <div>\n <div className=\"mb-6\">\n <h2 className=\"m-0\">Setup</h2>\n </div>\n {connections === null && !loadFailed && <p>Loading…</p>}\n {loadFailed && (\n <div className=\"mb-4\">\n <Alert variant=\"warning\">Failed to load connections. Try refreshing.</Alert>\n </div>\n )}\n {connections && (\n <>\n {!allOk && (\n <div className=\"mb-4\">\n <Alert variant=\"warning\">\n Some connections are incomplete — features that depend on them stay disabled until\n fixed. Run the connect command below, then recheck.\n </Alert>\n </div>\n )}\n <Card size=\"small\" texture=\"kraft\">\n {connections.map((c, idx) => (\n <ConnectionRow\n key={c.id}\n connection={c}\n isLast={idx === connections.length - 1}\n onRecheck={handleRecheck}\n rechecking={reloadingId === c.id}\n onConnect={handleConnect}\n connecting={connectingId === c.id}\n />\n ))}\n </Card>\n <div className=\"mt-4\">\n <Button size=\"small\" onClick={handleDismissToggle}>\n {setupDismissed ? 'Show Setup on open again' : \"Don't show Setup on open\"}\n </Button>\n </div>\n </>\n )}\n </div>\n );\n};\n","import { useSubjectVocabulary } from '@/app/hooks';\nimport { Button, Card, Divider } from '@dendelion/paper-ui';\nimport { useNavigate } from '@tanstack/react-router';\n\nexport const SubjectsSection = () => {\n const { subjects, loading, available } = useSubjectVocabulary();\n const navigate = useNavigate();\n\n return (\n <div>\n <div className=\"mb-6\">\n <h2 className=\"m-0\">Subjects</h2>\n <p className=\"opacity-50 mt-1\">\n Subjects are the roadmap's bets and standing concerns. Edit the vocabulary on the Roadmap\n page.\n </p>\n </div>\n {loading && <p>Loading…</p>}\n {!loading && !available && (\n <p className=\"opacity-60 m-0\">Couldn't load subjects — check the server config.</p>\n )}\n {!loading && available && (\n <Card size=\"small\" texture=\"kraft\">\n {subjects.length === 0 && <p className=\"opacity-[0.45] m-0 pb-2\">No subjects yet.</p>}\n {subjects.map((name, idx) => (\n <div key={name}>\n <div className=\"pb-2 pt-2\">{name}</div>\n {idx < subjects.length - 1 && <Divider />}\n </div>\n ))}\n {subjects.length > 0 && <Divider />}\n <div className=\"pt-3\">\n <Button size=\"small\" onClick={() => navigate({ to: '/roadmap' })}>\n Go to Roadmap\n </Button>\n </div>\n </Card>\n )}\n </div>\n );\n};\n","import { PageTitle } from '@/app/components/page-title';\nimport { useActiveSettingsSection, useProjectIdentity } from '@/app/hooks';\nimport { fetchConfig, saveConfig, uploadIcon } from '@/app/services/system';\nimport { color } from '@/app/styles/tokens';\nimport {\n AGENT_IDS,\n AGENT_LABELS,\n AGENT_OPTIONS,\n type AgentConfig,\n type AgentId,\n DEFAULT_AGENTS,\n type DefaultAgentsMap,\n type PaperCampConfig,\n agentConfigsEqual,\n} from '@/types/index';\nimport { Alert, Button, Card, Divider, Input, Select, Stamp, useToast } from '@dendelion/paper-ui';\nimport { useEffect, useRef, useState } from 'react';\nimport { MergePolicySection } from './components/merge-policy-section';\nimport { SetupSection } from './components/setup-section';\nimport { SubjectsSection } from './components/subjects-section';\n\nconst TASK_TYPE_KEYS = [\n 'phase',\n 'planDraft',\n 'ideaExtend',\n 'commitSuggest',\n 'feedback',\n 'codeReview',\n] as const;\ntype TaskTypeKey = (typeof TASK_TYPE_KEYS)[number];\n\nconst TASK_TYPE_LABELS: Record<TaskTypeKey, string> = {\n phase: 'Phase run',\n planDraft: 'Plan draft',\n ideaExtend: 'Idea extend',\n commitSuggest: 'Commit suggest',\n feedback: 'Scout chat',\n codeReview: 'Code review',\n};\n\ninterface AgentTaskRowProps {\n taskKey: TaskTypeKey;\n agentConfig: AgentConfig;\n isLast: boolean;\n onSave: (key: TaskTypeKey, config: AgentConfig) => Promise<void>;\n /** The code-authoring task's config — codeReview's model must never match it (IDEA-170). */\n authorConfig?: AgentConfig;\n}\n\nconst TASK_COLUMN_WIDTH = 110;\nconst AGENT_COLUMN_WIDTH = 140;\nconst MODEL_COLUMN_WIDTH = 160;\nconst EFFORT_COLUMN_WIDTH = 110;\n\nconst AgentTaskRowHeader = () => (\n <div className=\"flex items-center gap-3 pb-1\">\n <span className=\"w-[110px] shrink-0 text-sm opacity-[0.45]\">Task</span>\n <span className=\"w-[140px] shrink-0 text-sm opacity-[0.45]\">Agent</span>\n <span className=\"w-[160px] shrink-0 text-sm opacity-[0.45]\">Model</span>\n <span className=\"w-[110px] shrink-0 text-sm opacity-[0.45]\">Effort</span>\n </div>\n);\n\nconst AgentTaskRow = ({\n taskKey,\n agentConfig,\n isLast,\n onSave,\n authorConfig,\n}: AgentTaskRowProps) => {\n // Fall back if the config carries an unknown agent id — never white-screen the page.\n const opts = AGENT_OPTIONS[agentConfig.agent] ?? AGENT_OPTIONS['claude-code'];\n // Only the author's model is excluded, and only while both rows share the same agent id.\n const excludedModel =\n authorConfig && authorConfig.agent === agentConfig.agent\n ? (authorConfig.model ?? '')\n : undefined;\n const modelOpts = Array.isArray(opts.model)\n ? opts.model.filter((m) => m !== excludedModel)\n : opts.model;\n const effortOpts = opts.effort;\n const [localModel, setLocalModel] = useState(agentConfig.model ?? '');\n\n useEffect(() => {\n setLocalModel(agentConfig.model ?? '');\n }, [agentConfig.model]);\n\n const handleAgentChange = (v: string) => {\n const newId = v as AgentId;\n const newOpts = AGENT_OPTIONS[newId];\n const newConfig: AgentConfig = { agent: newId };\n // Only carry the model over if the new agent accepts it — otherwise a claude\n // model like 'opus' would leak into opencode and fail at launch.\n if (\n agentConfig.model &&\n (newOpts.model === null || newOpts.model?.includes(agentConfig.model))\n ) {\n newConfig.model = agentConfig.model;\n }\n if (agentConfig.effort && Array.isArray(newOpts.effort)) newConfig.effort = agentConfig.effort;\n onSave(taskKey, newConfig);\n };\n\n const handleModelSelectChange = (v: string) => {\n onSave(taskKey, { ...agentConfig, model: v || undefined });\n };\n\n const handleModelInputBlur = () => {\n onSave(taskKey, { ...agentConfig, model: localModel || undefined });\n };\n\n const handleEffortChange = (v: string) => {\n onSave(taskKey, { ...agentConfig, effort: v || undefined });\n };\n\n return (\n <>\n <div className=\"flex items-center gap-3 pb-2 pt-2\">\n <span className=\"w-[110px] shrink-0 text-sm opacity-[0.65]\">\n {TASK_TYPE_LABELS[taskKey]}\n </span>\n <Select\n size=\"small\"\n width={AGENT_COLUMN_WIDTH}\n value={agentConfig.agent}\n onChange={handleAgentChange}\n options={AGENT_IDS.map((id) => ({ value: id, label: AGENT_LABELS[id] }))}\n />\n {Array.isArray(modelOpts) ? (\n <Select\n size=\"small\"\n width={MODEL_COLUMN_WIDTH}\n value={agentConfig.model ?? ''}\n onChange={handleModelSelectChange}\n options={[\n ...(excludedModel === '' ? [] : [{ value: '', label: 'Default' }]),\n ...modelOpts.map((m) => ({ value: m, label: m })),\n ]}\n />\n ) : modelOpts === null ? (\n <Input\n size=\"small\"\n className=\"w-[160px]\"\n value={localModel}\n placeholder=\"Default model\"\n onChange={(e) => setLocalModel(e.target.value)}\n onBlur={handleModelInputBlur}\n />\n ) : null}\n {/* Reserve the effort slot even when the agent has no effort options, so\n switching agents doesn't change the control count and shift the row. */}\n <div className={Array.isArray(effortOpts) ? 'visible' : 'invisible'}>\n <Select\n size=\"small\"\n width={EFFORT_COLUMN_WIDTH}\n value={agentConfig.effort ?? ''}\n onChange={handleEffortChange}\n options={[\n { value: '', label: 'Default' },\n ...(Array.isArray(effortOpts) ? effortOpts : []).map((e) => ({ value: e, label: e })),\n ]}\n />\n </div>\n </div>\n {!isLast && <Divider />}\n </>\n );\n};\n\nconst GeneralSection = () => {\n const fileRef = useRef<HTMLInputElement>(null);\n const [config, setConfig] = useState<PaperCampConfig | null | undefined>(undefined);\n const { iconDataUri: fetchedIconDataUri, loading: identityLoading } = useProjectIdentity();\n const [uploadedIconDataUri, setUploadedIconDataUri] = useState<string | null>(null);\n const iconDataUri = uploadedIconDataUri ?? fetchedIconDataUri;\n const [uploading, setUploading] = useState(false);\n const [portInput, setPortInput] = useState('');\n const [nameInput, setNameInput] = useState('');\n const { toast } = useToast();\n\n useEffect(() => {\n fetchConfig().then((c) => {\n setConfig(c);\n if (c?.port !== undefined) setPortInput(String(c.port));\n if (c?.projectName !== undefined) setNameInput(c.projectName);\n });\n }, []);\n\n const handleSaveAgentConfig = async (key: TaskTypeKey, newEntry: AgentConfig) => {\n const current = config?.defaultAgents;\n const updated: DefaultAgentsMap = {\n phase: current?.phase ?? DEFAULT_AGENTS.phase,\n planDraft: current?.planDraft ?? DEFAULT_AGENTS.planDraft,\n ideaExtend: current?.ideaExtend ?? DEFAULT_AGENTS.ideaExtend,\n commitSuggest: current?.commitSuggest ?? DEFAULT_AGENTS.commitSuggest,\n feedback: current?.feedback ?? DEFAULT_AGENTS.feedback,\n codeReview: current?.codeReview ?? DEFAULT_AGENTS.codeReview,\n [key]: newEntry,\n };\n if (key === 'codeReview' && agentConfigsEqual(newEntry, updated.phase)) {\n toast({\n title: 'Failed to save',\n description: 'Code review must use a different model than Phase run.',\n variant: 'error',\n });\n return;\n }\n const { ok, error } = await saveConfig({ defaultAgents: updated });\n if (ok) {\n setConfig((prev) => (prev ? { ...prev, defaultAgents: updated } : prev));\n toast({ title: 'Saved', variant: 'success' });\n } else {\n toast({ title: 'Failed to save', description: error, variant: 'error' });\n }\n };\n\n const handleSavePort = async () => {\n const port = Number(portInput);\n if (!config || !Number.isInteger(port) || port <= 0 || port === config.port) return;\n const { ok, error } = await saveConfig({ port });\n if (ok) {\n setConfig((prev) => (prev ? { ...prev, port } : prev));\n toast({\n title: 'Saved',\n description: 'Restart `paper-camp dev` to apply the new port.',\n variant: 'success',\n });\n } else {\n toast({ title: 'Failed to save', description: error, variant: 'error' });\n }\n };\n\n const handleToggleIntegration = async () => {\n if (!config) return;\n const next = !(config.integration?.toolbar?.enabled ?? true);\n const integration = {\n ...config.integration,\n toolbar: { ...config.integration?.toolbar, enabled: next },\n };\n const { ok, error } = await saveConfig({ integration });\n if (ok) {\n setConfig((prev) => (prev ? { ...prev, integration } : prev));\n toast({ title: 'Saved', variant: 'success' });\n } else {\n toast({ title: 'Failed to save', description: error, variant: 'error' });\n }\n };\n\n const handleSaveName = async () => {\n const projectName = nameInput.trim();\n if (!config || !projectName || projectName === config.projectName) return;\n const { ok, error } = await saveConfig({ projectName });\n if (ok) {\n setConfig((prev) => (prev ? { ...prev, projectName } : prev));\n setNameInput(projectName);\n toast({ title: 'Saved', variant: 'success' });\n } else {\n toast({ title: 'Failed to save', description: error, variant: 'error' });\n }\n };\n\n const handleFile = async (e: React.ChangeEvent<HTMLInputElement>) => {\n const file = e.target.files?.[0];\n if (!file) return;\n setUploading(true);\n const reader = new FileReader();\n reader.onloadend = async () => {\n const dataUri = reader.result as string;\n const ok = await uploadIcon(dataUri);\n setUploading(false);\n if (ok) {\n setUploadedIconDataUri(dataUri);\n toast({ title: 'Saved', variant: 'success' });\n } else {\n toast({ title: 'Failed to save', variant: 'error' });\n }\n };\n reader.readAsDataURL(file);\n };\n\n return (\n <div>\n <div className=\"mb-6\">\n <h2 className=\"m-0\">Project Info</h2>\n </div>\n {config === undefined && <p>Loading…</p>}\n {config === null && (\n <Alert variant=\"warning\">\n No papercamp/config.json found — run <code>paper-camp init</code> in this directory first.\n </Alert>\n )}\n {config && (\n <Card size=\"small\" texture=\"kraft\">\n <div className=\"flex items-end gap-3 pb-3\">\n <Input\n value={nameInput}\n onChange={(e) => setNameInput(e.target.value)}\n onBlur={handleSaveName}\n label=\"Project Name\"\n />\n <Stamp\n size=\"small\"\n fillColor=\"rgba(143, 185, 150, 0.25)\"\n textColor={color.accentGreenDark}\n >\n v{config.version}\n </Stamp>\n </div>\n <Divider />\n\n <div className=\"flex items-center gap-3 pb-3 pt-3\">\n {iconDataUri && (\n <img\n src={iconDataUri}\n alt=\"Project icon\"\n className=\"w-10 h-10 object-contain shrink-0 rounded\"\n />\n )}\n <div>\n {/* paper-ui has no file-input component, so this raw input is intentional */}\n <input\n ref={fileRef}\n type=\"file\"\n accept=\".svg,.png,.jpg,.jpeg,.gif,.webp\"\n onChange={handleFile}\n className=\"hidden\"\n />\n <Button size=\"small\" onClick={() => fileRef.current?.click()} disabled={uploading}>\n {uploading ? 'Uploading…' : 'Choose File'}\n </Button>\n {identityLoading && <p className=\"text-sm opacity-50 mt-1 mx-0 mb-0\">Loading…</p>}\n {!identityLoading && !iconDataUri && !uploading && (\n <p className=\"text-sm opacity-[0.45] mt-1 mx-0 mb-0\">No icon set.</p>\n )}\n </div>\n </div>\n <Divider />\n\n <div className=\"flex items-end gap-3 pb-3 pt-3\">\n <Input\n type=\"number\"\n value={portInput}\n onChange={(e) => setPortInput(e.target.value)}\n onBlur={handleSavePort}\n label=\"Port\"\n helperText=\"Default for `paper-camp dev`. Restart the server to apply a change.\"\n />\n </div>\n <Divider />\n\n <div className=\"flex items-center justify-between gap-3 pb-3 pt-3\">\n <div>\n <p className=\"m-0\">In-app dev toolbar</p>\n <p className=\"opacity-[0.45] text-sm mt-1 mx-0 mb-0\">\n Inject the paper-camp toolbar into this project's dev server via the Vite plugin.\n </p>\n </div>\n <Button size=\"small\" onClick={handleToggleIntegration}>\n {(config.integration?.toolbar?.enabled ?? true) ? 'Disable' : 'Enable'}\n </Button>\n </div>\n <Divider />\n\n <AgentTaskRowHeader />\n {TASK_TYPE_KEYS.map((key, idx) => (\n <AgentTaskRow\n key={key}\n taskKey={key}\n agentConfig={config.defaultAgents?.[key] ?? DEFAULT_AGENTS[key]}\n isLast={idx === TASK_TYPE_KEYS.length - 1}\n onSave={handleSaveAgentConfig}\n authorConfig={\n key === 'codeReview'\n ? (config.defaultAgents?.phase ?? DEFAULT_AGENTS.phase)\n : undefined\n }\n />\n ))}\n </Card>\n )}\n\n <p className=\"opacity-[0.45] text-sm mt-4\">\n <strong>Initialized:</strong>{' '}\n {config ? new Date(config.initializedAt).toLocaleString() : '—'}\n </p>\n </div>\n );\n};\n\nexport const SettingsPage = () => {\n const section = useActiveSettingsSection();\n return (\n <div>\n <PageTitle>Settings</PageTitle>\n {section === 'subjects' ? (\n <SubjectsSection />\n ) : section === 'setup' ? (\n <SetupSection />\n ) : section === 'merge-policy' ? (\n <MergePolicySection />\n ) : (\n <GeneralSection />\n )}\n </div>\n );\n};\n","import { useActiveSettingsSection } from '@/app/hooks';\nimport { ListItem } from '@dendelion/paper-ui';\nimport { useNavigate } from '@tanstack/react-router';\nimport { SidebarSection } from '../../plans/components/sidebar-section';\n\nexport const SettingsSidebar = () => {\n const section = useActiveSettingsSection();\n const navigate = useNavigate();\n\n return (\n <>\n <SidebarSection label=\"General\">\n <ListItem\n size=\"small\"\n className=\"pc-row text-xs\"\n active={section === null}\n onClick={() => navigate({ to: '/settings' })}\n >\n Project Info\n </ListItem>\n <ListItem\n size=\"small\"\n className=\"pc-row text-xs\"\n active={section === 'setup'}\n onClick={() => navigate({ to: '/settings/$section', params: { section: 'setup' } })}\n >\n Setup\n </ListItem>\n <ListItem\n size=\"small\"\n className=\"pc-row text-xs\"\n active={section === 'merge-policy'}\n onClick={() =>\n navigate({ to: '/settings/$section', params: { section: 'merge-policy' } })\n }\n >\n Merge Policy\n </ListItem>\n </SidebarSection>\n <SidebarSection label=\"Ideas\">\n <ListItem\n size=\"small\"\n className=\"pc-row text-xs\"\n active={section === 'subjects'}\n onClick={() => navigate({ to: '/settings/$section', params: { section: 'subjects' } })}\n >\n Subjects\n </ListItem>\n </SidebarSection>\n </>\n );\n};\n"],"names":["fetchMergePolicy","response","apiUrl","applyMergePolicy","RECOMMENDED","POLICY_ROWS","formatValue","value","matchesRecommended","policy","row","PolicyRow","label","current","recommended","isLast","matches","jsxs","Fragment","jsx","Divider","MergePolicySection","result","setResult","useState","applying","setApplying","toast","useToast","useEffect","handleApply","applied","upToDate","Alert","Stamp","Card","idx","Button","RELAY_POLL_MS","RELAY_CONNECTION_ID","AUTH_FIX_COMMANDS","RelayFallbackGuide","cmd","CodeBlock","SignInAction","onSignedIn","loginRelay","useAppStore","s","startLoginRelay","loadLoginRelayStatus","cancelLoginRelay","openedUrlRef","useRef","phase","id","url","STATUS_STAMP","ConnectActionView","connection","onConnect","connecting","connect","ConnectionRow","onRecheck","rechecking","stamp","Tooltip","SetupSection","connections","setConnections","loadFailed","setLoadFailed","reloadingId","setReloadingId","connectingId","setConnectingId","setupDismissed","setSetupDismissed","applyConnections","useCallback","fetchConnections","fetchConfig","handleRecheck","handleConnect","updated","connectService","prev","c","handleDismissToggle","next","ok","saveConfig","allOk","SubjectsSection","subjects","loading","available","useSubjectVocabulary","navigate","useNavigate","name","TASK_TYPE_KEYS","TASK_TYPE_LABELS","AGENT_COLUMN_WIDTH","MODEL_COLUMN_WIDTH","EFFORT_COLUMN_WIDTH","AgentTaskRowHeader","AgentTaskRow","taskKey","agentConfig","onSave","authorConfig","opts","AGENT_OPTIONS","excludedModel","modelOpts","effortOpts","localModel","setLocalModel","handleAgentChange","v","newId","newOpts","newConfig","_a","handleModelSelectChange","handleModelInputBlur","handleEffortChange","Select","AGENT_IDS","AGENT_LABELS","Input","e","GeneralSection","fileRef","config","setConfig","fetchedIconDataUri","identityLoading","useProjectIdentity","uploadedIconDataUri","setUploadedIconDataUri","iconDataUri","uploading","setUploading","portInput","setPortInput","nameInput","setNameInput","handleSaveAgentConfig","key","newEntry","DEFAULT_AGENTS","agentConfigsEqual","error","handleSavePort","port","handleToggleIntegration","_b","integration","_c","handleSaveName","projectName","handleFile","file","reader","dataUri","uploadIcon","color","SettingsPage","section","useActiveSettingsSection","PageTitle","SettingsSidebar","SidebarSection","ListItem"],"mappings":"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"}
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import{u as o,a as x,j as e,J as w,r as p,f as C,d as m,M as b,b as S,E as v,P as V,i as $,p as D}from"./main-DP_okYxh.js";import{S as g}from"./sidebar-section-B-A20bg9.js";const N=(s,i)=>s.toLowerCase().includes(i.toLowerCase()),L=(s,i,a=120)=>{const l=s.toLowerCase().indexOf(i.toLowerCase());if(l===-1)return s.slice(0,a);const n=Math.max(0,l-40),d=Math.min(s.length,l+i.length+40);let r=s.slice(n,d);return n>0&&(r=`…${r}`),d<s.length&&(r=`${r}…`),r},T=({query:s})=>{const i=o(c=>c.repoDocs),a=o(c=>c.setActiveDocTitle),l=o(c=>c.setDocSearchQuery),n=x(),d=[];for(const c of i)(N(c.name,s)||N(c.content,s))&&d.push({title:c.name,snippet:L(c.content,s)});const r=c=>{n({to:"/docs/$section",params:{section:"repo-docs"}}),a(c),l("")};return d.length===0?e.jsxs("p",{className:"opacity-50",children:['No results found for "',s,'".']}):e.jsxs("div",{children:[e.jsxs("p",{className:"mb-3 text-sm opacity-50",children:[d.length," result",d.length===1?"":"s",' for "',s,'"']}),e.jsx("div",{className:"flex flex-col gap-1",children:d.map(c=>e.jsx(w,{variant:"ghost",size:"small",fullWidth:!0,onClick:()=>r(c.title),className:"justify-start px-3 py-2",children:e.jsxs("span",{className:"text-left",children:[e.jsx("span",{className:"mb-[0.2rem] block font-display-luminari text-[0.95rem] font-semibold",children:c.title}),e.jsx("span",{className:"text-sm opacity-60",children:c.snippet})]})},c.title))})]})},A=s=>{const[i,a]=p.useState(null);return p.useEffect(()=>{if(a(null),!s)return;let l=!1;return C(s).then(n=>{l||a(n)}).catch(()=>{}),()=>{l=!0}},[s]),i},E=()=>{const s=o(l=>l.activeReleaseVersion),i=A(s);if(!s)return null;if(!i)return e.jsxs("div",{children:[e.jsx("h2",{className:`${m} m-0 mb-4`,children:s}),e.jsxs("p",{className:"opacity-50",children:["No release notes found for ",s,"."]})]});if(i.length===0)return e.jsxs("div",{children:[e.jsx("h2",{className:`${m} m-0 mb-4`,children:s}),e.jsx("p",{className:"opacity-50",children:"No ideas could be resolved for this release."})]});const a=i.map(l=>`### ${l.label}
|
|
2
|
-
|
|
3
|
-
${l.ideas.map(n=>`- ${n.title} (${n.id})`).join(`
|
|
4
|
-
`)}`).join(`
|
|
5
|
-
|
|
6
|
-
`);return e.jsxs("div",{children:[e.jsx("h2",{className:`${m} m-0 mb-4`,children:s}),e.jsx("div",{className:"text-base leading-[1.7] text-ink-900",children:e.jsx(b,{children:a})})]})},Q=()=>{const s=o(n=>n.repoDocs),i=o(n=>n.activeDocTitle),a=s.find(n=>n.name===i);if(!a)return null;const l=a.name.endsWith(".md");return e.jsxs("div",{children:[e.jsx("h2",{className:`${m} m-0 mb-4`,children:a.name}),l?e.jsx("div",{className:"text-base leading-[1.7] text-ink-900",children:e.jsx(b,{children:a.content})}):e.jsx("pre",{className:"overflow-x-auto whitespace-pre-wrap rounded-md bg-black/[4%] p-4 font-mono text-xs leading-normal",children:a.content})]})},J=()=>{const s=o(r=>r.docSearchQuery),i=o(r=>r.activeDocTitle),a=o(r=>r.activeReleaseVersion),l=x(),n=S(),d=()=>l({to:"/docs"});return s.trim()?e.jsx("div",{children:e.jsx(T,{query:s})}):n==="repo-docs"&&i?e.jsxs("div",{children:[e.jsx("div",{className:"mb-4",children:e.jsx(v,{items:[{id:"docs",label:"Docs",onClick:d},{id:"repo-doc",label:i}]})}),e.jsx(Q,{})]}):n==="release-notes"&&a?e.jsxs("div",{children:[e.jsx("div",{className:"mb-4",children:e.jsx(v,{items:[{id:"docs",label:"Docs",onClick:d},{id:"release-notes",label:a}]})}),e.jsx(E,{})]}):e.jsxs("div",{children:[e.jsx(V,{children:"Docs"}),e.jsx("p",{className:"opacity-50",children:"Pick a doc from the sidebar — start with a repo doc if you're new here."})]})},M=s=>s.charAt(0).toUpperCase()+s.slice(1).toLowerCase(),h=({children:s})=>e.jsx("span",{className:"block px-3 py-1 text-sm italic opacity-[0.35]",children:s}),W=()=>{const s=o(t=>t.repoDocs),i=o(t=>t.repoDocsLoading),a=o(t=>t.loadRepoDocs),l=o(t=>t.activeDocTitle),n=o(t=>t.setActiveDocTitle),d=o(t=>t.docSearchQuery),r=o(t=>t.setDocSearchQuery),c=o(t=>t.releaseVersions),R=o(t=>t.releaseVersionsLoading),u=o(t=>t.loadReleaseVersions),y=o(t=>t.activeReleaseVersion),k=o(t=>t.setActiveReleaseVersion),j=S(),f=x();return p.useEffect(()=>{a()},[a]),p.useEffect(()=>{u()},[u]),e.jsxs(e.Fragment,{children:[e.jsx("div",{className:"flex h-[64px] items-center",children:e.jsx($,{size:"small","aria-label":"Search docs",placeholder:"Search docs…",value:d,onChange:t=>r(t.target.value)})}),e.jsx(g,{label:"Repo Docs",children:i&&s.length===0?e.jsx(h,{children:"Loading…"}):s.length>0?s.map(t=>e.jsx(D,{size:"small",className:"pc-row text-xs",active:j==="repo-docs"&&l===t.name,onClick:()=>{f({to:"/docs/$section",params:{section:"repo-docs"}}),n(t.name)},children:M(t.name)},t.name)):e.jsx(h,{children:"No repo docs found"})}),e.jsx(g,{label:"Releases",children:R&&c.length===0?e.jsx(h,{children:"Loading…"}):c.length>0?c.map(t=>e.jsx(D,{size:"small",className:"pc-row text-xs",active:j==="release-notes"&&y===t,onClick:()=>{f({to:"/docs/$section",params:{section:"release-notes"}}),k(t)},children:t},t)):e.jsx(h,{children:"No releases yet"})})]})};export{J as DocsPage,W as DocsSidebar};
|
|
7
|
-
//# sourceMappingURL=index-DfekHwwU.js.map
|