@dendelion/paper-camp 0.19.0 → 0.20.0

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.
Files changed (73) hide show
  1. package/dist/app/assets/index-3pzg7Iyw.js +2 -0
  2. package/dist/app/assets/index-3pzg7Iyw.js.map +1 -0
  3. package/dist/app/assets/{index-B6251tL6.js → index-BHmWkgBW.js} +2 -2
  4. package/dist/app/assets/{index-B6251tL6.js.map → index-BHmWkgBW.js.map} +1 -1
  5. package/dist/app/assets/{index-DLkUANCR.js → index-BuzzfADD.js} +2 -2
  6. package/dist/app/assets/{index-DLkUANCR.js.map → index-BuzzfADD.js.map} +1 -1
  7. package/dist/app/assets/{index-D5j1njQR.js → index-CkLeljbk.js} +2 -2
  8. package/dist/app/assets/{index-D5j1njQR.js.map → index-CkLeljbk.js.map} +1 -1
  9. package/dist/app/assets/{index-nuVVSVma.js → index-DMDqfoCe.js} +2 -2
  10. package/dist/app/assets/index-DMDqfoCe.js.map +1 -0
  11. package/dist/app/assets/index-D_Lo8ptS.js +6 -0
  12. package/dist/app/assets/index-D_Lo8ptS.js.map +1 -0
  13. package/dist/app/assets/{index-BNoowtMI.js → index-DfekHwwU.js} +3 -3
  14. package/dist/app/assets/{index-BNoowtMI.js.map → index-DfekHwwU.js.map} +1 -1
  15. package/dist/app/assets/{index-D2iIIpI1.js → index-Gi97Lmex.js} +3 -3
  16. package/dist/app/assets/{index-D2iIIpI1.js.map → index-Gi97Lmex.js.map} +1 -1
  17. package/dist/app/assets/main-Bm0W4ucz.css +1 -0
  18. package/dist/app/assets/main-DP_okYxh.js +215 -0
  19. package/dist/app/assets/main-DP_okYxh.js.map +1 -0
  20. package/dist/app/assets/sidebar-section-B-A20bg9.js +2 -0
  21. package/dist/app/assets/sidebar-section-B-A20bg9.js.map +1 -0
  22. package/dist/app/index.html +2 -2
  23. package/dist/app/toolbar.js +1071 -1049
  24. package/dist/app/toolbar.js.map +1 -1
  25. package/dist/chunks/{phase-progress.CwSKAAZ-.js → phase-progress.DPAZ01Gg.js} +1720 -1594
  26. package/dist/chunks/phase-progress.DPAZ01Gg.js.map +1 -0
  27. package/dist/cli/index.js +6633 -6345
  28. package/dist/cli/index.js.map +1 -1
  29. package/dist/core/corpus-format.d.ts +11 -0
  30. package/dist/core/corpus-format.d.ts.map +1 -0
  31. package/dist/core/corpus-format.test.d.ts +2 -0
  32. package/dist/core/corpus-format.test.d.ts.map +1 -0
  33. package/dist/core/doctor/checks/metadata.d.ts.map +1 -1
  34. package/dist/core/doctor/rules.d.ts +6 -1
  35. package/dist/core/doctor/rules.d.ts.map +1 -1
  36. package/dist/core/git-pr/pr-lookup.d.ts +6 -1
  37. package/dist/core/git-pr/pr-lookup.d.ts.map +1 -1
  38. package/dist/core/git-pr/pr.d.ts +10 -0
  39. package/dist/core/git-pr/pr.d.ts.map +1 -1
  40. package/dist/core/index.js +54 -53
  41. package/dist/core/issues.d.ts +51 -0
  42. package/dist/core/issues.d.ts.map +1 -0
  43. package/dist/core/issues.test.d.ts +2 -0
  44. package/dist/core/issues.test.d.ts.map +1 -0
  45. package/dist/core/parse/parser.d.ts.map +1 -1
  46. package/dist/core/parse/schemas.d.ts +8 -4
  47. package/dist/core/parse/schemas.d.ts.map +1 -1
  48. package/dist/core/readers.d.ts +7 -2
  49. package/dist/core/readers.d.ts.map +1 -1
  50. package/dist/core/run-order.d.ts +1 -1
  51. package/dist/core/run-order.d.ts.map +1 -1
  52. package/dist/core/scaffold/scaffold.d.ts +0 -1
  53. package/dist/core/scaffold/scaffold.d.ts.map +1 -1
  54. package/dist/core/scaffold/templates.d.ts +1 -1
  55. package/dist/core/scaffold/templates.d.ts.map +1 -1
  56. package/dist/core/sections.d.ts.map +1 -1
  57. package/dist/core/serialize/serializer.d.ts +4 -2
  58. package/dist/core/serialize/serializer.d.ts.map +1 -1
  59. package/dist/core/status/status.d.ts +8 -4
  60. package/dist/core/status/status.d.ts.map +1 -1
  61. package/dist/types/index.d.ts +65 -5
  62. package/dist/types/index.d.ts.map +1 -1
  63. package/dist/types/index.js.map +1 -1
  64. package/package.json +2 -2
  65. package/dist/app/assets/index-nuVVSVma.js.map +0 -1
  66. package/dist/app/assets/index-s6CVH54G.js +0 -6
  67. package/dist/app/assets/index-s6CVH54G.js.map +0 -1
  68. package/dist/app/assets/main--EStESJz.js +0 -217
  69. package/dist/app/assets/main--EStESJz.js.map +0 -1
  70. package/dist/app/assets/main-CAJuC1Pz.css +0 -1
  71. package/dist/app/assets/sidebar-section-DTU8Naqm.js +0 -2
  72. package/dist/app/assets/sidebar-section-DTU8Naqm.js.map +0 -1
  73. package/dist/chunks/phase-progress.CwSKAAZ-.js.map +0 -1
@@ -0,0 +1,2 @@
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
@@ -0,0 +1 @@
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":"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"}
@@ -1,2 +1,2 @@
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--EStESJz.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-B6251tL6.js.map
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 +1 @@
1
- {"version":3,"file":"index-B6251tL6.js","sources":["../../../src/app/services/content/stats-api.ts","../../../src/app/features/stats/stats-page.tsx"],"sourcesContent":["import type { ProjectStats } from '@/types/index';\nimport { apiUrl } from '../api-base';\n\nexport const fetchStats = async () => {\n const res = await fetch(apiUrl('/api/stats'));\n if (!res.ok) throw new Error(`Failed to fetch stats: ${res.status}`);\n return res.json() as Promise<ProjectStats>;\n};\n","import { PageTitle } from '@/app/components/page-title';\nimport { STATUS_LABEL } from '@/app/features/plans/constants';\nimport { fetchStats } from '@/app/services/content';\nimport { color } from '@/app/styles/tokens';\nimport { formatDuration, formatTokens } from '@/core/phase-run';\nimport { capacityLevel, resetsAtMs } from '@/core/rate-limit';\nimport { type EntityStatus, PLAN_STATUSES, type ProjectStats } from '@/types/index';\nimport { Card, Progress, Stamp } from '@dendelion/paper-ui';\nimport { useEffect, useState } from 'react';\n\nconst ENTITY_STATUS_ORDER: EntityStatus[] = ['open', ...PLAN_STATUSES];\n\nconst ENTITY_STATUS_LABEL: Record<EntityStatus, string> = { ...STATUS_LABEL, open: 'Open' };\n\nconst pct = (value: number) => `${Math.round(value * 100)}%`;\n\nconst StatCard = ({ title, children }: { title: string; children: React.ReactNode }) => (\n <div className=\"flex-[1_1_260px] max-w-[360px] max-[480px]:flex-[1_1_100%] max-[480px]:max-w-none\">\n <Card size=\"small\" texture=\"kraft\" className=\"h-full\">\n <div className=\"flex flex-col gap-3\">\n <span className=\"font-handwritten text-xs font-semibold opacity-[0.55]\">{title}</span>\n {children}\n </div>\n </Card>\n </div>\n);\n\nconst StatRow = ({ label, value }: { label: string; value: React.ReactNode }) => (\n <div className=\"flex items-center justify-between gap-2\">\n <span className=\"opacity-70\">{label}</span>\n <span className=\"font-semibold\">{value}</span>\n </div>\n);\n\nconst CommentRatioCard = ({ comments }: { comments: ProjectStats['comments'] }) => (\n <StatCard title=\"Comment ratio\">\n <Progress value={comments.ratio * 100} color={color.accentSlate} />\n <StatRow label=\"Ratio\" value={pct(comments.ratio)} />\n <StatRow label=\"Comment lines\" value={comments.commentLines} />\n <StatRow label=\"Source lines\" value={comments.sourceLines} />\n </StatCard>\n);\n\nconst TestCoverageCard = ({ testCoveragePct }: { testCoveragePct: number | null }) => (\n <StatCard title=\"Test coverage\">\n {testCoveragePct === null ? (\n <p className=\"opacity-50 m-0\">Run `pnpm test` to generate a coverage report.</p>\n ) : (\n <>\n <Progress value={testCoveragePct} color={color.accentGreen} />\n <StatRow label=\"Line coverage\" value={`${Math.round(testCoveragePct)}%`} />\n </>\n )}\n </StatCard>\n);\n\nconst CodebaseSizeCard = ({\n sourceLines,\n testLines,\n}: { sourceLines: number; testLines: number }) => (\n <StatCard title=\"Codebase size\">\n <StatRow label=\"Source lines\" value={sourceLines} />\n <StatRow label=\"Test lines\" value={testLines} />\n </StatCard>\n);\n\nconst EntitiesByStatusCard = ({\n entitiesByStatus,\n}: { entitiesByStatus: ProjectStats['entitiesByStatus'] }) => (\n <StatCard title=\"Entities by status\">\n {ENTITY_STATUS_ORDER.filter((status) => entitiesByStatus[status]).map((status) => (\n <StatRow key={status} label={ENTITY_STATUS_LABEL[status]} value={entitiesByStatus[status]} />\n ))}\n {Object.keys(entitiesByStatus).length === 0 && (\n <p className=\"opacity-50 m-0\">No entities yet.</p>\n )}\n </StatCard>\n);\n\nconst NotesCard = ({ openQuestions, decisions }: { openQuestions: number; decisions: number }) => (\n <StatCard title=\"Notes\">\n <StatRow label=\"Open questions\" value={openQuestions} />\n <StatRow label=\"Decisions\" value={decisions} />\n </StatCard>\n);\n\nconst TasksPerWeekCard = ({ tasksPerWeek }: { tasksPerWeek: ProjectStats['tasksPerWeek'] }) => {\n const max = Math.max(1, ...tasksPerWeek.map((w) => w.count));\n return (\n <StatCard title=\"Tasks per week\">\n {tasksPerWeek.length === 0 && <p className=\"opacity-50 m-0\">No tasks run yet.</p>}\n {tasksPerWeek.map((week) => (\n <div key={week.week} className=\"flex flex-col gap-1\">\n <StatRow label={week.week} value={week.count} />\n <Progress value={week.count} max={max} color={color.accentAmber} height={4} />\n </div>\n ))}\n </StatCard>\n );\n};\n\nconst UsagePerWeekCard = ({ usagePerWeek }: { usagePerWeek: ProjectStats['usagePerWeek'] }) => {\n const max = Math.max(1, ...usagePerWeek.map((w) => w.agentMinutes));\n return (\n <StatCard title=\"Usage per week\">\n {usagePerWeek.length === 0 && <p className=\"opacity-50 m-0\">No usage recorded yet.</p>}\n {usagePerWeek.map((week) => (\n <div key={week.week} className=\"flex flex-col gap-1\">\n <StatRow label={week.week} value={`${week.agentMinutes}m`} />\n <Progress value={week.agentMinutes} max={max} color={color.accentAmber} height={4} />\n <span className=\"text-2xs opacity-50\">\n {formatTokens(week.inputTokens)} in · {formatTokens(week.outputTokens)} out\n </span>\n </div>\n ))}\n </StatCard>\n );\n};\n\nconst MedianPhaseDurationCard = ({\n medianPhaseDurationMs,\n}: { medianPhaseDurationMs: number | null }) => (\n <StatCard title=\"Median phase duration\">\n {medianPhaseDurationMs === null ? (\n <p className=\"opacity-50 m-0\">No phase runs recorded yet.</p>\n ) : (\n <StatRow label=\"Median\" value={formatDuration(medianPhaseDurationMs)} />\n )}\n </StatCard>\n);\n\nconst MostExpensiveIdeasCard = ({\n mostExpensiveIdeas,\n}: { mostExpensiveIdeas: ProjectStats['mostExpensiveIdeas'] }) => (\n <StatCard title=\"Most expensive ideas\">\n {mostExpensiveIdeas.length === 0 && <p className=\"opacity-50 m-0\">No usage recorded yet.</p>}\n {mostExpensiveIdeas.map((idea) => (\n <div key={idea.planId} className=\"flex flex-col gap-0.5\">\n <StatRow\n label={idea.planId}\n value={`${formatTokens(idea.inputTokens)} in · ${formatTokens(idea.outputTokens)} out`}\n />\n <span className=\"text-2xs opacity-50 truncate\">{idea.planTitle}</span>\n </div>\n ))}\n </StatCard>\n);\n\nconst CAPACITY_STAMP = {\n allowed: 'success',\n warning: 'warning',\n rejected: 'error',\n} as const;\n\nconst ClaudeCapacityCard = ({ capacity }: { capacity: ProjectStats['capacity'] }) => {\n const [now, setNow] = useState(() => Date.now());\n useEffect(() => {\n const id = setInterval(() => setNow(Date.now()), 30_000);\n return () => clearInterval(id);\n }, []);\n return (\n <StatCard title=\"Claude capacity\">\n {capacity === null ? (\n <p className=\"opacity-50 m-0\">No agent run has reported capacity yet.</p>\n ) : (\n <>\n <div className=\"flex items-center gap-2\">\n <Stamp size=\"small\" variant={CAPACITY_STAMP[capacityLevel(capacity.snapshot.status)]}>\n {capacity.snapshot.status}\n </Stamp>\n {capacity.snapshot.overage && (\n <Stamp size=\"small\" variant=\"warning\">\n overage\n </Stamp>\n )}\n </div>\n {capacity.snapshot.rateLimitType && (\n <StatRow label=\"Window\" value={capacity.snapshot.rateLimitType} />\n )}\n {capacity.snapshot.resetsAt !== undefined && (\n <StatRow\n label=\"Resets\"\n value={new Date(resetsAtMs(capacity.snapshot.resetsAt)).toLocaleTimeString()}\n />\n )}\n <span className=\"text-2xs opacity-50\">\n as of last agent run,{' '}\n {formatDuration(Math.max(0, now - Date.parse(capacity.capturedAt)))} ago\n </span>\n </>\n )}\n </StatCard>\n );\n};\n\nexport const StatsPage = () => {\n const [stats, setStats] = useState<ProjectStats | null>(null);\n const [loadFailed, setLoadFailed] = useState(false);\n\n useEffect(() => {\n fetchStats()\n .then(setStats)\n .catch(() => setLoadFailed(true));\n }, []);\n\n return (\n <div>\n <PageTitle>Stats</PageTitle>\n <p className=\"opacity-50 mb-6\">\n A read-only look at project health — every number here is informational, none of them gate\n anything.\n </p>\n {loadFailed && <p className=\"opacity-50\">Couldn't load stats.</p>}\n {!loadFailed && !stats && <p className=\"opacity-50\">Loading…</p>}\n {stats && (\n <>\n <div className=\"flex flex-wrap items-start gap-2.5\">\n <CommentRatioCard comments={stats.comments} />\n <TestCoverageCard testCoveragePct={stats.testCoveragePct} />\n <CodebaseSizeCard\n sourceLines={stats.comments.sourceLines}\n testLines={stats.testLines}\n />\n <EntitiesByStatusCard entitiesByStatus={stats.entitiesByStatus} />\n <NotesCard openQuestions={stats.openQuestions} decisions={stats.decisions} />\n <TasksPerWeekCard tasksPerWeek={stats.tasksPerWeek} />\n <UsagePerWeekCard usagePerWeek={stats.usagePerWeek} />\n <MedianPhaseDurationCard medianPhaseDurationMs={stats.medianPhaseDurationMs} />\n <MostExpensiveIdeasCard mostExpensiveIdeas={stats.mostExpensiveIdeas} />\n <ClaudeCapacityCard capacity={stats.capacity} />\n </div>\n <p className=\"opacity-[0.4] text-2xs mt-6\">\n Generated {new Date(stats.generatedAt).toLocaleString()}\n </p>\n </>\n )}\n </div>\n );\n};\n"],"names":["fetchStats","res","apiUrl","ENTITY_STATUS_ORDER","PLAN_STATUSES","ENTITY_STATUS_LABEL","STATUS_LABEL","pct","value","StatCard","title","children","jsx","Card","jsxs","StatRow","label","CommentRatioCard","comments","Progress","color","TestCoverageCard","testCoveragePct","Fragment","CodebaseSizeCard","sourceLines","testLines","EntitiesByStatusCard","entitiesByStatus","status","NotesCard","openQuestions","decisions","TasksPerWeekCard","tasksPerWeek","max","w","week","UsagePerWeekCard","usagePerWeek","formatTokens","MedianPhaseDurationCard","medianPhaseDurationMs","formatDuration","MostExpensiveIdeasCard","mostExpensiveIdeas","idea","CAPACITY_STAMP","ClaudeCapacityCard","capacity","now","setNow","useState","useEffect","id","Stamp","capacityLevel","resetsAtMs","StatsPage","stats","setStats","loadFailed","setLoadFailed","PageTitle"],"mappings":"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"}
1
+ {"version":3,"file":"index-BHmWkgBW.js","sources":["../../../src/app/services/content/stats-api.ts","../../../src/app/features/stats/stats-page.tsx"],"sourcesContent":["import type { ProjectStats } from '@/types/index';\nimport { apiUrl } from '../api-base';\n\nexport const fetchStats = async () => {\n const res = await fetch(apiUrl('/api/stats'));\n if (!res.ok) throw new Error(`Failed to fetch stats: ${res.status}`);\n return res.json() as Promise<ProjectStats>;\n};\n","import { PageTitle } from '@/app/components/page-title';\nimport { STATUS_LABEL } from '@/app/features/plans/constants';\nimport { fetchStats } from '@/app/services/content';\nimport { color } from '@/app/styles/tokens';\nimport { formatDuration, formatTokens } from '@/core/phase-run';\nimport { capacityLevel, resetsAtMs } from '@/core/rate-limit';\nimport { type EntityStatus, PLAN_STATUSES, type ProjectStats } from '@/types/index';\nimport { Card, Progress, Stamp } from '@dendelion/paper-ui';\nimport { useEffect, useState } from 'react';\n\nconst ENTITY_STATUS_ORDER: EntityStatus[] = ['open', ...PLAN_STATUSES];\n\nconst ENTITY_STATUS_LABEL: Record<EntityStatus, string> = { ...STATUS_LABEL, open: 'Open' };\n\nconst pct = (value: number) => `${Math.round(value * 100)}%`;\n\nconst StatCard = ({ title, children }: { title: string; children: React.ReactNode }) => (\n <div className=\"flex-[1_1_260px] max-w-[360px] max-[480px]:flex-[1_1_100%] max-[480px]:max-w-none\">\n <Card size=\"small\" texture=\"kraft\" className=\"h-full\">\n <div className=\"flex flex-col gap-3\">\n <span className=\"font-handwritten text-xs font-semibold opacity-[0.55]\">{title}</span>\n {children}\n </div>\n </Card>\n </div>\n);\n\nconst StatRow = ({ label, value }: { label: string; value: React.ReactNode }) => (\n <div className=\"flex items-center justify-between gap-2\">\n <span className=\"opacity-70\">{label}</span>\n <span className=\"font-semibold\">{value}</span>\n </div>\n);\n\nconst CommentRatioCard = ({ comments }: { comments: ProjectStats['comments'] }) => (\n <StatCard title=\"Comment ratio\">\n <Progress value={comments.ratio * 100} color={color.accentSlate} />\n <StatRow label=\"Ratio\" value={pct(comments.ratio)} />\n <StatRow label=\"Comment lines\" value={comments.commentLines} />\n <StatRow label=\"Source lines\" value={comments.sourceLines} />\n </StatCard>\n);\n\nconst TestCoverageCard = ({ testCoveragePct }: { testCoveragePct: number | null }) => (\n <StatCard title=\"Test coverage\">\n {testCoveragePct === null ? (\n <p className=\"opacity-50 m-0\">Run `pnpm test` to generate a coverage report.</p>\n ) : (\n <>\n <Progress value={testCoveragePct} color={color.accentGreen} />\n <StatRow label=\"Line coverage\" value={`${Math.round(testCoveragePct)}%`} />\n </>\n )}\n </StatCard>\n);\n\nconst CodebaseSizeCard = ({\n sourceLines,\n testLines,\n}: { sourceLines: number; testLines: number }) => (\n <StatCard title=\"Codebase size\">\n <StatRow label=\"Source lines\" value={sourceLines} />\n <StatRow label=\"Test lines\" value={testLines} />\n </StatCard>\n);\n\nconst EntitiesByStatusCard = ({\n entitiesByStatus,\n}: { entitiesByStatus: ProjectStats['entitiesByStatus'] }) => (\n <StatCard title=\"Entities by status\">\n {ENTITY_STATUS_ORDER.filter((status) => entitiesByStatus[status]).map((status) => (\n <StatRow key={status} label={ENTITY_STATUS_LABEL[status]} value={entitiesByStatus[status]} />\n ))}\n {Object.keys(entitiesByStatus).length === 0 && (\n <p className=\"opacity-50 m-0\">No entities yet.</p>\n )}\n </StatCard>\n);\n\nconst NotesCard = ({ openQuestions, decisions }: { openQuestions: number; decisions: number }) => (\n <StatCard title=\"Notes\">\n <StatRow label=\"Open questions\" value={openQuestions} />\n <StatRow label=\"Decisions\" value={decisions} />\n </StatCard>\n);\n\nconst TasksPerWeekCard = ({ tasksPerWeek }: { tasksPerWeek: ProjectStats['tasksPerWeek'] }) => {\n const max = Math.max(1, ...tasksPerWeek.map((w) => w.count));\n return (\n <StatCard title=\"Tasks per week\">\n {tasksPerWeek.length === 0 && <p className=\"opacity-50 m-0\">No tasks run yet.</p>}\n {tasksPerWeek.map((week) => (\n <div key={week.week} className=\"flex flex-col gap-1\">\n <StatRow label={week.week} value={week.count} />\n <Progress value={week.count} max={max} color={color.accentAmber} height={4} />\n </div>\n ))}\n </StatCard>\n );\n};\n\nconst UsagePerWeekCard = ({ usagePerWeek }: { usagePerWeek: ProjectStats['usagePerWeek'] }) => {\n const max = Math.max(1, ...usagePerWeek.map((w) => w.agentMinutes));\n return (\n <StatCard title=\"Usage per week\">\n {usagePerWeek.length === 0 && <p className=\"opacity-50 m-0\">No usage recorded yet.</p>}\n {usagePerWeek.map((week) => (\n <div key={week.week} className=\"flex flex-col gap-1\">\n <StatRow label={week.week} value={`${week.agentMinutes}m`} />\n <Progress value={week.agentMinutes} max={max} color={color.accentAmber} height={4} />\n <span className=\"text-2xs opacity-50\">\n {formatTokens(week.inputTokens)} in · {formatTokens(week.outputTokens)} out\n </span>\n </div>\n ))}\n </StatCard>\n );\n};\n\nconst MedianPhaseDurationCard = ({\n medianPhaseDurationMs,\n}: { medianPhaseDurationMs: number | null }) => (\n <StatCard title=\"Median phase duration\">\n {medianPhaseDurationMs === null ? (\n <p className=\"opacity-50 m-0\">No phase runs recorded yet.</p>\n ) : (\n <StatRow label=\"Median\" value={formatDuration(medianPhaseDurationMs)} />\n )}\n </StatCard>\n);\n\nconst MostExpensiveIdeasCard = ({\n mostExpensiveIdeas,\n}: { mostExpensiveIdeas: ProjectStats['mostExpensiveIdeas'] }) => (\n <StatCard title=\"Most expensive ideas\">\n {mostExpensiveIdeas.length === 0 && <p className=\"opacity-50 m-0\">No usage recorded yet.</p>}\n {mostExpensiveIdeas.map((idea) => (\n <div key={idea.planId} className=\"flex flex-col gap-0.5\">\n <StatRow\n label={idea.planId}\n value={`${formatTokens(idea.inputTokens)} in · ${formatTokens(idea.outputTokens)} out`}\n />\n <span className=\"text-2xs opacity-50 truncate\">{idea.planTitle}</span>\n </div>\n ))}\n </StatCard>\n);\n\nconst CAPACITY_STAMP = {\n allowed: 'success',\n warning: 'warning',\n rejected: 'error',\n} as const;\n\nconst ClaudeCapacityCard = ({ capacity }: { capacity: ProjectStats['capacity'] }) => {\n const [now, setNow] = useState(() => Date.now());\n useEffect(() => {\n const id = setInterval(() => setNow(Date.now()), 30_000);\n return () => clearInterval(id);\n }, []);\n return (\n <StatCard title=\"Claude capacity\">\n {capacity === null ? (\n <p className=\"opacity-50 m-0\">No agent run has reported capacity yet.</p>\n ) : (\n <>\n <div className=\"flex items-center gap-2\">\n <Stamp size=\"small\" variant={CAPACITY_STAMP[capacityLevel(capacity.snapshot.status)]}>\n {capacity.snapshot.status}\n </Stamp>\n {capacity.snapshot.overage && (\n <Stamp size=\"small\" variant=\"warning\">\n overage\n </Stamp>\n )}\n </div>\n {capacity.snapshot.rateLimitType && (\n <StatRow label=\"Window\" value={capacity.snapshot.rateLimitType} />\n )}\n {capacity.snapshot.resetsAt !== undefined && (\n <StatRow\n label=\"Resets\"\n value={new Date(resetsAtMs(capacity.snapshot.resetsAt)).toLocaleTimeString()}\n />\n )}\n <span className=\"text-2xs opacity-50\">\n as of last agent run,{' '}\n {formatDuration(Math.max(0, now - Date.parse(capacity.capturedAt)))} ago\n </span>\n </>\n )}\n </StatCard>\n );\n};\n\nexport const StatsPage = () => {\n const [stats, setStats] = useState<ProjectStats | null>(null);\n const [loadFailed, setLoadFailed] = useState(false);\n\n useEffect(() => {\n fetchStats()\n .then(setStats)\n .catch(() => setLoadFailed(true));\n }, []);\n\n return (\n <div>\n <PageTitle>Stats</PageTitle>\n <p className=\"opacity-50 mb-6\">\n A read-only look at project health — every number here is informational, none of them gate\n anything.\n </p>\n {loadFailed && <p className=\"opacity-50\">Couldn't load stats.</p>}\n {!loadFailed && !stats && <p className=\"opacity-50\">Loading…</p>}\n {stats && (\n <>\n <div className=\"flex flex-wrap items-start gap-2.5\">\n <CommentRatioCard comments={stats.comments} />\n <TestCoverageCard testCoveragePct={stats.testCoveragePct} />\n <CodebaseSizeCard\n sourceLines={stats.comments.sourceLines}\n testLines={stats.testLines}\n />\n <EntitiesByStatusCard entitiesByStatus={stats.entitiesByStatus} />\n <NotesCard openQuestions={stats.openQuestions} decisions={stats.decisions} />\n <TasksPerWeekCard tasksPerWeek={stats.tasksPerWeek} />\n <UsagePerWeekCard usagePerWeek={stats.usagePerWeek} />\n <MedianPhaseDurationCard medianPhaseDurationMs={stats.medianPhaseDurationMs} />\n <MostExpensiveIdeasCard mostExpensiveIdeas={stats.mostExpensiveIdeas} />\n <ClaudeCapacityCard capacity={stats.capacity} />\n </div>\n <p className=\"opacity-[0.4] text-2xs mt-6\">\n Generated {new Date(stats.generatedAt).toLocaleString()}\n </p>\n </>\n )}\n </div>\n );\n};\n"],"names":["fetchStats","res","apiUrl","ENTITY_STATUS_ORDER","PLAN_STATUSES","ENTITY_STATUS_LABEL","STATUS_LABEL","pct","value","StatCard","title","children","jsx","Card","jsxs","StatRow","label","CommentRatioCard","comments","Progress","color","TestCoverageCard","testCoveragePct","Fragment","CodebaseSizeCard","sourceLines","testLines","EntitiesByStatusCard","entitiesByStatus","status","NotesCard","openQuestions","decisions","TasksPerWeekCard","tasksPerWeek","max","w","week","UsagePerWeekCard","usagePerWeek","formatTokens","MedianPhaseDurationCard","medianPhaseDurationMs","formatDuration","MostExpensiveIdeasCard","mostExpensiveIdeas","idea","CAPACITY_STAMP","ClaudeCapacityCard","capacity","now","setNow","useState","useEffect","id","Stamp","capacityLevel","resetsAtMs","StatsPage","stats","setStats","loadFailed","setLoadFailed","PageTitle"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- import{j as a,af as j,C as w,r as u,ag as v,ah as I,ai as E,aj as $,ak as C,aa as R,J as D,al as P,u as x,a as T,P as S,N as k}from"./main--EStESJz.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(j,{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:g,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 g(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(j,{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()])},g=(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=()=>g(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(j,{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-DLkUANCR.js.map
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 +1 @@
1
- {"version":3,"file":"index-DLkUANCR.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
+ {"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 +1,2 @@
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--EStESJz.js";import{S as V}from"./sidebar-section-DTU8Naqm.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",active:s===null,onClick:()=>t({to:"/settings"}),children:"Project Info"}),e.jsx(L,{size:"small",active:s==="setup",onClick:()=>t({to:"/settings/$section",params:{section:"setup"}}),children:"Setup"}),e.jsx(L,{size:"small",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",active:s==="subjects",onClick:()=>t({to:"/settings/$section",params:{section:"subjects"}}),children:"Subjects"})})]})};export{Pe as SettingsPage,Fe as SettingsSidebar};
2
- //# sourceMappingURL=index-D5j1njQR.js.map
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