@dendelion/paper-camp 0.17.1 → 0.18.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/dist/app/assets/index-0_VbynEu.js +4 -0
  2. package/dist/app/assets/index-0_VbynEu.js.map +1 -0
  3. package/dist/app/assets/{index-DJwR0UyW.js → index-Bm61MVP8.js} +2 -2
  4. package/dist/app/assets/{index-DJwR0UyW.js.map → index-Bm61MVP8.js.map} +1 -1
  5. package/dist/app/assets/index-CHYyIAaz.js +6 -0
  6. package/dist/app/assets/index-CHYyIAaz.js.map +1 -0
  7. package/dist/app/assets/index-DNRXB82k.js +2 -0
  8. package/dist/app/assets/{index-_tpcAf-k.js.map → index-DNRXB82k.js.map} +1 -1
  9. package/dist/app/assets/index-Dz77-ZQi.js +3 -0
  10. package/dist/app/assets/index-Dz77-ZQi.js.map +1 -0
  11. package/dist/app/assets/{index-C4T8Gzjs.js → index-NfzGv6aM.js} +3 -3
  12. package/dist/app/assets/{index-C4T8Gzjs.js.map → index-NfzGv6aM.js.map} +1 -1
  13. package/dist/app/assets/index-pnCSf_Qk.js +2 -0
  14. package/dist/app/assets/index-pnCSf_Qk.js.map +1 -0
  15. package/dist/app/assets/main-AfQrNPNu.js +217 -0
  16. package/dist/app/assets/main-AfQrNPNu.js.map +1 -0
  17. package/dist/app/assets/main-BuCfycKH.css +1 -0
  18. package/dist/app/assets/{sidebar-section-DxUjM4Rm.js → sidebar-section-AupYBPL3.js} +2 -2
  19. package/dist/app/assets/{sidebar-section-DxUjM4Rm.js.map → sidebar-section-AupYBPL3.js.map} +1 -1
  20. package/dist/app/index.html +2 -2
  21. package/dist/app/toolbar.js +7957 -7882
  22. package/dist/app/toolbar.js.map +1 -1
  23. package/dist/chunks/{phase-progress.DsBJvd_o.js → phase-progress.BIIDRJmI.js} +2783 -2365
  24. package/dist/chunks/phase-progress.BIIDRJmI.js.map +1 -0
  25. package/dist/cli/index.js +9950 -9104
  26. package/dist/cli/index.js.map +1 -1
  27. package/dist/core/ci/ci-release.d.ts +25 -0
  28. package/dist/core/ci/ci-release.d.ts.map +1 -0
  29. package/dist/core/ci/ci-release.test.d.ts +2 -0
  30. package/dist/core/ci/ci-release.test.d.ts.map +1 -0
  31. package/dist/core/ci/index.d.ts +2 -0
  32. package/dist/core/ci/index.d.ts.map +1 -0
  33. package/dist/core/doctor/checks/metadata.d.ts +3 -0
  34. package/dist/core/doctor/checks/metadata.d.ts.map +1 -0
  35. package/dist/core/doctor/checks/metadata.test.d.ts +2 -0
  36. package/dist/core/doctor/checks/metadata.test.d.ts.map +1 -0
  37. package/dist/core/doctor/checks/structural.d.ts +3 -0
  38. package/dist/core/doctor/checks/structural.d.ts.map +1 -0
  39. package/dist/core/doctor/checks/structural.test.d.ts +2 -0
  40. package/dist/core/doctor/checks/structural.test.d.ts.map +1 -0
  41. package/dist/core/doctor/doctor.d.ts +18 -0
  42. package/dist/core/doctor/doctor.d.ts.map +1 -0
  43. package/dist/core/doctor/finding.d.ts +25 -0
  44. package/dist/core/doctor/finding.d.ts.map +1 -0
  45. package/dist/core/doctor/finding.test.d.ts +2 -0
  46. package/dist/core/doctor/finding.test.d.ts.map +1 -0
  47. package/dist/core/doctor/fix.d.ts +25 -0
  48. package/dist/core/doctor/fix.d.ts.map +1 -0
  49. package/dist/core/doctor/fix.test.d.ts +2 -0
  50. package/dist/core/doctor/fix.test.d.ts.map +1 -0
  51. package/dist/core/doctor/index.d.ts +5 -0
  52. package/dist/core/doctor/index.d.ts.map +1 -0
  53. package/dist/core/doctor/rules.d.ts +52 -0
  54. package/dist/core/doctor/rules.d.ts.map +1 -0
  55. package/dist/core/index.d.ts +1 -0
  56. package/dist/core/index.d.ts.map +1 -1
  57. package/dist/core/index.js +120 -102
  58. package/dist/core/notifications.d.ts +6 -0
  59. package/dist/core/notifications.d.ts.map +1 -0
  60. package/dist/core/notifications.test.d.ts +2 -0
  61. package/dist/core/notifications.test.d.ts.map +1 -0
  62. package/dist/core/parse/desk-schema.test.d.ts +2 -0
  63. package/dist/core/parse/desk-schema.test.d.ts.map +1 -0
  64. package/dist/core/parse/parser.d.ts +2 -1
  65. package/dist/core/parse/parser.d.ts.map +1 -1
  66. package/dist/core/parse/schemas.d.ts +65 -0
  67. package/dist/core/parse/schemas.d.ts.map +1 -1
  68. package/dist/types/index.d.ts +96 -1
  69. package/dist/types/index.d.ts.map +1 -1
  70. package/dist/types/index.js.map +1 -1
  71. package/dist/vite/index.d.ts.map +1 -1
  72. package/dist/vite/index.js +47 -40
  73. package/dist/vite/index.js.map +1 -1
  74. package/package.json +2 -3
  75. package/dist/app/assets/index-BXRfFWnQ.js +0 -4
  76. package/dist/app/assets/index-BXRfFWnQ.js.map +0 -1
  77. package/dist/app/assets/index-BrriNOla.js +0 -2
  78. package/dist/app/assets/index-BrriNOla.js.map +0 -1
  79. package/dist/app/assets/index-CSl9MOCi.js +0 -6
  80. package/dist/app/assets/index-CSl9MOCi.js.map +0 -1
  81. package/dist/app/assets/index-D0gBw6x1.js +0 -3
  82. package/dist/app/assets/index-D0gBw6x1.js.map +0 -1
  83. package/dist/app/assets/index-_tpcAf-k.js +0 -2
  84. package/dist/app/assets/main-Ll-lvqgh.css +0 -1
  85. package/dist/app/assets/main-Zb7gEID9.js +0 -207
  86. package/dist/app/assets/main-Zb7gEID9.js.map +0 -1
  87. package/dist/chunks/phase-progress.DsBJvd_o.js.map +0 -1
@@ -0,0 +1,4 @@
1
+ import{u as g,m as L,r as c,C as O,j as e,F as P,v as T,J as y,G as M,H as B,a as D,z as F,P as k,I as U,K as $,M as A,g as w,S as N,L as q,R as _,N as V,O as H,i as z,Q as G,T as J,U as K,p as I}from"./main-AfQrNPNu.js";import{V as xe}from"./main-AfQrNPNu.js";const C="__no-subject__",Q=({horizonTitle:s,item:t,candidateName:o,onClose:d,onPromoted:a})=>{const m=g(x=>x.promoteRoadmapItem),i=g(x=>x.launchIdeaExtend),{subjects:b,available:j}=L(),[u,h]=c.useState(C),[f,n]=c.useState(!1),[p,v]=c.useState(null),{toast:l}=O();c.useEffect(()=>{t&&(h(o?t.name:C),n(!1),v(null))},[t,o]);const r=async()=>{if(!(!t||!s)){n(!0),v(null);try{const x=await m(s,t,u===C?void 0:u,o??void 0),S=g.getState().ideaEntries.find(R=>R.id===x);if(S)try{await i(x,M(S,s))}catch(R){l({title:"Idea created, but the refine agent failed to launch",description:B(R.message),variant:"error"})}a(),d()}catch(x){v(x.message)}finally{n(!1)}}};return e.jsx(P,{open:t!==null,onClose:d,title:o??(t==null?void 0:t.name)??"",size:"small",children:e.jsxs("div",{className:"flex flex-col gap-4",children:[!o&&e.jsx("p",{className:"m-0 opacity-80",children:t==null?void 0:t.description}),e.jsx("div",{children:e.jsx(T,{size:"small",value:u,onChange:h,disabled:f||!j,options:[{value:C,label:"No subject"},...!j&&u!==C?[{value:u,label:u}]:[],...b.map(x=>({value:x,label:x}))]})}),p&&e.jsx("p",{className:"m-0 text-watercolor-rose-dark text-sm",children:p}),e.jsxs("div",{className:"flex justify-end gap-2",children:[e.jsx(y,{type:"button",variant:"ghost",onClick:d,disabled:f,children:"Cancel"}),e.jsx(y,{type:"button",variant:"primary",onClick:r,disabled:f,children:f?"Promoting…":"Promote to idea"})]})]})})},W=s=>({shipped:s.filter(t=>t.status==="done").length,queued:s.filter(t=>t.status!=="done"&&t.status!=="dropped").length}),Z="font-handwritten text-md font-semibold opacity-70 leading-none pt-2 px-1 pb-0",X=({size:s=14})=>e.jsx("svg",{width:s,height:s,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round","aria-hidden":"true",children:e.jsx("polyline",{points:"9 18 15 12 9 6"})}),Y=({name:s,onPromote:t})=>e.jsx(_,{size:"small",texture:"kraft",className:"plan-row-card",children:e.jsxs("div",{className:"flex items-center gap-3",children:[e.jsx("span",{className:"flex-1",children:s}),e.jsx(y,{type:"button",variant:"ghost",size:"small",onClick:t,children:"Promote to idea"})]})}),ee=({onAdd:s})=>{const[t,o]=c.useState(""),[d,a]=c.useState(!1),m=async()=>{if(t.trim()){a(!0);try{await s(t.trim()),o("")}finally{a(!1)}}};return e.jsxs("div",{className:"flex gap-2",children:[e.jsx(z,{size:"small",value:t,onChange:i=>o(i.target.value),onKeyDown:i=>{i.key==="Enter"&&m()},placeholder:"Add option…",disabled:d,className:"flex-1"}),e.jsx(y,{type:"button",variant:"ghost",size:"small",onClick:m,disabled:d||!t.trim(),children:"Add"})]})},se=(s,t)=>{const o=new Set,d=t.map(a=>(a.id&&o.add(a.id),a.idea&&o.add(a.idea),{key:a.title,label:a.title,status:a.status,pr:a.pr,planId:a.id,planTitle:a.title}));for(const a of s)o.has(a.id)||d.push({key:a.id,label:a.id,status:a.status,pr:a.pr});return d},te=({idea:s,onOpen:t})=>e.jsx(_,{size:"small",texture:"kraft",className:"plan-row-card",children:e.jsxs("div",{className:"flex items-center gap-3",children:[t?e.jsx("button",{type:"button",onClick:t,className:"flex-1 min-w-0 overflow-hidden text-ellipsis whitespace-nowrap bg-transparent border-none p-0 cursor-pointer [font:inherit] text-inherit text-left",children:s.label}):e.jsx("span",{className:"flex-1 min-w-0 overflow-hidden text-ellipsis whitespace-nowrap",children:s.label}),s.pr&&e.jsx(V,{pr:s.pr}),e.jsx(w,{size:"small",fillColor:N[s.status].fill,textColor:N[s.status].text,children:H[s.status]})]})}),ae=({done:s,total:t})=>{const o=t>0?Math.round(s/t*100):0;return e.jsx("div",{className:"w-24 h-1.5 rounded-full bg-black/[0.08] overflow-hidden shrink-0",children:e.jsx("div",{className:`h-full rounded-full ${t>0?"bg-watercolor-green":"bg-transparent"}`,style:{width:`${o}%`}})})},ne=({item:s,graduated:t,highlighted:o,onPromote:d,onPromoteCandidate:a,onAddCandidate:m,onOpenGraduated:i})=>{const[b,j]=c.useState(o),{shipped:u,queued:h}=W(t),f=s.candidates.length,n=se(s.links,t);return c.useEffect(()=>{o&&j(!0)},[o]),e.jsxs("div",{className:`border-b border-black/10 last:border-b-0 ${o?"roadmap-item-highlighted outline outline-2 outline-offset-[-2px] outline-[rgba(200,154,90,0.5)]":""}`,children:[e.jsxs("button",{type:"button","aria-expanded":b,onClick:()=>j(p=>!p),className:"flex w-full items-center gap-3 py-2.5 px-0 bg-transparent border-none cursor-pointer text-left [font:inherit] text-inherit",children:[e.jsx("span",{className:`inline-flex items-center opacity-50 shrink-0 ${b?"rotate-90":""}`,children:e.jsx(X,{})}),e.jsx("span",{className:"font-semibold flex-1 min-w-0 truncate",children:s.name}),e.jsx(ae,{done:s.rollup.done,total:s.rollup.total}),h>0&&e.jsxs(w,{size:"small",fillColor:N.planned.fill,textColor:N.planned.text,children:[h," in queue"]}),u>0&&e.jsxs(w,{size:"small",fillColor:N.done.fill,textColor:N.done.text,children:[u," shipped"]}),f>0&&e.jsxs(w,{size:"small",fillColor:"rgba(0, 0, 0, 0.06)",textColor:"rgba(0, 0, 0, 0.55)",children:[f," candidate",f===1?"":"s"]})]}),b&&e.jsxs("div",{className:"flex flex-col gap-1 pl-6 pb-4",children:[e.jsx("span",{className:"text-sm opacity-70",children:s.description}),n.map(p=>e.jsx(te,{idea:p,onOpen:p.planTitle?()=>i(p.planId,p.planTitle):void 0},p.key)),s.candidates.map(p=>e.jsx(Y,{name:p,onPromote:()=>a(p)},p)),e.jsx(ee,{onAdd:m}),e.jsx(y,{type:"button",variant:"ghost",size:"small",onClick:d,className:"self-start",children:"Promote to idea"})]})]})},le=({goal:s})=>{const[t,o]=c.useState(!1),[d,...a]=s.split(/\n{2,}/),m=a.length>0;return e.jsxs("div",{className:"mb-6 pb-4 border-b border-black/[8%]",children:[e.jsx("span",{className:"font-handwritten text-xs font-semibold opacity-[0.55]",children:"The goal"}),e.jsxs("div",{className:"text-sm leading-relaxed opacity-80 mt-2",children:[e.jsx(A,{children:d}),t&&m&&e.jsx(A,{children:a.join(`
2
+
3
+ `)})]}),m&&e.jsx("button",{type:"button",onClick:()=>o(i=>!i),className:"bg-transparent border-none p-0 mt-2 cursor-pointer text-2xs opacity-[0.65] underline",children:t?"Show less":"Show more"})]})},de=()=>{const s=g(l=>l.roadmap),t=g(l=>l.roadmapError),o=g(l=>l.loadRoadmap),d=g(l=>l.roadmapFilters),[a,m]=c.useState(null),i=g(l=>l.plans),b=D(),{item:j}=F({from:"/roadmap"}),u=c.useRef(null);if(c.useEffect(()=>{o()},[o]),c.useEffect(()=>{var r;if(!j||!s)return;const l=(r=u.current)==null?void 0:r.querySelector(".roadmap-item-highlighted");l==null||l.scrollIntoView({behavior:"smooth",block:"center"})},[j,s]),t)return e.jsxs("div",{children:[e.jsx(k,{children:"Roadmap"}),e.jsx("p",{className:"opacity-50",children:"Couldn't load the roadmap — the server may need a restart to pick up new routes."})]});if(!s)return e.jsxs("div",{children:[e.jsx(k,{children:"Roadmap"}),e.jsx("p",{className:"opacity-50",children:"Loading…"})]});if(s.horizons.length===0)return e.jsxs("div",{children:[e.jsx(k,{children:"Roadmap"}),e.jsx("p",{className:"opacity-50",children:"No `ROADMAP.md` found at the project root."})]});const h=l=>(i==null?void 0:i.entries.filter(r=>r.subject===l.name))??[],f=async(l,r,x)=>{await q(l,r,x),await o()},n=U(s,d),p=n.reduce((l,r)=>l+r.items.length,0),v=d.horizons.length>0||d.statuses.length>0;return e.jsxs("div",{ref:u,children:[e.jsx(k,{children:"Roadmap"}),e.jsx(le,{goal:s.goal}),p===0?e.jsx("p",{className:"opacity-50 py-6 px-0 text-center",children:v?"Nothing matches these filters — clear one from the sidebar to see more.":"No roadmap items yet — add one from the sidebar."}):e.jsx("div",{className:"flex flex-col gap-6",children:n.filter(l=>l.items.length>0).map(l=>e.jsxs("div",{className:"flex flex-col gap-1",children:[e.jsx("div",{className:Z,children:l.title}),e.jsx("div",{className:"flex flex-col",children:l.items.map(r=>e.jsx(ne,{item:r,graduated:h(r),highlighted:r.name===j,onPromote:()=>m({horizonTitle:l.title,item:r}),onPromoteCandidate:x=>m({horizonTitle:l.title,item:r,candidateName:x}),onAddCandidate:x=>f(l.title,r.name,x),onOpenGraduated:(x,S)=>b({to:"/plans/$planId",params:{planId:$(x,S)}})},r.name))})]},l.title))}),e.jsx(Q,{horizonTitle:(a==null?void 0:a.horizonTitle)??null,item:(a==null?void 0:a.item)??null,candidateName:a==null?void 0:a.candidateName,onClose:()=>m(null),onPromoted:o})]})},oe=({open:s,horizonTitles:t,onClose:o})=>{const d=g(r=>r.loadRoadmap),[a,m]=c.useState(t[0]??""),[i,b]=c.useState(""),[j,u]=c.useState(""),[h,f]=c.useState(!1),[n,p]=c.useState(null),v=c.useRef(t);v.current=t,c.useEffect(()=>{s&&(m(v.current[0]??""),b(""),u(""),p(null),f(!1))},[s]);const l=async()=>{if(!(!i.trim()||!a)){f(!0),p(null);try{await G(a,i.trim(),j.trim()),await d(),o()}catch(r){p(r.message)}finally{f(!1)}}};return e.jsx(P,{open:s,onClose:o,title:"Add roadmap item",size:"small",children:e.jsxs("div",{className:"flex flex-col gap-4",children:[e.jsx(T,{size:"small",value:a,onChange:m,disabled:h,options:t.map(r=>({value:r,label:r}))}),e.jsx(z,{size:"small",value:i,onChange:r=>b(r.target.value),placeholder:"Item name…",disabled:h,autoFocus:!0}),e.jsx(z,{size:"small",value:j,onChange:r=>u(r.target.value),onKeyDown:r=>{r.key==="Enter"&&l()},placeholder:"Description…",disabled:h}),n&&e.jsx("p",{className:"m-0 text-watercolor-rose-dark text-sm",children:n}),e.jsxs("div",{className:"flex justify-end gap-2",children:[e.jsx(y,{type:"button",variant:"ghost",onClick:o,disabled:h,children:"Cancel"}),e.jsx(y,{type:"button",variant:"primary",onClick:l,disabled:h||!i.trim()||!a,children:h?"Adding…":"Add item"})]})]})})},re=["in-progress","review","planned","idea","done","dropped"],E="text-2xs font-semibold tracking-[0.08em] uppercase text-ink-300 mb-2",ce=()=>{const s=g(n=>n.roadmap),t=g(n=>n.roadmapFilters),o=g(n=>n.toggleRoadmapHorizon),d=g(n=>n.toggleRoadmapStatus),[a,m]=c.useState(!1);if(!s)return null;const i=s.horizons.map(n=>n.title),b=J(s,t),j=K(s,t),u=new Set(t.horizons),h=new Set(t.statuses),f=re.filter(n=>(j[n]??0)>0||h.has(n));return e.jsxs("div",{className:"flex flex-col gap-8 -mt-5",children:[e.jsx(y,{type:"button",variant:"primary",size:"small",onClick:()=>m(!0),disabled:i.length===0,children:"+ Add item"}),e.jsxs("div",{children:[e.jsx("div",{className:E,children:"Horizon"}),e.jsx("div",{className:"flex flex-col gap-1",children:i.map(n=>e.jsx(I,{size:"small",active:u.has(n),onClick:()=>o(n),className:"text-xs leading-4 py-2",action:e.jsx("span",{className:"text-2xs text-ink-500",children:b[n]??0}),children:n},n))})]}),e.jsxs("div",{children:[e.jsx("div",{className:E,children:"Status"}),e.jsxs("div",{className:"flex flex-col gap-1",children:[f.length===0&&e.jsx("span",{className:"opacity-50 text-2xs",children:"No linked ideas"}),f.map(n=>e.jsx(I,{size:"small",active:h.has(n),onClick:()=>d(n),className:"text-xs leading-4 py-2",icon:e.jsx("span",{className:"w-[9px] h-[9px] rounded-full shrink-0",style:{background:N[n].text}}),action:e.jsx("span",{className:"text-2xs text-ink-500",children:j[n]??0}),children:H[n]},n))]})]}),e.jsx(oe,{open:a,horizonTitles:i,onClose:()=>m(!1)})]})};export{de as RoadmapPage,ce as RoadmapSidebar,xe as useRoadmapItemNames};
4
+ //# sourceMappingURL=index-0_VbynEu.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index-0_VbynEu.js","sources":["../../../src/app/features/roadmap/promote-roadmap-item-modal.tsx","../../../src/app/features/roadmap/roadmap-page.tsx","../../../src/app/features/roadmap/add-roadmap-item-modal.tsx","../../../src/app/features/roadmap/roadmap-sidebar.tsx"],"sourcesContent":["import { buildRoadmapPromotePrompt } from '@/app/features/plans/prompts';\nimport { useSubjectVocabulary } from '@/app/hooks/use-subject-vocabulary';\nimport { useAppStore } from '@/app/stores/app-store';\nimport { oneLineErrorSummary } from '@/app/utils/error-summary';\nimport type { RoadmapItem } from '@/types/index';\nimport { Button, Modal, Select, useToast } from '@dendelion/paper-ui';\nimport { useEffect, useState } from 'react';\n\nconst NO_SUBJECT = '__no-subject__';\n\ninterface PromoteRoadmapItemModalProps {\n horizonTitle: string | null;\n item: RoadmapItem | null;\n candidateName?: string | null;\n onClose: () => void;\n onPromoted: () => void;\n}\n\nexport const PromoteRoadmapItemModal = ({\n horizonTitle,\n item,\n candidateName,\n onClose,\n onPromoted,\n}: PromoteRoadmapItemModalProps) => {\n const promoteRoadmapItem = useAppStore((s) => s.promoteRoadmapItem);\n const launchIdeaExtend = useAppStore((s) => s.launchIdeaExtend);\n const { subjects, available: subjectsAvailable } = useSubjectVocabulary();\n const [subject, setSubject] = useState(NO_SUBJECT);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const { toast } = useToast();\n\n useEffect(() => {\n if (item) {\n // A candidate defaults to its parent item's name — the \"big bet becomes a\n // Subject\" rule — but the picker still lets the user override it.\n setSubject(candidateName ? item.name : NO_SUBJECT);\n setLoading(false);\n setError(null);\n }\n }, [item, candidateName]);\n\n const handlePromote = async () => {\n if (!item || !horizonTitle) return;\n setLoading(true);\n setError(null);\n try {\n const id = await promoteRoadmapItem(\n horizonTitle,\n item,\n subject === NO_SUBJECT ? undefined : subject,\n candidateName ?? undefined,\n );\n const idea = useAppStore.getState().ideaEntries.find((e) => e.id === id);\n if (idea) {\n try {\n await launchIdeaExtend(id, buildRoadmapPromotePrompt(idea, horizonTitle));\n } catch (err) {\n // The idea was created either way — a failed refine launch just means\n // no background research ran, not that the promotion itself failed.\n toast({\n title: 'Idea created, but the refine agent failed to launch',\n description: oneLineErrorSummary((err as Error).message),\n variant: 'error',\n });\n }\n }\n onPromoted();\n onClose();\n } catch (err) {\n setError((err as Error).message);\n } finally {\n setLoading(false);\n }\n };\n\n return (\n <Modal\n open={item !== null}\n onClose={onClose}\n title={candidateName ?? item?.name ?? ''}\n size=\"small\"\n >\n <div className=\"flex flex-col gap-4\">\n {!candidateName && <p className=\"m-0 opacity-80\">{item?.description}</p>}\n <div>\n <Select\n size=\"small\"\n value={subject}\n onChange={setSubject}\n disabled={loading || !subjectsAvailable}\n options={[\n { value: NO_SUBJECT, label: 'No subject' },\n ...(!subjectsAvailable && subject !== NO_SUBJECT\n ? [{ value: subject, label: subject }]\n : []),\n ...subjects.map((s) => ({ value: s, label: s })),\n ]}\n />\n </div>\n {error && <p className=\"m-0 text-watercolor-rose-dark text-sm\">{error}</p>}\n <div className=\"flex justify-end gap-2\">\n <Button type=\"button\" variant=\"ghost\" onClick={onClose} disabled={loading}>\n Cancel\n </Button>\n <Button type=\"button\" variant=\"primary\" onClick={handlePromote} disabled={loading}>\n {loading ? 'Promoting…' : 'Promote to idea'}\n </Button>\n </div>\n </div>\n </Modal>\n );\n};\n","import { Markdown } from '@/app/components/markdown';\nimport { PageTitle } from '@/app/components/page-title';\nimport { PrBadge } from '@/app/features/plans/components/pr-badge';\nimport { STATUS_LABEL, STATUS_STAMP } from '@/app/features/plans/constants';\nimport { entityRouteParam } from '@/app/hooks';\nimport { addRoadmapCandidate } from '@/app/services/content/docs-api';\nimport { useAppStore } from '@/app/stores/app-store';\nimport type {\n PlanEntry,\n PlanStatus,\n PrInfo,\n ResolvedRoadmapItem,\n RoadmapLink,\n} from '@/types/index';\nimport { Button, Card, Input, Stamp } from '@dendelion/paper-ui';\nimport { useNavigate, useSearch } from '@tanstack/react-router';\nimport { useEffect, useRef, useState } from 'react';\nimport { PromoteRoadmapItemModal } from './promote-roadmap-item-modal';\nimport { filterHorizons } from './roadmap-filters';\n\nconst graduationCounts = (graduated: PlanEntry[]) => ({\n shipped: graduated.filter((p) => p.status === 'done').length,\n queued: graduated.filter((p) => p.status !== 'done' && p.status !== 'dropped').length,\n});\n\nconst HORIZON_HEADER_CLASSES =\n 'font-handwritten text-md font-semibold opacity-70 leading-none pt-2 px-1 pb-0';\n\nconst ChevronRightIcon = ({ size = 14 }: { size?: number }) => (\n <svg\n width={size}\n height={size}\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n aria-hidden=\"true\"\n >\n <polyline points=\"9 18 15 12 9 6\" />\n </svg>\n);\n\nconst CandidateRow = ({\n name,\n onPromote,\n}: {\n name: string;\n onPromote: () => void;\n}) => (\n <Card size=\"small\" texture=\"kraft\" className=\"plan-row-card\">\n <div className=\"flex items-center gap-3\">\n <span className=\"flex-1\">{name}</span>\n <Button type=\"button\" variant=\"ghost\" size=\"small\" onClick={onPromote}>\n Promote to idea\n </Button>\n </div>\n </Card>\n);\n\nconst AddCandidateForm = ({ onAdd }: { onAdd: (name: string) => Promise<void> }) => {\n const [name, setName] = useState('');\n const [saving, setSaving] = useState(false);\n\n const handleAdd = async () => {\n if (!name.trim()) return;\n setSaving(true);\n try {\n await onAdd(name.trim());\n setName('');\n } finally {\n setSaving(false);\n }\n };\n\n return (\n <div className=\"flex gap-2\">\n <Input\n size=\"small\"\n value={name}\n onChange={(e) => setName(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === 'Enter') handleAdd();\n }}\n placeholder=\"Add option…\"\n disabled={saving}\n className=\"flex-1\"\n />\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"small\"\n onClick={handleAdd}\n disabled={saving || !name.trim()}\n >\n Add\n </Button>\n </div>\n );\n};\n\ninterface MergedIdea {\n key: string;\n label: string;\n status: PlanStatus;\n pr?: PrInfo;\n planId?: string;\n planTitle?: string;\n}\n\nconst mergeIdeas = (links: RoadmapLink[], graduated: PlanEntry[]): MergedIdea[] => {\n const represented = new Set<string>();\n const merged: MergedIdea[] = graduated.map((plan) => {\n if (plan.id) represented.add(plan.id);\n if (plan.idea) represented.add(plan.idea);\n return {\n key: plan.title,\n label: plan.title,\n status: plan.status,\n pr: plan.pr,\n planId: plan.id,\n planTitle: plan.title,\n };\n });\n for (const link of links) {\n if (represented.has(link.id)) continue;\n merged.push({ key: link.id, label: link.id, status: link.status, pr: link.pr });\n }\n return merged;\n};\n\nconst IdeaRow = ({ idea, onOpen }: { idea: MergedIdea; onOpen?: () => void }) => (\n <Card size=\"small\" texture=\"kraft\" className=\"plan-row-card\">\n <div className=\"flex items-center gap-3\">\n {onOpen ? (\n <button\n type=\"button\"\n onClick={onOpen}\n className=\"flex-1 min-w-0 overflow-hidden text-ellipsis whitespace-nowrap bg-transparent border-none p-0 cursor-pointer [font:inherit] text-inherit text-left\"\n >\n {idea.label}\n </button>\n ) : (\n <span className=\"flex-1 min-w-0 overflow-hidden text-ellipsis whitespace-nowrap\">\n {idea.label}\n </span>\n )}\n {idea.pr && <PrBadge pr={idea.pr} />}\n <Stamp\n size=\"small\"\n fillColor={STATUS_STAMP[idea.status].fill}\n textColor={STATUS_STAMP[idea.status].text}\n >\n {STATUS_LABEL[idea.status]}\n </Stamp>\n </div>\n </Card>\n);\n\nconst ProgressBar = ({ done, total }: { done: number; total: number }) => {\n const percent = total > 0 ? Math.round((done / total) * 100) : 0;\n return (\n <div className=\"w-24 h-1.5 rounded-full bg-black/[0.08] overflow-hidden shrink-0\">\n <div\n className={`h-full rounded-full ${total > 0 ? 'bg-watercolor-green' : 'bg-transparent'}`}\n style={{ width: `${percent}%` }}\n />\n </div>\n );\n};\n\nconst RoadmapItemRow = ({\n item,\n graduated,\n highlighted,\n onPromote,\n onPromoteCandidate,\n onAddCandidate,\n onOpenGraduated,\n}: {\n item: ResolvedRoadmapItem;\n graduated: PlanEntry[];\n highlighted: boolean;\n onPromote: () => void;\n onPromoteCandidate: (candidateName: string) => void;\n onAddCandidate: (name: string) => Promise<void>;\n onOpenGraduated: (id: string | undefined, title: string) => void;\n}) => {\n const [expanded, setExpanded] = useState(highlighted);\n const { shipped, queued } = graduationCounts(graduated);\n const candidates = item.candidates.length;\n const ideas = mergeIdeas(item.links, graduated);\n\n useEffect(() => {\n if (highlighted) setExpanded(true);\n }, [highlighted]);\n\n return (\n <div\n className={`border-b border-black/10 last:border-b-0 ${highlighted ? 'roadmap-item-highlighted outline outline-2 outline-offset-[-2px] outline-[rgba(200,154,90,0.5)]' : ''}`}\n >\n <button\n type=\"button\"\n aria-expanded={expanded}\n onClick={() => setExpanded((v) => !v)}\n className=\"flex w-full items-center gap-3 py-2.5 px-0 bg-transparent border-none cursor-pointer text-left [font:inherit] text-inherit\"\n >\n <span\n className={`inline-flex items-center opacity-50 shrink-0 ${expanded ? 'rotate-90' : ''}`}\n >\n <ChevronRightIcon />\n </span>\n <span className=\"font-semibold flex-1 min-w-0 truncate\">{item.name}</span>\n <ProgressBar done={item.rollup.done} total={item.rollup.total} />\n {queued > 0 && (\n <Stamp\n size=\"small\"\n fillColor={STATUS_STAMP.planned.fill}\n textColor={STATUS_STAMP.planned.text}\n >\n {queued} in queue\n </Stamp>\n )}\n {shipped > 0 && (\n <Stamp size=\"small\" fillColor={STATUS_STAMP.done.fill} textColor={STATUS_STAMP.done.text}>\n {shipped} shipped\n </Stamp>\n )}\n {candidates > 0 && (\n <Stamp size=\"small\" fillColor=\"rgba(0, 0, 0, 0.06)\" textColor=\"rgba(0, 0, 0, 0.55)\">\n {candidates} candidate{candidates === 1 ? '' : 's'}\n </Stamp>\n )}\n </button>\n {expanded && (\n <div className=\"flex flex-col gap-1 pl-6 pb-4\">\n <span className=\"text-sm opacity-70\">{item.description}</span>\n {ideas.map((idea) => (\n <IdeaRow\n key={idea.key}\n idea={idea}\n onOpen={\n idea.planTitle\n ? () => onOpenGraduated(idea.planId, idea.planTitle as string)\n : undefined\n }\n />\n ))}\n {item.candidates.map((candidateName) => (\n <CandidateRow\n key={candidateName}\n name={candidateName}\n onPromote={() => onPromoteCandidate(candidateName)}\n />\n ))}\n <AddCandidateForm onAdd={onAddCandidate} />\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"small\"\n onClick={onPromote}\n className=\"self-start\"\n >\n Promote to idea\n </Button>\n </div>\n )}\n </div>\n );\n};\n\nconst GoalBanner = ({ goal }: { goal: string }) => {\n const [expanded, setExpanded] = useState(false);\n const [firstParagraph, ...restParagraphs] = goal.split(/\\n{2,}/);\n const hasMore = restParagraphs.length > 0;\n\n return (\n <div className=\"mb-6 pb-4 border-b border-black/[8%]\">\n <span className=\"font-handwritten text-xs font-semibold opacity-[0.55]\">The goal</span>\n <div className=\"text-sm leading-relaxed opacity-80 mt-2\">\n <Markdown>{firstParagraph}</Markdown>\n {expanded && hasMore && <Markdown>{restParagraphs.join('\\n\\n')}</Markdown>}\n </div>\n {hasMore && (\n <button\n type=\"button\"\n onClick={() => setExpanded((v) => !v)}\n className=\"bg-transparent border-none p-0 mt-2 cursor-pointer text-2xs opacity-[0.65] underline\"\n >\n {expanded ? 'Show less' : 'Show more'}\n </button>\n )}\n </div>\n );\n};\n\nexport const RoadmapPage = () => {\n const roadmap = useAppStore((s) => s.roadmap);\n const roadmapError = useAppStore((s) => s.roadmapError);\n const loadRoadmap = useAppStore((s) => s.loadRoadmap);\n const filters = useAppStore((s) => s.roadmapFilters);\n const [promoting, setPromoting] = useState<{\n horizonTitle: string;\n item: ResolvedRoadmapItem;\n candidateName?: string;\n } | null>(null);\n const plans = useAppStore((s) => s.plans);\n const navigate = useNavigate();\n const { item: highlightedItem } = useSearch({ from: '/roadmap' });\n const containerRef = useRef<HTMLDivElement>(null);\n\n useEffect(() => {\n loadRoadmap();\n }, [loadRoadmap]);\n\n useEffect(() => {\n if (!highlightedItem || !roadmap) return;\n const row = containerRef.current?.querySelector('.roadmap-item-highlighted');\n row?.scrollIntoView({ behavior: 'smooth', block: 'center' });\n }, [highlightedItem, roadmap]);\n\n if (roadmapError) {\n return (\n <div>\n <PageTitle>Roadmap</PageTitle>\n <p className=\"opacity-50\">\n Couldn't load the roadmap — the server may need a restart to pick up new routes.\n </p>\n </div>\n );\n }\n\n if (!roadmap) {\n return (\n <div>\n <PageTitle>Roadmap</PageTitle>\n <p className=\"opacity-50\">Loading…</p>\n </div>\n );\n }\n\n if (roadmap.horizons.length === 0) {\n return (\n <div>\n <PageTitle>Roadmap</PageTitle>\n <p className=\"opacity-50\">No `ROADMAP.md` found at the project root.</p>\n </div>\n );\n }\n\n const graduatedByItem = (item: ResolvedRoadmapItem) =>\n plans?.entries.filter((p) => p.subject === item.name) ?? [];\n\n const handleAddCandidate = async (horizonTitle: string, itemName: string, name: string) => {\n await addRoadmapCandidate(horizonTitle, itemName, name);\n await loadRoadmap();\n };\n\n const horizons = filterHorizons(roadmap, filters);\n const totalVisible = horizons.reduce((count, horizon) => count + horizon.items.length, 0);\n const hasActiveFilters = filters.horizons.length > 0 || filters.statuses.length > 0;\n\n return (\n <div ref={containerRef}>\n <PageTitle>Roadmap</PageTitle>\n <GoalBanner goal={roadmap.goal} />\n {totalVisible === 0 ? (\n <p className=\"opacity-50 py-6 px-0 text-center\">\n {hasActiveFilters\n ? 'Nothing matches these filters — clear one from the sidebar to see more.'\n : 'No roadmap items yet — add one from the sidebar.'}\n </p>\n ) : (\n <div className=\"flex flex-col gap-6\">\n {horizons\n .filter((horizon) => horizon.items.length > 0)\n .map((horizon) => (\n <div key={horizon.title} className=\"flex flex-col gap-1\">\n <div className={HORIZON_HEADER_CLASSES}>{horizon.title}</div>\n <div className=\"flex flex-col\">\n {horizon.items.map((item) => (\n <RoadmapItemRow\n key={item.name}\n item={item}\n graduated={graduatedByItem(item)}\n highlighted={item.name === highlightedItem}\n onPromote={() => setPromoting({ horizonTitle: horizon.title, item })}\n onPromoteCandidate={(candidateName) =>\n setPromoting({ horizonTitle: horizon.title, item, candidateName })\n }\n onAddCandidate={(name) => handleAddCandidate(horizon.title, item.name, name)}\n onOpenGraduated={(id, title) =>\n navigate({\n to: '/plans/$planId',\n params: { planId: entityRouteParam(id, title) },\n })\n }\n />\n ))}\n </div>\n </div>\n ))}\n </div>\n )}\n <PromoteRoadmapItemModal\n horizonTitle={promoting?.horizonTitle ?? null}\n item={promoting?.item ?? null}\n candidateName={promoting?.candidateName}\n onClose={() => setPromoting(null)}\n onPromoted={loadRoadmap}\n />\n </div>\n );\n};\n","import { addRoadmapItem } from '@/app/services/content/docs-api';\nimport { useAppStore } from '@/app/stores/app-store';\nimport { Button, Input, Modal, Select } from '@dendelion/paper-ui';\nimport { useEffect, useRef, useState } from 'react';\n\ninterface AddRoadmapItemModalProps {\n open: boolean;\n horizonTitles: string[];\n onClose: () => void;\n}\n\nexport const AddRoadmapItemModal = ({ open, horizonTitles, onClose }: AddRoadmapItemModalProps) => {\n const loadRoadmap = useAppStore((s) => s.loadRoadmap);\n const [horizon, setHorizon] = useState(horizonTitles[0] ?? '');\n const [name, setName] = useState('');\n const [description, setDescription] = useState('');\n const [saving, setSaving] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const horizonTitlesRef = useRef(horizonTitles);\n horizonTitlesRef.current = horizonTitles;\n\n useEffect(() => {\n if (open) {\n setHorizon(horizonTitlesRef.current[0] ?? '');\n setName('');\n setDescription('');\n setError(null);\n setSaving(false);\n }\n }, [open]);\n\n const handleAdd = async () => {\n if (!name.trim() || !horizon) return;\n setSaving(true);\n setError(null);\n try {\n await addRoadmapItem(horizon, name.trim(), description.trim());\n await loadRoadmap();\n onClose();\n } catch (err) {\n setError((err as Error).message);\n } finally {\n setSaving(false);\n }\n };\n\n return (\n <Modal open={open} onClose={onClose} title=\"Add roadmap item\" size=\"small\">\n <div className=\"flex flex-col gap-4\">\n <Select\n size=\"small\"\n value={horizon}\n onChange={setHorizon}\n disabled={saving}\n options={horizonTitles.map((title) => ({ value: title, label: title }))}\n />\n <Input\n size=\"small\"\n value={name}\n onChange={(e) => setName(e.target.value)}\n placeholder=\"Item name…\"\n disabled={saving}\n autoFocus\n />\n <Input\n size=\"small\"\n value={description}\n onChange={(e) => setDescription(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === 'Enter') handleAdd();\n }}\n placeholder=\"Description…\"\n disabled={saving}\n />\n {error && <p className=\"m-0 text-watercolor-rose-dark text-sm\">{error}</p>}\n <div className=\"flex justify-end gap-2\">\n <Button type=\"button\" variant=\"ghost\" onClick={onClose} disabled={saving}>\n Cancel\n </Button>\n <Button\n type=\"button\"\n variant=\"primary\"\n onClick={handleAdd}\n disabled={saving || !name.trim() || !horizon}\n >\n {saving ? 'Adding…' : 'Add item'}\n </Button>\n </div>\n </div>\n </Modal>\n );\n};\n","import { STATUS_LABEL, STATUS_STAMP } from '@/app/features/plans/constants';\nimport { useAppStore } from '@/app/stores/app-store';\nimport type { PlanStatus } from '@/types/index';\nimport { Button, ListItem } from '@dendelion/paper-ui';\nimport { useState } from 'react';\nimport { AddRoadmapItemModal } from './add-roadmap-item-modal';\nimport { horizonItemCounts, statusItemCounts } from './roadmap-filters';\n\nconst STATUS_CHIP_ORDER: PlanStatus[] = [\n 'in-progress',\n 'review',\n 'planned',\n 'idea',\n 'done',\n 'dropped',\n];\n\nconst sectionLabelClass = 'text-2xs font-semibold tracking-[0.08em] uppercase text-ink-300 mb-2';\n\nexport const RoadmapSidebar = () => {\n const roadmap = useAppStore((s) => s.roadmap);\n const filters = useAppStore((s) => s.roadmapFilters);\n const toggleRoadmapHorizon = useAppStore((s) => s.toggleRoadmapHorizon);\n const toggleRoadmapStatus = useAppStore((s) => s.toggleRoadmapStatus);\n const [addOpen, setAddOpen] = useState(false);\n\n if (!roadmap) return null;\n\n const horizonTitles = roadmap.horizons.map((horizon) => horizon.title);\n const horizonCounts = horizonItemCounts(roadmap, filters);\n const statusCounts = statusItemCounts(roadmap, filters);\n const activeHorizons = new Set(filters.horizons);\n const activeStatuses = new Set(filters.statuses);\n const visibleStatuses = STATUS_CHIP_ORDER.filter(\n (status) => (statusCounts[status] ?? 0) > 0 || activeStatuses.has(status),\n );\n\n return (\n <div className=\"flex flex-col gap-8 -mt-5\">\n <Button\n type=\"button\"\n variant=\"primary\"\n size=\"small\"\n onClick={() => setAddOpen(true)}\n disabled={horizonTitles.length === 0}\n >\n + Add item\n </Button>\n\n <div>\n <div className={sectionLabelClass}>Horizon</div>\n <div className=\"flex flex-col gap-1\">\n {horizonTitles.map((title) => (\n <ListItem\n key={title}\n size=\"small\"\n active={activeHorizons.has(title)}\n onClick={() => toggleRoadmapHorizon(title)}\n className=\"text-xs leading-4 py-2\"\n action={<span className=\"text-2xs text-ink-500\">{horizonCounts[title] ?? 0}</span>}\n >\n {title}\n </ListItem>\n ))}\n </div>\n </div>\n\n <div>\n <div className={sectionLabelClass}>Status</div>\n <div className=\"flex flex-col gap-1\">\n {visibleStatuses.length === 0 && (\n <span className=\"opacity-50 text-2xs\">No linked ideas</span>\n )}\n {visibleStatuses.map((status) => (\n <ListItem\n key={status}\n size=\"small\"\n active={activeStatuses.has(status)}\n onClick={() => toggleRoadmapStatus(status)}\n className=\"text-xs leading-4 py-2\"\n icon={\n <span\n className=\"w-[9px] h-[9px] rounded-full shrink-0\"\n style={{ background: STATUS_STAMP[status].text }}\n />\n }\n action={<span className=\"text-2xs text-ink-500\">{statusCounts[status] ?? 0}</span>}\n >\n {STATUS_LABEL[status]}\n </ListItem>\n ))}\n </div>\n </div>\n\n <AddRoadmapItemModal\n open={addOpen}\n horizonTitles={horizonTitles}\n onClose={() => setAddOpen(false)}\n />\n </div>\n );\n};\n"],"names":["NO_SUBJECT","PromoteRoadmapItemModal","horizonTitle","item","candidateName","onClose","onPromoted","promoteRoadmapItem","useAppStore","s","launchIdeaExtend","subjects","subjectsAvailable","useSubjectVocabulary","subject","setSubject","useState","loading","setLoading","error","setError","toast","useToast","useEffect","handlePromote","id","idea","e","buildRoadmapPromotePrompt","err","oneLineErrorSummary","jsx","Modal","jsxs","Select","Button","graduationCounts","graduated","p","HORIZON_HEADER_CLASSES","ChevronRightIcon","size","CandidateRow","name","onPromote","Card","AddCandidateForm","onAdd","setName","saving","setSaving","handleAdd","Input","mergeIdeas","links","represented","merged","plan","link","IdeaRow","onOpen","PrBadge","Stamp","STATUS_STAMP","STATUS_LABEL","ProgressBar","done","total","percent","RoadmapItemRow","highlighted","onPromoteCandidate","onAddCandidate","onOpenGraduated","expanded","setExpanded","shipped","queued","candidates","ideas","v","GoalBanner","goal","firstParagraph","restParagraphs","hasMore","Markdown","RoadmapPage","roadmap","roadmapError","loadRoadmap","filters","promoting","setPromoting","plans","navigate","useNavigate","highlightedItem","useSearch","containerRef","useRef","row","_a","PageTitle","graduatedByItem","handleAddCandidate","itemName","addRoadmapCandidate","horizons","filterHorizons","totalVisible","count","horizon","hasActiveFilters","title","entityRouteParam","AddRoadmapItemModal","open","horizonTitles","setHorizon","description","setDescription","horizonTitlesRef","addRoadmapItem","STATUS_CHIP_ORDER","sectionLabelClass","RoadmapSidebar","toggleRoadmapHorizon","toggleRoadmapStatus","addOpen","setAddOpen","horizonCounts","horizonItemCounts","statusCounts","statusItemCounts","activeHorizons","activeStatuses","visibleStatuses","status","ListItem"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- import{c as m,r as o,j as e,P as u,X as i,Y as h,N as j,Z as r,_ as p,U as d,$ as g,a0 as v,w as N}from"./main-Zb7gEID9.js";import{c}from"./tokens-C3C44lLs.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)}%`})]})}),w=({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})]}),L=({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"]})]})})},R=()=>{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(w,{sourceLines:s.comments.sourceLines,testLines:s.testLines}),e.jsx(L,{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{R as StatsPage};
2
- //# sourceMappingURL=index-DJwR0UyW.js.map
1
+ import{c as m,r as o,j as e,P as u,W as i,X as h,O as j,Y as r,Z as p,g as d,_ as g,a0 as v,R as N}from"./main-AfQrNPNu.js";import{c}from"./tokens-C3C44lLs.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"]})]})})},U=()=>{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{U as StatsPage};
2
+ //# sourceMappingURL=index-Bm61MVP8.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index-DJwR0UyW.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-Bm61MVP8.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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"}
@@ -0,0 +1,6 @@
1
+ import{j as e,g as b,a as k,u as r,r as x,a1 as F,a2 as P,a3 as C,a4 as S,w as E,E as y,a5 as $,$ as A,J as v,a6 as D,a7 as I,a8 as z,p as B}from"./main-AfQrNPNu.js";function L(s){if(!s)return[];const t=(s.endsWith(`
2
+ `)?s.slice(0,-1):s).split(`
3
+ `),i=[];let l=null;for(const n of t){if(n.startsWith("@@ ")){l={header:n,lines:[]},i.push(l);continue}l&&(n.startsWith("+")?l.lines.push({type:"add",text:n.slice(1)}):n.startsWith("-")?l.lines.push({type:"remove",text:n.slice(1)}):l.lines.push({type:"context",text:n.startsWith(" ")?n.slice(1):n}))}return i}function R(s){return s?[{header:"",lines:(s.endsWith(`
4
+ `)?s.slice(0,-1):s).split(`
5
+ `).map(i=>({type:"add",text:i}))}]:[]}const W={add:"bg-watercolor-green/[18%] text-watercolor-green-dark",remove:"bg-watercolor-rose/[18%] text-watercolor-rose-dark",context:""},T={add:"+",remove:"-",context:" "},G=({additions:s,deletions:t})=>e.jsxs("span",{className:"inline-flex shrink-0 gap-2 font-mono text-2xs",children:[e.jsxs("span",{className:"text-watercolor-green-dark",children:["+",s]}),e.jsxs("span",{className:"text-watercolor-rose-dark",children:["-",t]})]}),H=({entry:s})=>e.jsxs("div",{className:"flex w-full min-w-0 items-center justify-between gap-3",children:[e.jsx("span",{className:"overflow-hidden text-ellipsis whitespace-nowrap font-mono text-xs",children:s.renameSource?`${s.renameSource} → ${s.path}`:s.path}),e.jsxs("span",{className:"flex shrink-0 items-center gap-2",children:[s.staged&&e.jsx(b,{size:"small",children:"staged"}),!s.binary&&e.jsx(G,{additions:s.additions,deletions:s.deletions})]})]}),M=({entry:s})=>{if(s.binary)return e.jsx("p",{className:"m-0 opacity-60",children:"Binary file not shown."});if(s.contentKind==="too-large")return e.jsx("p",{className:"m-0 opacity-60",children:"File too large to preview."});const t=s.contentKind==="raw"?R(s.patch):L(s.patch);return t.length===0?s.renameSource?e.jsx("p",{className:"m-0 opacity-60",children:s.additions===0&&s.deletions===0?"Renamed, no content changes.":"Renamed with unrelated content — diff omitted."}):e.jsx("p",{className:"m-0 opacity-60",children:"No changes to preview."}):e.jsx("div",{className:"flex min-w-0 max-w-full flex-col gap-3",children:t.map((i,l)=>e.jsxs("div",{className:"min-w-0 max-w-full",children:[i.header&&e.jsx("div",{className:"mb-1 font-mono text-2xs opacity-50",children:i.header}),e.jsx("pre",{className:"m-0 min-w-0 max-w-full overflow-x-auto font-mono text-2xs",children:i.lines.map((n,c)=>e.jsxs("span",{className:`block ${W[n.type]}`,children:[T[n.type],n.text]},`${n.type}-${c}`))})]},`${i.header}-${l}`))})},q=({entry:s})=>e.jsxs("div",{"data-diff-path":s.path,className:"min-w-0 max-w-full scroll-mt-4",children:[e.jsx("div",{className:"mb-3",children:e.jsx(H,{entry:s})}),e.jsx(M,{entry:s})]}),w=()=>{const s=r(o=>o.gitAhead),t=r(o=>o.gitBranchHygiene),{pushing:i,syncing:l,pulling:n,gitActionBusy:c,handlePush:j,handleSync:p,handlePull:h}=$();return e.jsxs("div",{className:"mb-4 flex items-center gap-2",children:[e.jsx(A,{content:t==="clean-on-main"?"Already on clean main":void 0,children:e.jsx(v,{size:"small",icon:e.jsx(D,{size:14}),disabled:c||t==="clean-on-main",onClick:p,children:l?"Syncing…":"Sync to main"})}),e.jsx(v,{size:"small",icon:e.jsx(I,{size:14}),disabled:c||s===0,onClick:j,children:i?"Pushing…":s>0?`Push (${s})`:"Push"}),e.jsx(v,{size:"small",icon:e.jsx(z,{size:14}),disabled:c,onClick:h,children:n?"Pulling…":"Pull"})]})},V=()=>{const s=k(),t=r(a=>a.diffFiles),i=r(a=>a.diffLoadFailed),l=r(a=>a.loadDiffFiles),n=r(a=>a.setActiveDiffPath),c=x.useRef(null),j=x.useMemo(()=>(t==null?void 0:t.map(a=>a.path))??[],[t]),p=F(void 0,j);x.useEffect(()=>{l()},[l]),x.useEffect(()=>{let a;const d=P(u=>{u.message==="changed"&&(a&&clearTimeout(a),a=setTimeout(l,250))}),m=()=>l();return window.addEventListener("focus",m),()=>{a&&clearTimeout(a),window.removeEventListener("focus",m),d()}},[l]),x.useEffect(()=>{const a=c.current;if(!a)return;const d=Array.from(a.querySelectorAll("[data-diff-path]"));if(d.length===0)return;const m=new Set,u=new IntersectionObserver(g=>{for(const f of g)f.isIntersecting?m.add(f.target):m.delete(f.target);const N=d.find(f=>m.has(f));N&&n(N.dataset.diffPath??null)},{rootMargin:"0px 0px -70% 0px"});for(const g of d)u.observe(g);return()=>u.disconnect()},[t,n]);const h=e.jsx("div",{className:"mb-4",children:e.jsx(y,{items:[{id:"plans",label:"Plans",onClick:()=>s({to:"/"})},{id:"git",label:"Git"}]})}),o="min-h-page";return i?e.jsxs("div",{children:[h,e.jsx(w,{}),e.jsx("div",{className:o,children:e.jsx("p",{className:"opacity-50",children:"Couldn't load the working-tree diff."})})]}):t?e.jsxs("div",{children:[h,e.jsx(w,{}),t.length===0?e.jsx("div",{className:o,children:e.jsx("p",{className:"opacity-50",children:"No changed files."})}):e.jsxs(e.Fragment,{children:[e.jsxs("div",{className:"mb-6 flex items-start gap-2",children:[e.jsx("div",{className:"flex-1",children:e.jsx(C,{state:p,filesEmpty:!1})}),e.jsx(S,{state:p,filesEmpty:!1})]}),e.jsx("div",{ref:c,className:`flex min-w-0 flex-col gap-6 ${o}`,children:t.map((a,d)=>e.jsxs(x.Fragment,{children:[d>0&&e.jsx(E,{}),e.jsx(q,{entry:a})]},a.path))})]})]}):e.jsxs("div",{children:[h,e.jsx(w,{}),e.jsx("div",{className:o,children:e.jsx("p",{className:"opacity-50",children:"Loading…"})})]})},J="text-2xs font-semibold tracking-[0.08em] uppercase text-ink-300 mb-2",K=({additions:s,deletions:t})=>e.jsxs("span",{className:"inline-flex gap-2 font-mono text-2xs",children:[e.jsxs("span",{className:"text-watercolor-green-dark",children:["+",s]}),e.jsxs("span",{className:"text-watercolor-rose-dark",children:["-",t]})]}),_=s=>{var t;(t=document.querySelector(`[data-diff-path="${CSS.escape(s)}"]`))==null||t.scrollIntoView({behavior:"smooth",block:"start"})},X=()=>{const s=r(i=>i.diffFiles),t=r(i=>i.activeDiffPath);return!s||s.length===0?null:e.jsx("div",{className:"flex flex-col gap-8 -mt-5",children:e.jsxs("div",{children:[e.jsx("div",{className:J,children:"Changed files"}),e.jsx("div",{className:"flex flex-col gap-1",children:s.map(i=>e.jsx(B,{size:"small",active:i.path===t,onClick:()=>_(i.path),action:e.jsxs("span",{className:"flex items-center gap-2",children:[i.staged&&e.jsx(b,{size:"small",children:"staged"}),!i.binary&&e.jsx(K,{additions:i.additions,deletions:i.deletions})]}),children:e.jsx("span",{className:"block overflow-hidden text-ellipsis whitespace-nowrap font-mono text-2xs",children:i.path})},i.path))})]})})};export{X as GitFileList,V as GitPage};
6
+ //# sourceMappingURL=index-CHYyIAaz.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index-CHYyIAaz.js","sources":["../../../src/app/utils/parse-diff.ts","../../../src/app/features/git/file-diff-section.tsx","../../../src/app/features/git/git-page.tsx","../../../src/app/features/git/git-file-list.tsx"],"sourcesContent":["export type DiffLineType = 'add' | 'remove' | 'context';\n\nexport interface DiffLine {\n type: DiffLineType;\n text: string;\n}\n\nexport interface DiffHunk {\n header: string;\n lines: DiffLine[];\n}\n\n// Parses a unified diff (git.ts getWorkingDiff, contentKind: 'diff'). A real diff\n// with no hunks (pure rename, mode-only change) is just the preamble, so it yields [].\nexport function parsePatch(patch: string): DiffHunk[] {\n if (!patch) return [];\n const lines = (patch.endsWith('\\n') ? patch.slice(0, -1) : patch).split('\\n');\n\n const hunks: DiffHunk[] = [];\n let current: DiffHunk | null = null;\n for (const line of lines) {\n if (line.startsWith('@@ ')) {\n current = { header: line, lines: [] };\n hunks.push(current);\n continue;\n }\n if (!current) continue; // preamble: diff --git / index / --- / +++\n if (line.startsWith('+')) current.lines.push({ type: 'add', text: line.slice(1) });\n else if (line.startsWith('-')) current.lines.push({ type: 'remove', text: line.slice(1) });\n else current.lines.push({ type: 'context', text: line.startsWith(' ') ? line.slice(1) : line });\n }\n return hunks;\n}\n\n// Untracked files store raw content, not a diff (git.ts getWorkingDiff, contentKind:\n// 'raw') — synthesize a single all-added hunk so the renderer only has one shape to draw.\nexport function rawContentHunks(content: string): DiffHunk[] {\n if (!content) return [];\n const lines = (content.endsWith('\\n') ? content.slice(0, -1) : content).split('\\n');\n return [{ header: '', lines: lines.map((text) => ({ type: 'add', text })) }];\n}\n","import { type DiffLineType, parsePatch, rawContentHunks } from '@/app/utils/parse-diff';\nimport type { FileDiffEntry } from '@/types/index';\nimport { Stamp } from '@dendelion/paper-ui';\n\nconst LINE_CLASS: Record<DiffLineType, string> = {\n add: 'bg-watercolor-green/[18%] text-watercolor-green-dark',\n remove: 'bg-watercolor-rose/[18%] text-watercolor-rose-dark',\n context: '',\n};\n\nconst LINE_PREFIX: Record<DiffLineType, string> = { add: '+', remove: '-', context: ' ' };\n\ninterface CountBadgeProps {\n additions: number;\n deletions: number;\n}\n\nconst CountBadge = ({ additions, deletions }: CountBadgeProps) => (\n <span className=\"inline-flex shrink-0 gap-2 font-mono text-2xs\">\n <span className=\"text-watercolor-green-dark\">+{additions}</span>\n <span className=\"text-watercolor-rose-dark\">-{deletions}</span>\n </span>\n);\n\ninterface FileHeaderProps {\n entry: FileDiffEntry;\n}\n\nconst FileHeader = ({ entry }: FileHeaderProps) => (\n <div className=\"flex w-full min-w-0 items-center justify-between gap-3\">\n <span className=\"overflow-hidden text-ellipsis whitespace-nowrap font-mono text-xs\">\n {entry.renameSource ? `${entry.renameSource} → ${entry.path}` : entry.path}\n </span>\n <span className=\"flex shrink-0 items-center gap-2\">\n {entry.staged && <Stamp size=\"small\">staged</Stamp>}\n {!entry.binary && <CountBadge additions={entry.additions} deletions={entry.deletions} />}\n </span>\n </div>\n);\n\ninterface DiffBodyProps {\n entry: FileDiffEntry;\n}\n\nconst DiffBody = ({ entry }: DiffBodyProps) => {\n if (entry.binary) {\n return <p className=\"m-0 opacity-60\">Binary file not shown.</p>;\n }\n if (entry.contentKind === 'too-large') {\n return <p className=\"m-0 opacity-60\">File too large to preview.</p>;\n }\n const hunks =\n entry.contentKind === 'raw' ? rawContentHunks(entry.patch) : parsePatch(entry.patch);\n if (hunks.length === 0) {\n if (entry.renameSource) {\n return (\n <p className=\"m-0 opacity-60\">\n {entry.additions === 0 && entry.deletions === 0\n ? 'Renamed, no content changes.'\n : 'Renamed with unrelated content — diff omitted.'}\n </p>\n );\n }\n return <p className=\"m-0 opacity-60\">No changes to preview.</p>;\n }\n return (\n <div className=\"flex min-w-0 max-w-full flex-col gap-3\">\n {hunks.map((hunk, i) => (\n <div key={`${hunk.header}-${i}`} className=\"min-w-0 max-w-full\">\n {hunk.header && <div className=\"mb-1 font-mono text-2xs opacity-50\">{hunk.header}</div>}\n {/* paper-ui's CodeBlock has no per-line add/remove styling. */}\n <pre className=\"m-0 min-w-0 max-w-full overflow-x-auto font-mono text-2xs\">\n {hunk.lines.map((line, j) => (\n <span key={`${line.type}-${j}`} className={`block ${LINE_CLASS[line.type]}`}>\n {LINE_PREFIX[line.type]}\n {line.text}\n </span>\n ))}\n </pre>\n </div>\n ))}\n </div>\n );\n};\n\ninterface FileDiffSectionProps {\n entry: FileDiffEntry;\n}\n\nexport const FileDiffSection = ({ entry }: FileDiffSectionProps) => (\n <div data-diff-path={entry.path} className=\"min-w-0 max-w-full scroll-mt-4\">\n <div className=\"mb-3\">\n <FileHeader entry={entry} />\n </div>\n <DiffBody entry={entry} />\n </div>\n);\n","import { MergeIcon, PullIcon, PushIcon } from '@/app/components/icons';\nimport { FileDiffSection } from '@/app/features/git/file-diff-section';\nimport {\n DeliverCommitButton,\n DeliverCommitInputRow,\n useDeliverCommitForm,\n} from '@/app/features/plans/components/deliver-controls';\nimport { useBranchSync } from '@/app/hooks/use-branch-sync';\nimport { subscribeToActivityStream } from '@/app/services/activity-stream';\nimport { useAppStore } from '@/app/stores/app-store';\nimport { Breadcrumb, Button, Divider, Tooltip } from '@dendelion/paper-ui';\nimport { useNavigate } from '@tanstack/react-router';\nimport { Fragment, useEffect, useMemo, useRef } from 'react';\n\nconst GitActionsRow = () => {\n const gitAhead = useAppStore((s) => s.gitAhead);\n const gitBranchHygiene = useAppStore((s) => s.gitBranchHygiene);\n const { pushing, syncing, pulling, gitActionBusy, handlePush, handleSync, handlePull } =\n useBranchSync();\n\n return (\n <div className=\"mb-4 flex items-center gap-2\">\n <Tooltip content={gitBranchHygiene === 'clean-on-main' ? 'Already on clean main' : undefined}>\n <Button\n size=\"small\"\n icon={<MergeIcon size={14} />}\n disabled={gitActionBusy || gitBranchHygiene === 'clean-on-main'}\n onClick={handleSync}\n >\n {syncing ? 'Syncing…' : 'Sync to main'}\n </Button>\n </Tooltip>\n <Button\n size=\"small\"\n icon={<PushIcon size={14} />}\n disabled={gitActionBusy || gitAhead === 0}\n onClick={handlePush}\n >\n {pushing ? 'Pushing…' : gitAhead > 0 ? `Push (${gitAhead})` : 'Push'}\n </Button>\n <Button\n size=\"small\"\n icon={<PullIcon size={14} />}\n disabled={gitActionBusy}\n onClick={handlePull}\n >\n {pulling ? 'Pulling…' : 'Pull'}\n </Button>\n </div>\n );\n};\n\nexport const GitPage = () => {\n const navigate = useNavigate();\n const files = useAppStore((s) => s.diffFiles);\n const loadFailed = useAppStore((s) => s.diffLoadFailed);\n const loadDiffFiles = useAppStore((s) => s.loadDiffFiles);\n const setActiveDiffPath = useAppStore((s) => s.setActiveDiffPath);\n const sectionsRef = useRef<HTMLDivElement>(null);\n const commitFiles = useMemo(() => files?.map((entry) => entry.path) ?? [], [files]);\n const commitForm = useDeliverCommitForm(undefined, commitFiles);\n\n useEffect(() => {\n loadDiffFiles();\n }, [loadDiffFiles]);\n\n useEffect(() => {\n let timer: ReturnType<typeof setTimeout> | undefined;\n const unsubscribe = subscribeToActivityStream((payload) => {\n if (payload.message !== 'changed') return;\n if (timer) clearTimeout(timer);\n timer = setTimeout(loadDiffFiles, 250);\n });\n const onFocus = () => loadDiffFiles();\n window.addEventListener('focus', onFocus);\n return () => {\n if (timer) clearTimeout(timer);\n window.removeEventListener('focus', onFocus);\n unsubscribe();\n };\n }, [loadDiffFiles]);\n\n // biome-ignore lint/correctness/useExhaustiveDependencies: files is the trigger to rebuild the observer, not a value read in the body.\n useEffect(() => {\n const root = sectionsRef.current;\n if (!root) return;\n const sections = Array.from(root.querySelectorAll<HTMLElement>('[data-diff-path]'));\n if (sections.length === 0) return;\n const visible = new Set<HTMLElement>();\n const observer = new IntersectionObserver(\n (entries) => {\n for (const entry of entries) {\n if (entry.isIntersecting) visible.add(entry.target as HTMLElement);\n else visible.delete(entry.target as HTMLElement);\n }\n const topmost = sections.find((el) => visible.has(el));\n if (topmost) setActiveDiffPath(topmost.dataset.diffPath ?? null);\n },\n { rootMargin: '0px 0px -70% 0px' },\n );\n for (const el of sections) observer.observe(el);\n return () => observer.disconnect();\n }, [files, setActiveDiffPath]);\n\n const breadcrumb = (\n <div className=\"mb-4\">\n <Breadcrumb\n items={[\n { id: 'plans', label: 'Plans', onClick: () => navigate({ to: '/' }) },\n { id: 'git', label: 'Git' },\n ]}\n />\n </div>\n );\n\n const contentClass = 'min-h-page';\n\n if (loadFailed) {\n return (\n <div>\n {breadcrumb}\n <GitActionsRow />\n <div className={contentClass}>\n <p className=\"opacity-50\">Couldn't load the working-tree diff.</p>\n </div>\n </div>\n );\n }\n\n if (!files) {\n return (\n <div>\n {breadcrumb}\n <GitActionsRow />\n <div className={contentClass}>\n <p className=\"opacity-50\">Loading…</p>\n </div>\n </div>\n );\n }\n\n return (\n <div>\n {breadcrumb}\n <GitActionsRow />\n {files.length === 0 ? (\n <div className={contentClass}>\n <p className=\"opacity-50\">No changed files.</p>\n </div>\n ) : (\n <>\n <div className=\"mb-6 flex items-start gap-2\">\n <div className=\"flex-1\">\n <DeliverCommitInputRow state={commitForm} filesEmpty={false} />\n </div>\n <DeliverCommitButton state={commitForm} filesEmpty={false} />\n </div>\n <div ref={sectionsRef} className={`flex min-w-0 flex-col gap-6 ${contentClass}`}>\n {files.map((entry, idx) => (\n <Fragment key={entry.path}>\n {idx > 0 && <Divider />}\n <FileDiffSection entry={entry} />\n </Fragment>\n ))}\n </div>\n </>\n )}\n </div>\n );\n};\n","import { useAppStore } from '@/app/stores/app-store';\nimport { ListItem, Stamp } from '@dendelion/paper-ui';\n\nconst sectionLabelClass = 'text-2xs font-semibold tracking-[0.08em] uppercase text-ink-300 mb-2';\n\ninterface CountBadgeProps {\n additions: number;\n deletions: number;\n}\n\nconst CountBadge = ({ additions, deletions }: CountBadgeProps) => (\n <span className=\"inline-flex gap-2 font-mono text-2xs\">\n <span className=\"text-watercolor-green-dark\">+{additions}</span>\n <span className=\"text-watercolor-rose-dark\">-{deletions}</span>\n </span>\n);\n\nconst scrollToFile = (path: string) => {\n document\n .querySelector(`[data-diff-path=\"${CSS.escape(path)}\"]`)\n ?.scrollIntoView({ behavior: 'smooth', block: 'start' });\n};\n\nexport const GitFileList = () => {\n const files = useAppStore((s) => s.diffFiles);\n const activePath = useAppStore((s) => s.activeDiffPath);\n\n if (!files || files.length === 0) return null;\n\n return (\n <div className=\"flex flex-col gap-8 -mt-5\">\n <div>\n <div className={sectionLabelClass}>Changed files</div>\n <div className=\"flex flex-col gap-1\">\n {files.map((entry) => (\n <ListItem\n key={entry.path}\n size=\"small\"\n active={entry.path === activePath}\n onClick={() => scrollToFile(entry.path)}\n action={\n <span className=\"flex items-center gap-2\">\n {entry.staged && <Stamp size=\"small\">staged</Stamp>}\n {!entry.binary && (\n <CountBadge additions={entry.additions} deletions={entry.deletions} />\n )}\n </span>\n }\n >\n <span className=\"block overflow-hidden text-ellipsis whitespace-nowrap font-mono text-2xs\">\n {entry.path}\n </span>\n </ListItem>\n ))}\n </div>\n </div>\n </div>\n );\n};\n"],"names":["parsePatch","patch","lines","hunks","current","line","rawContentHunks","content","text","LINE_CLASS","LINE_PREFIX","CountBadge","additions","deletions","jsxs","FileHeader","entry","jsx","Stamp","DiffBody","hunk","i","j","FileDiffSection","GitActionsRow","gitAhead","useAppStore","s","gitBranchHygiene","pushing","syncing","pulling","gitActionBusy","handlePush","handleSync","handlePull","useBranchSync","Tooltip","Button","MergeIcon","PushIcon","PullIcon","GitPage","navigate","useNavigate","files","loadFailed","loadDiffFiles","setActiveDiffPath","sectionsRef","useRef","commitFiles","useMemo","commitForm","useDeliverCommitForm","useEffect","timer","unsubscribe","subscribeToActivityStream","payload","onFocus","root","sections","visible","observer","entries","topmost","el","breadcrumb","Breadcrumb","contentClass","Fragment","DeliverCommitInputRow","DeliverCommitButton","idx","Divider","sectionLabelClass","scrollToFile","path","_a","GitFileList","activePath","ListItem"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import{c as K,r as d,C as _,j as e,e as z,g as P,R as F,J as f,w as v,h as $,k as X,$ as te,l as ae,s as R,u as T,t as Q,m as ne,a as Z,n as ee,P as le,o as ie,i as G,D,q as oe,A as O,v as q,x as re,y as ce,p as L}from"./main-AfQrNPNu.js";import{c as de}from"./tokens-C3C44lLs.js";import{S as V}from"./sidebar-section-AupYBPL3.js";const me=async()=>{try{const s=await fetch(K("/api/merge-policy"));return s.ok?await s.json():null}catch{return null}},pe=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"},B=[{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 he(s){return B.every(t=>s[t.key]===se[t.key])}const ue=({label:s,current:t,recommended:r,isLast:l})=>{const p=t===r;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(r)]})]}),!l&&e.jsx(v,{})]})},xe=()=>{const[s,t]=d.useState(void 0),[r,l]=d.useState(!1),{toast:p}=_();d.useEffect(()=>{me().then(t)},[]);const u=async()=>{l(!0);const n=await pe();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"})},h=(s==null?void 0:s.status)==="ok"&&he(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(z,{variant:"warning",children:"Failed to load merge policy."}),(s==null?void 0:s.status)==="unavailable"&&e.jsx(z,{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:h?"rgba(143, 185, 150, 0.25)":"rgba(212, 163, 115, 0.25)",textColor:h?"#5E8A66":"#A67B4F",children:h?"Matches recommended policy":"Differs from recommended policy"})]}),e.jsx(F,{size:"small",texture:"kraft",children:B.map((n,c)=>e.jsx(ue,{label:n.label,current:s.policy[n.key],recommended:se[n.key],isLast:c===B.length-1},n.key))}),e.jsx("div",{className:"mt-4",children:e.jsx(f,{size:"small",onClick:u,disabled:r||h,children:r?"Applying…":"Apply recommended policy"})})]})]})},je=2e3,ge="agent:claude-code",fe=["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:"}),fe.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."]})]}),ve=({onSignedIn:s})=>{const t=T(c=>c.loginRelay),r=T(c=>c.startLoginRelay),l=T(c=>c.loadLoginRelayStatus),p=T(c=>c.cancelLoginRelay),{toast:u}=_(),h=d.useRef(null),n=t==null?void 0:t.phase;return d.useEffect(()=>{if(n!=="starting"&&n!=="awaiting-authorization")return;const c=window.setInterval(()=>l(),je);return()=>window.clearInterval(c)},[n,l]),d.useEffect(()=>{const c=t==null?void 0:t.authorizeUrl;n!=="awaiting-authorization"||!c||h.current===c||(h.current=c,window.open(c,"_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(f,{size:"small",disabled:!0,children:"Starting…"}):n==="awaiting-authorization"?e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx(f,{size:"small",onClick:()=>(t==null?void 0:t.authorizeUrl)&&window.open(t.authorizeUrl,"_blank","noopener,noreferrer"),children:"Reopen sign-in tab"}),e.jsx(f,{size:"small",onClick:()=>p(),children:"Cancel"})]}):e.jsxs("div",{children:[e.jsx(f,{size:"small",onClick:()=>r(),children:"Sign in"}),n==="error"&&e.jsx(be,{})]})},ye={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"}},Ne=({connection:s,onConnect:t,connecting:r})=>{const{connect:l}=s;return l?l.kind==="command"&&l.runnable?e.jsx(f,{size:"small",onClick:()=>t(s.id),disabled:r,children:r?"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(f,{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},Se=({connection:s,isLast:t,onRecheck:r,rechecking:l,onConnect:p,connecting:u})=>{const h=ye[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:h.fill,textColor:h.text,children:h.label}),e.jsx(f,{size:"small",onClick:()=>r(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===ge&&s.authenticated===!1?e.jsx(ve,{onSignedIn:()=>r(s.id)}):e.jsx(Ne,{connection:s,onConnect:p,connecting:u})})]}),!t&&e.jsx(v,{})]})},ke=()=>{const[s,t]=d.useState(null),[r,l]=d.useState(!1),[p,u]=d.useState(null),[h,n]=d.useState(null),[c,S]=d.useState(!1),{toast:y}=_(),N=d.useCallback(i=>{if(i===null){l(!0);return}l(!1),t(i)},[]);d.useEffect(()=>{$().then(N),X().then(i=>S((i==null?void 0:i.setupDismissed)??!1))},[N]);const A=async i=>{u(i),N(await $()),u(null)},m=async i=>{n(i);const b=await ae(i);b?t(C=>(C==null?void 0:C.map(M=>M.id===i?b:M))??C):y({title:"Failed to connect",variant:"error"}),n(null)},x=async()=>{const i=!c,{ok:b}=await R({setupDismissed:i});b?(S(i),y({title:"Saved",variant:"success"})):y({title:"Failed to save",variant:"error"})},k=(s==null?void 0:s.every(i=>i.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&&!r&&e.jsx("p",{children:"Loading…"}),r&&e.jsx("div",{className:"mb-4",children:e.jsx(z,{variant:"warning",children:"Failed to load connections. Try refreshing."})}),s&&e.jsxs(e.Fragment,{children:[!k&&e.jsx("div",{className:"mb-4",children:e.jsx(z,{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(F,{size:"small",texture:"kraft",children:s.map((i,b)=>e.jsx(Se,{connection:i,isLast:b===s.length-1,onRecheck:A,rechecking:p===i.id,onConnect:m,connecting:h===i.id},i.id))}),e.jsx("div",{className:"mt-4",children:e.jsx(f,{size:"small",onClick:x,children:c?"Show Setup on open again":"Don't show Setup on open"})})]})]})},we=()=>{const{subjects:s,loading:t,available:r}=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&&!r&&e.jsx("p",{className:"opacity-60 m-0",children:"Couldn't load subjects — check the server config."}),!t&&r&&e.jsxs(F,{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(v,{})]},p)),s.length>0&&e.jsx(v,{}),e.jsx("div",{className:"pt-3",children:e.jsx(f,{size:"small",onClick:()=>l({to:"/roadmap"}),children:"Go to Roadmap"})})]})]})},J=["phase","planDraft","ideaExtend","commitSuggest","feedback"],Ae={phase:"Phase run",planDraft:"Plan draft",ideaExtend:"Idea extend",commitSuggest:"Commit suggest",feedback:"Scout chat"},Ce=140,Ee=160,Ie=110,De=()=>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"})]}),Re=({taskKey:s,agentConfig:t,isLast:r,onSave:l})=>{const p=O[t.agent]??O["claude-code"],u=p.model,h=p.effort,[n,c]=d.useState(t.model??"");d.useEffect(()=>{c(t.model??"")},[t.model]);const S=m=>{var b;const x=m,k=O[x],i={agent:x};t.model&&(k.model===null||(b=k.model)!=null&&b.includes(t.model))&&(i.model=t.model),t.effort&&Array.isArray(k.effort)&&(i.effort=t.effort),l(s,i)},y=m=>{l(s,{...t,model:m||void 0})},N=()=>{l(s,{...t,model:n||void 0})},A=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:Ae[s]}),e.jsx(q,{size:"small",width:Ce,value:t.agent,onChange:S,options:re.map(m=>({value:m,label:ce[m]}))}),Array.isArray(u)?e.jsx(q,{size:"small",width:Ee,value:t.model??"",onChange:y,options:[{value:"",label:"Default"},...u.map(m=>({value:m,label:m}))]}):u===null?e.jsx(G,{size:"small",className:"w-[160px]",value:n,placeholder:"Default model",onChange:m=>c(m.target.value),onBlur:N}):null,e.jsx("div",{className:Array.isArray(h)?"visible":"invisible",children:e.jsx(q,{size:"small",width:Ie,value:t.effort??"",onChange:A,options:[{value:"",label:"Default"},...(Array.isArray(h)?h:[]).map(m=>({value:m,label:m}))]})})]}),!r&&e.jsx(v,{})]})},ze=()=>{var Y,H;const s=d.useRef(null),[t,r]=d.useState(void 0),{iconDataUri:l,loading:p}=ie(),[u,h]=d.useState(null),n=u??l,[c,S]=d.useState(!1),[y,N]=d.useState(""),[A,m]=d.useState(""),{toast:x}=_();d.useEffect(()=>{X().then(a=>{r(a),(a==null?void 0:a.port)!==void 0&&N(String(a.port)),(a==null?void 0:a.projectName)!==void 0&&m(a.projectName)})},[]);const k=async(a,g)=>{const o=t==null?void 0:t.defaultAgents,j={phase:(o==null?void 0:o.phase)??D.phase,planDraft:(o==null?void 0:o.planDraft)??D.planDraft,ideaExtend:(o==null?void 0:o.ideaExtend)??D.ideaExtend,commitSuggest:(o==null?void 0:o.commitSuggest)??D.commitSuggest,feedback:(o==null?void 0:o.feedback)??D.feedback,[a]:g},{ok:w,error:E}=await R({defaultAgents:j});w?(r(I=>I&&{...I,defaultAgents:j}),x({title:"Saved",variant:"success"})):x({title:"Failed to save",description:E,variant:"error"})},i=async()=>{const a=Number(y);if(!t||!Number.isInteger(a)||a<=0||a===t.port)return;const{ok:g,error:o}=await R({port:a});g?(r(j=>j&&{...j,port:a}),x({title:"Saved",description:"Restart `paper-camp dev` to apply the new port.",variant:"success"})):x({title:"Failed to save",description:o,variant:"error"})},b=async()=>{var w,E,I;if(!t)return;const a=!(((E=(w=t.integration)==null?void 0:w.toolbar)==null?void 0:E.enabled)??!0),g={...t.integration,toolbar:{...(I=t.integration)==null?void 0:I.toolbar,enabled:a}},{ok:o,error:j}=await R({integration:g});o?(r(U=>U&&{...U,integration:g}),x({title:"Saved",variant:"success"})):x({title:"Failed to save",description:j,variant:"error"})},C=async()=>{const a=A.trim();if(!t||!a||a===t.projectName)return;const{ok:g,error:o}=await R({projectName:a});g?(r(j=>j&&{...j,projectName:a}),m(a),x({title:"Saved",variant:"success"})):x({title:"Failed to save",description:o,variant:"error"})},M=async a=>{var j;const g=(j=a.target.files)==null?void 0:j[0];if(!g)return;S(!0);const o=new FileReader;o.onloadend=async()=>{const w=o.result,E=await oe(w);S(!1),E?(h(w),x({title:"Saved",variant:"success"})):x({title:"Failed to save",variant:"error"})},o.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(z,{variant:"warning",children:["No papercamp/config.json found — run ",e.jsx("code",{children:"paper-camp init"})," in this directory first."]}),t&&e.jsxs(F,{size:"small",texture:"kraft",children:[e.jsxs("div",{className:"flex items-end gap-3 pb-3",children:[e.jsx(G,{value:A,onChange:a=>m(a.target.value),onBlur:C,label:"Project Name"}),e.jsxs(P,{size:"small",fillColor:"rgba(143, 185, 150, 0.25)",textColor:de.accentGreenDark,children:["v",t.version]})]}),e.jsx(v,{}),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:M,className:"hidden"}),e.jsx(f,{size:"small",onClick:()=>{var a;return(a=s.current)==null?void 0:a.click()},disabled:c,children:c?"Uploading…":"Choose File"}),p&&e.jsx("p",{className:"text-sm opacity-50 mt-1 mx-0 mb-0",children:"Loading…"}),!p&&!n&&!c&&e.jsx("p",{className:"text-sm opacity-[0.45] mt-1 mx-0 mb-0",children:"No icon set."})]})]}),e.jsx(v,{}),e.jsx("div",{className:"flex items-end gap-3 pb-3 pt-3",children:e.jsx(G,{type:"number",value:y,onChange:a=>N(a.target.value),onBlur:i,label:"Port",helperText:"Default for `paper-camp dev`. Restart the server to apply a change."})}),e.jsx(v,{}),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(f,{size:"small",onClick:b,children:((H=(Y=t.integration)==null?void 0:Y.toolbar)==null?void 0:H.enabled)??!0?"Disable":"Enable"})]}),e.jsx(v,{}),e.jsx(De,{}),J.map((a,g)=>{var o;return e.jsx(Re,{taskKey:a,agentConfig:((o=t.defaultAgents)==null?void 0:o[a])??D[a],isLast:g===J.length-1,onSave:k},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(we,{}):s==="setup"?e.jsx(ke,{}):s==="merge-policy"?e.jsx(xe,{}):e.jsx(ze,{})]})},_e=()=>{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,_e as SettingsSidebar};
2
+ //# sourceMappingURL=index-DNRXB82k.js.map