@dendelion/paper-camp 0.19.0 → 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +34 -0
- package/dist/app/assets/index-BK4XPXt_.js +4 -0
- package/dist/app/assets/index-BK4XPXt_.js.map +1 -0
- package/dist/app/assets/index-BZnU3b_x.js +2 -0
- package/dist/app/assets/index-BZnU3b_x.js.map +1 -0
- package/dist/app/assets/index-BxQBW2Oi.js +2 -0
- package/dist/app/assets/index-BxQBW2Oi.js.map +1 -0
- package/dist/app/assets/index-COEivZHG.js +6 -0
- package/dist/app/assets/index-COEivZHG.js.map +1 -0
- package/dist/app/assets/index-COQOoPiS.js +2 -0
- package/dist/app/assets/{index-B6251tL6.js.map → index-COQOoPiS.js.map} +1 -1
- package/dist/app/assets/index-DIlqYxon.js +7 -0
- package/dist/app/assets/index-DIlqYxon.js.map +1 -0
- package/dist/app/assets/index-VXeQcBh1.js +2 -0
- package/dist/app/assets/index-VXeQcBh1.js.map +1 -0
- package/dist/app/assets/{index-nuVVSVma.js → index-vgBxkLmQ.js} +2 -2
- package/dist/app/assets/index-vgBxkLmQ.js.map +1 -0
- package/dist/app/assets/main-DEUaro51.css +1 -0
- package/dist/app/assets/main-s2b5XHSS.js +425 -0
- package/dist/app/assets/main-s2b5XHSS.js.map +1 -0
- package/dist/app/assets/sidebar-section-4MxZoa3M.js +2 -0
- package/dist/app/assets/sidebar-section-4MxZoa3M.js.map +1 -0
- package/dist/app/index.html +6 -6
- package/dist/app/toolbar.js +13828 -5485
- package/dist/app/toolbar.js.map +1 -1
- package/dist/chunks/{phase-progress.CwSKAAZ-.js → phase-progress.DimXpZFz.js} +1909 -1746
- package/dist/chunks/phase-progress.DimXpZFz.js.map +1 -0
- package/dist/cli/index.js +6765 -6293
- package/dist/cli/index.js.map +1 -1
- package/dist/core/corpus-format.d.ts +25 -0
- package/dist/core/corpus-format.d.ts.map +1 -0
- package/dist/core/corpus-format.test.d.ts +2 -0
- package/dist/core/corpus-format.test.d.ts.map +1 -0
- package/dist/core/doctor/checks/metadata.d.ts.map +1 -1
- package/dist/core/doctor/rules.d.ts +6 -1
- package/dist/core/doctor/rules.d.ts.map +1 -1
- package/dist/core/entity-view.d.ts +6 -0
- package/dist/core/entity-view.d.ts.map +1 -0
- package/dist/core/git-log.d.ts +2 -0
- package/dist/core/git-log.d.ts.map +1 -1
- package/dist/core/git-pr/pr-lookup.d.ts +6 -1
- package/dist/core/git-pr/pr-lookup.d.ts.map +1 -1
- package/dist/core/git-pr/pr.d.ts +10 -0
- package/dist/core/git-pr/pr.d.ts.map +1 -1
- package/dist/core/index.js +102 -99
- package/dist/core/issues.d.ts +50 -0
- package/dist/core/issues.d.ts.map +1 -0
- package/dist/core/issues.test.d.ts +2 -0
- package/dist/core/issues.test.d.ts.map +1 -0
- package/dist/core/parse/parser.d.ts.map +1 -1
- package/dist/core/parse/schemas.d.ts +19 -4
- package/dist/core/parse/schemas.d.ts.map +1 -1
- package/dist/core/phase-run.d.ts +1 -0
- package/dist/core/phase-run.d.ts.map +1 -1
- package/dist/core/readers.d.ts +11 -5
- package/dist/core/readers.d.ts.map +1 -1
- package/dist/core/release-notes.d.ts +2 -0
- package/dist/core/release-notes.d.ts.map +1 -1
- package/dist/core/roadmap.d.ts +21 -0
- package/dist/core/roadmap.d.ts.map +1 -1
- package/dist/core/run-order.d.ts +2 -1
- package/dist/core/run-order.d.ts.map +1 -1
- package/dist/core/scaffold/scaffold.d.ts +1 -2
- package/dist/core/scaffold/scaffold.d.ts.map +1 -1
- package/dist/core/scaffold/templates.d.ts +1 -1
- package/dist/core/scaffold/templates.d.ts.map +1 -1
- package/dist/core/sections.d.ts.map +1 -1
- package/dist/core/serialize/entity-file.d.ts +79 -0
- package/dist/core/serialize/entity-file.d.ts.map +1 -0
- package/dist/core/serialize/index.d.ts +1 -0
- package/dist/core/serialize/index.d.ts.map +1 -1
- package/dist/core/serialize/serializer.d.ts +1 -74
- package/dist/core/serialize/serializer.d.ts.map +1 -1
- package/dist/core/stats.d.ts +2 -0
- package/dist/core/stats.d.ts.map +1 -1
- package/dist/core/status/status.d.ts +25 -4
- package/dist/core/status/status.d.ts.map +1 -1
- package/dist/types/index.d.ts +86 -8
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +16 -15
- package/dist/types/index.js.map +1 -1
- package/package.json +3 -3
- package/dist/app/assets/index-B6251tL6.js +0 -2
- package/dist/app/assets/index-BNoowtMI.js +0 -7
- package/dist/app/assets/index-BNoowtMI.js.map +0 -1
- package/dist/app/assets/index-D2iIIpI1.js +0 -4
- package/dist/app/assets/index-D2iIIpI1.js.map +0 -1
- package/dist/app/assets/index-D5j1njQR.js +0 -2
- package/dist/app/assets/index-D5j1njQR.js.map +0 -1
- package/dist/app/assets/index-DLkUANCR.js +0 -2
- package/dist/app/assets/index-DLkUANCR.js.map +0 -1
- package/dist/app/assets/index-nuVVSVma.js.map +0 -1
- package/dist/app/assets/index-s6CVH54G.js +0 -6
- package/dist/app/assets/index-s6CVH54G.js.map +0 -1
- package/dist/app/assets/main--EStESJz.js +0 -217
- package/dist/app/assets/main--EStESJz.js.map +0 -1
- package/dist/app/assets/main-CAJuC1Pz.css +0 -1
- package/dist/app/assets/sidebar-section-DTU8Naqm.js +0 -2
- package/dist/app/assets/sidebar-section-DTU8Naqm.js.map +0 -1
- package/dist/chunks/phase-progress.CwSKAAZ-.js.map +0 -1
package/README.md
CHANGED
|
@@ -23,6 +23,40 @@ bun run dev
|
|
|
23
23
|
|
|
24
24
|
Open `http://localhost:3333` to access the dashboard.
|
|
25
25
|
|
|
26
|
+
## Reachable from anywhere
|
|
27
|
+
|
|
28
|
+
`paper-camp dev` only answers requests from the machine it runs on, or another
|
|
29
|
+
device on the same LAN/tailnet. To reach it from the hosted client on a
|
|
30
|
+
different machine over the open internet, run:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
paper-camp dev --share
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
This opens an account-less `cloudflared` quick tunnel and prints a
|
|
37
|
+
registration link with its `https://…trycloudflare.com` address baked in —
|
|
38
|
+
open that link in the hosted client and it's paired. The address changes every
|
|
39
|
+
restart; that's the trade for needing no setup and no account.
|
|
40
|
+
|
|
41
|
+
### Already on Tailscale?
|
|
42
|
+
|
|
43
|
+
If every machine involved is already on your tailnet, `tailscale serve` gives
|
|
44
|
+
you a stable address and a real certificate with no third party in the path —
|
|
45
|
+
worth it if you have Tailscale, but more setup than `--share`:
|
|
46
|
+
|
|
47
|
+
1. Enable HTTPS certificates for your tailnet once, in the
|
|
48
|
+
[Tailscale admin console](https://login.tailscale.com/admin/dns) under
|
|
49
|
+
**DNS → HTTPS Certificates**.
|
|
50
|
+
2. From the machine running `paper-camp dev`, serve it over HTTPS at a stable
|
|
51
|
+
MagicDNS address:
|
|
52
|
+
```bash
|
|
53
|
+
sudo tailscale serve --bg --https=443 / http://localhost:3333
|
|
54
|
+
```
|
|
55
|
+
The first run needs `sudo` so Tailscale can provision the certificate.
|
|
56
|
+
3. Open `https://<your-machine>.<your-tailnet>.ts.net/` — directly, or pasted
|
|
57
|
+
into the hosted client — from any device on the tailnet. Stop sharing with
|
|
58
|
+
`tailscale serve reset`.
|
|
59
|
+
|
|
26
60
|
## Pages
|
|
27
61
|
|
|
28
62
|
- **Plans** — Browse and manage plans from `papercamp/plans/`
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import{u as b,m as L,r as c,C as O,j as e,G as P,x as T,J as y,H as M,I as B,a as D,K as F,E as U,P as S,L as q,M as E,g as k,S as N,N as V,R as _,O as $,Q as H,i as z,T as G,U as J,V as K,p as A}from"./main-s2b5XHSS.js";import{W as xe}from"./main-s2b5XHSS.js";const w="__no-subject__",W=({horizonTitle:s,item:t,candidateName:o,onClose:d,onPromoted:a})=>{const m=b(x=>x.promoteRoadmapItem),i=b(x=>x.launchIdeaExtend),{subjects:g,available:j}=L(),[u,h]=c.useState(w),[f,n]=c.useState(!1),[p,v]=c.useState(null),{toast:l}=O();c.useEffect(()=>{t&&(h(o?t.name:w),n(!1),v(null))},[t,o]);const r=async()=>{if(!(!t||!s)){n(!0),v(null);try{const x=await m(s,t,u===w?void 0:u,o??void 0),C=b.getState().ideaEntries.find(R=>R.id===x);if(C)try{await i(x,M(C,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:w,label:"No subject"},...!j&&u!==w?[{value:u,label:u}]:[],...g.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"})]})]})})},Q=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($,{pr:s.pr}),e.jsx(k,{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[g,j]=c.useState(o),{shipped:u,queued:h}=Q(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":g,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 ${g?"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(k,{size:"small",fillColor:N.planned.fill,textColor:N.planned.text,children:[h," in queue"]}),u>0&&e.jsxs(k,{size:"small",fillColor:N.done.fill,textColor:N.done.text,children:[u," shipped"]}),f>0&&e.jsxs(k,{size:"small",fillColor:"rgba(0, 0, 0, 0.06)",textColor:"rgba(0, 0, 0, 0.55)",children:[f," candidate",f===1?"":"s"]})]}),g&&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(E,{children:d}),t&&m&&e.jsx(E,{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=b(l=>l.roadmap),t=b(l=>l.roadmapError),o=b(l=>l.loadRoadmap),d=b(l=>l.roadmapFilters),[a,m]=c.useState(null),i=b(l=>l.plans);D();const g=F(),{item:j}=U({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(S,{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(S,{children:"Roadmap"}),e.jsx("p",{className:"opacity-50",children:"Loading…"})]});if(s.horizons.length===0)return e.jsxs("div",{children:[e.jsx(S,{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 V(l,r,x),await o()},n=q(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(S,{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,C)=>g(x,C)},r.name))})]},l.title))}),e.jsx(W,{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=b(r=>r.loadRoadmap),[a,m]=c.useState(t[0]??""),[i,g]=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]??""),g(""),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=>g(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"],I="pc-row-label font-handwritten text-xs font-semibold opacity-[0.45]",ce=()=>{const s=b(n=>n.roadmap),t=b(n=>n.roadmapFilters),o=b(n=>n.toggleRoadmapHorizon),d=b(n=>n.toggleRoadmapStatus),[a,m]=c.useState(!1);if(!s)return null;const i=s.horizons.map(n=>n.title),g=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",children:[e.jsx("div",{className:"flex h-[64px] items-center",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:I,children:"Horizon"}),e.jsx("div",{className:"flex flex-col",children:i.map(n=>e.jsx(A,{size:"small",active:u.has(n),onClick:()=>o(n),className:"pc-row text-xs",action:e.jsx("span",{className:"text-2xs text-ink-500",children:g[n]??0}),children:n},n))})]}),e.jsxs("div",{children:[e.jsx("div",{className:I,children:"Status"}),e.jsxs("div",{className:"flex flex-col",children:[f.length===0&&e.jsx("span",{className:"opacity-50 text-2xs",children:"No linked ideas"}),f.map(n=>e.jsx(A,{size:"small",active:h.has(n),onClick:()=>d(n),className:"pc-row text-xs",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-BK4XPXt_.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index-BK4XPXt_.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, useOpenEntity } 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 openEntity = useOpenEntity();\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) => openEntity(id, title)}\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\n// Matches SidebarSection — handwritten, no caps, one grid cell.\nconst sectionLabelClass = 'pc-row-label font-handwritten text-xs font-semibold opacity-[0.45]';\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\">\n <div className=\"flex h-[64px] items-center\">\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 </div>\n\n <div>\n <div className={sectionLabelClass}>Horizon</div>\n <div className=\"flex flex-col\">\n {horizonTitles.map((title) => (\n <ListItem\n key={title}\n size=\"small\"\n active={activeHorizons.has(title)}\n onClick={() => toggleRoadmapHorizon(title)}\n className=\"pc-row text-xs\"\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\">\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=\"pc-row text-xs\"\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","useNavigate","openEntity","useOpenEntity","highlightedItem","useSearch","containerRef","useRef","row","_a","PageTitle","graduatedByItem","handleAddCandidate","itemName","addRoadmapCandidate","horizons","filterHorizons","totalVisible","count","horizon","hasActiveFilters","title","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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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{j as e,ag as g,C as k,r as c,ah as w,ai as v,aj as E,ak as I,al as C,a7 as D,J as R,am as $,u as l,K as T,P}from"./main-s2b5XHSS.js";function S(a){return Math.floor((Date.now()-Date.parse(a))/864e5)}function L(a){return a.kind==="question"?a.ageDays:S(a.date)}const N=a=>Number.isFinite(a)?a<=0?"today":a===1?"1 day ago":`${a} days ago`:"age unknown",A={completed:"Completed",reply:"Reply"},K=({notification:a,ageDays:o,onOpen:s})=>e.jsxs("div",{className:"flex items-center gap-3 py-2.5 border-b border-black/10 last:border-b-0",children:[e.jsx("button",{type:"button",onClick:s,"aria-label":`Open ${a.entityTitle}`,className:"bg-transparent border-none p-0 cursor-pointer",children:e.jsx(g,{id:a.entityId})}),e.jsx("span",{className:"text-xs uppercase tracking-wide opacity-40 whitespace-nowrap",children:A[a.kind]}),e.jsx("span",{className:"flex-1 min-w-0 truncate",children:a.text}),e.jsx("span",{className:"opacity-50 text-xs whitespace-nowrap",children:N(o)})]}),O=({question:a,plan:o,expanded:s,onToggle:b,onOpen:d,reload:h})=>{const{toast:p}=k(),[n,x]=c.useState(""),{sending:u,send:t,undo:j,undoing:r,undoEdit:y}=w(o,{reload:h,notify:p}),i=`question-reply:${a.entityId}`;c.useEffect(()=>{const f=v(i);f&&x(f)},[i]),c.useEffect(()=>{n&&E(i,n)},[i,n]);const m=async()=>{n.trim()&&await t(n.trim())&&(x(""),$(i))};return e.jsxs("div",{className:"border-b border-black/10 last:border-b-0",children:[e.jsxs("div",{className:"flex items-center gap-3 py-2.5",children:[e.jsx("button",{type:"button",onClick:d,"aria-label":`Open ${a.entityTitle}`,className:"bg-transparent border-none p-0 cursor-pointer",children:e.jsx(g,{id:a.entityId})}),e.jsx("button",{type:"button",onClick:b,"aria-expanded":s,className:"flex-1 min-w-0 truncate text-left bg-transparent border-none p-0 cursor-pointer",children:a.text}),e.jsx("span",{className:"opacity-50 text-xs whitespace-nowrap",children:N(a.ageDays)})]}),s&&e.jsxs("div",{className:"flex flex-col gap-3 pb-4",children:[e.jsx(I,{messages:o.thread??[],undo:j,undoing:r,onUndo:y}),e.jsx(C,{value:n,onChange:f=>x(f.target.value),"aria-label":`Reply to ${a.entityTitle}`,placeholder:"Reply to resolve and resume…",rows:2,disabled:u}),e.jsxs("div",{className:"flex justify-end items-center gap-3",children:[e.jsx("span",{className:u?"visible":"invisible",children:e.jsx(D,{size:"small",label:"Agent replying…"})}),e.jsx(R,{size:"small",onClick:m,disabled:u||!n.trim(),children:"Reply"})]})]})]})},q=()=>{const[a,o]=c.useState(null),s=l(t=>t.notifications),b=l(t=>t.notificationsError!==null),d=l(t=>t.plans),h=l(t=>t.loadPlans),p=l(t=>t.loadNotifications),n=l(t=>t.markRead),x=T();c.useEffect(()=>{p()},[p]),c.useEffect(()=>{if(s)for(const t of s)t.kind!=="question"&&!t.read&&n(t.id)},[s,n]);const u=async()=>{await Promise.all([h(),p()])};return e.jsxs("div",{children:[e.jsx(P,{children:"Inbox"}),e.jsx("p",{className:"opacity-50 mb-6",children:"Every parked question, completed run, and agent reply, oldest first."}),b&&e.jsx("p",{className:"opacity-50",children:"Couldn't load notifications."}),!b&&!s&&e.jsx("p",{className:"opacity-50",children:"Loading…"}),s&&s.length===0&&e.jsx("p",{className:"opacity-50",children:"Nothing here — no parked questions or recent activity."}),s&&s.length>0&&e.jsx("div",{className:"flex flex-col",children:s.map((t,j)=>{const r=`${t.entityId}-${t.kind}-${j}`,y=()=>x(t.entityId,t.entityTitle);if(t.kind!=="question")return e.jsx(K,{notification:t,ageDays:L(t),onOpen:y},r);const i=d==null?void 0:d.entries.find(m=>m.id===t.entityId);return i?e.jsx(O,{question:t,plan:i,expanded:a===r,onToggle:()=>o(m=>m===r?null:r),onOpen:y,reload:u},r):e.jsxs("div",{className:"flex items-center gap-3 py-2.5 border-b border-black/10 last:border-b-0",children:[e.jsx("button",{type:"button",onClick:y,"aria-label":`Open ${t.entityTitle}`,className:"bg-transparent border-none p-0 cursor-pointer",children:e.jsx(g,{id:t.entityId})}),e.jsx("span",{className:"flex-1 min-w-0 truncate",children:t.text}),e.jsx("span",{className:"opacity-50 text-xs whitespace-nowrap",children:N(t.ageDays)})]},r)})})]})};export{q as InboxPage};
|
|
2
|
+
//# sourceMappingURL=index-BZnU3b_x.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index-BZnU3b_x.js","sources":["../../../src/core/notifications.ts","../../../src/app/features/inbox/helpers.ts","../../../src/app/features/inbox/notification-row.tsx","../../../src/app/features/inbox/question-row.tsx","../../../src/app/features/inbox/inbox-page.tsx"],"sourcesContent":["import type { Notification, ParkedQuestion, StoredNotification } from '../types/index';\n\nfunction ageInDays(date: string): number {\n return Math.floor((Date.now() - Date.parse(date)) / 86_400_000);\n}\n\nexport function notificationAgeDays(notification: Notification): number {\n return notification.kind === 'question' ? notification.ageDays : ageInDays(notification.date);\n}\n\n/** Every parked question alongside completed/reply notifications, oldest-first — the\n * unified feed (IDEA-153) replacing the questions-only Inbox. */\nexport function mergeNotifications(\n parkedQuestions: ParkedQuestion[],\n stored: StoredNotification[],\n): Notification[] {\n const entries: Notification[] = [\n ...parkedQuestions.map((q): Notification => ({ ...q, kind: 'question' })),\n ...stored,\n ];\n return entries.sort((a, b) => notificationAgeDays(b) - notificationAgeDays(a));\n}\n","export const formatAge = (ageDays: number): string => {\n if (!Number.isFinite(ageDays)) return 'age unknown';\n if (ageDays <= 0) return 'today';\n return ageDays === 1 ? '1 day ago' : `${ageDays} days ago`;\n};\n","import { PlanIdStamp } from '@/app/features/plans/components';\nimport type { StoredNotification } from '@/types/index';\nimport { formatAge } from './helpers';\n\nconst KIND_LABELS: Record<StoredNotification['kind'], string> = {\n completed: 'Completed',\n reply: 'Reply',\n};\n\ninterface NotificationRowProps {\n notification: StoredNotification;\n ageDays: number;\n onOpen: () => void;\n}\n\nexport const NotificationRow = ({ notification, ageDays, onOpen }: NotificationRowProps) => (\n <div className=\"flex items-center gap-3 py-2.5 border-b border-black/10 last:border-b-0\">\n <button\n type=\"button\"\n onClick={onOpen}\n aria-label={`Open ${notification.entityTitle}`}\n className=\"bg-transparent border-none p-0 cursor-pointer\"\n >\n <PlanIdStamp id={notification.entityId} />\n </button>\n <span className=\"text-xs uppercase tracking-wide opacity-40 whitespace-nowrap\">\n {KIND_LABELS[notification.kind]}\n </span>\n <span className=\"flex-1 min-w-0 truncate\">{notification.text}</span>\n <span className=\"opacity-50 text-xs whitespace-nowrap\">{formatAge(ageDays)}</span>\n </div>\n);\n","import { FeedbackThread, PlanIdStamp } from '@/app/features/plans/components';\nimport { useSendFeedbackMessage } from '@/app/features/plans/hooks';\nimport { readLocalDraft, removeLocalDraft, writeLocalDraft } from '@/app/utils/local-draft-store';\nimport type { ParkedQuestion, PlanEntry } from '@/types/index';\nimport { Button, Spinner, Textarea, useToast } from '@dendelion/paper-ui';\nimport { useEffect, useState } from 'react';\nimport { formatAge } from './helpers';\n\ninterface QuestionRowProps {\n question: ParkedQuestion;\n plan: PlanEntry;\n expanded: boolean;\n onToggle: () => void;\n onOpen: () => void;\n reload: () => Promise<void>;\n}\n\nexport const QuestionRow = ({\n question,\n plan,\n expanded,\n onToggle,\n onOpen,\n reload,\n}: QuestionRowProps) => {\n const { toast } = useToast();\n const [input, setInput] = useState('');\n const { sending, send, undo, undoing, undoEdit } = useSendFeedbackMessage(plan, {\n reload,\n notify: toast,\n });\n\n const draftKey = `question-reply:${question.entityId}`;\n\n useEffect(() => {\n const draft = readLocalDraft<string>(draftKey);\n if (draft) setInput(draft);\n }, [draftKey]);\n\n useEffect(() => {\n if (!input) return;\n writeLocalDraft(draftKey, input);\n }, [draftKey, input]);\n\n const handleSend = async () => {\n if (!input.trim()) return;\n if (await send(input.trim())) {\n setInput('');\n removeLocalDraft(draftKey);\n }\n };\n\n return (\n <div className=\"border-b border-black/10 last:border-b-0\">\n <div className=\"flex items-center gap-3 py-2.5\">\n <button\n type=\"button\"\n onClick={onOpen}\n aria-label={`Open ${question.entityTitle}`}\n className=\"bg-transparent border-none p-0 cursor-pointer\"\n >\n <PlanIdStamp id={question.entityId} />\n </button>\n <button\n type=\"button\"\n onClick={onToggle}\n aria-expanded={expanded}\n className=\"flex-1 min-w-0 truncate text-left bg-transparent border-none p-0 cursor-pointer\"\n >\n {question.text}\n </button>\n <span className=\"opacity-50 text-xs whitespace-nowrap\">{formatAge(question.ageDays)}</span>\n </div>\n {expanded && (\n <div className=\"flex flex-col gap-3 pb-4\">\n <FeedbackThread\n messages={plan.thread ?? []}\n undo={undo}\n undoing={undoing}\n onUndo={undoEdit}\n />\n <Textarea\n value={input}\n onChange={(e) => setInput(e.target.value)}\n aria-label={`Reply to ${question.entityTitle}`}\n placeholder=\"Reply to resolve and resume…\"\n rows={2}\n disabled={sending}\n />\n <div className=\"flex justify-end items-center gap-3\">\n <span className={sending ? 'visible' : 'invisible'}>\n <Spinner size=\"small\" label=\"Agent replying…\" />\n </span>\n <Button size=\"small\" onClick={handleSend} disabled={sending || !input.trim()}>\n Reply\n </Button>\n </div>\n </div>\n )}\n </div>\n );\n};\n","import { PageTitle } from '@/app/components/page-title';\nimport { PlanIdStamp } from '@/app/features/plans/components';\nimport { useOpenEntity } from '@/app/hooks';\nimport { useAppStore } from '@/app/stores/app-store';\nimport { notificationAgeDays } from '@/core/notifications';\nimport { useEffect, useState } from 'react';\nimport { formatAge } from './helpers';\nimport { NotificationRow } from './notification-row';\nimport { QuestionRow } from './question-row';\n\nexport const InboxPage = () => {\n const [expandedKey, setExpandedKey] = useState<string | null>(null);\n const notifications = useAppStore((s) => s.notifications);\n const loadFailed = useAppStore((s) => s.notificationsError !== null);\n const plans = useAppStore((s) => s.plans);\n const loadPlans = useAppStore((s) => s.loadPlans);\n const loadNotifications = useAppStore((s) => s.loadNotifications);\n const markRead = useAppStore((s) => s.markRead);\n const openEntity = useOpenEntity();\n\n useEffect(() => {\n loadNotifications();\n }, [loadNotifications]);\n\n useEffect(() => {\n if (!notifications) return;\n for (const n of notifications) {\n if (n.kind !== 'question' && !n.read) markRead(n.id);\n }\n }, [notifications, markRead]);\n\n const reload = async () => {\n await Promise.all([loadPlans(), loadNotifications()]);\n };\n\n return (\n <div>\n <PageTitle>Inbox</PageTitle>\n <p className=\"opacity-50 mb-6\">\n Every parked question, completed run, and agent reply, oldest first.\n </p>\n {loadFailed && <p className=\"opacity-50\">Couldn't load notifications.</p>}\n {!loadFailed && !notifications && <p className=\"opacity-50\">Loading…</p>}\n {notifications && notifications.length === 0 && (\n <p className=\"opacity-50\">Nothing here — no parked questions or recent activity.</p>\n )}\n {notifications && notifications.length > 0 && (\n <div className=\"flex flex-col\">\n {notifications.map((n, i) => {\n const key = `${n.entityId}-${n.kind}-${i}`;\n const onOpen = () => openEntity(n.entityId, n.entityTitle);\n\n if (n.kind !== 'question') {\n return (\n <NotificationRow\n key={key}\n notification={n}\n ageDays={notificationAgeDays(n)}\n onOpen={onOpen}\n />\n );\n }\n\n const plan = plans?.entries.find((p) => p.id === n.entityId);\n if (!plan) {\n return (\n <div\n key={key}\n className=\"flex items-center gap-3 py-2.5 border-b border-black/10 last:border-b-0\"\n >\n <button\n type=\"button\"\n onClick={onOpen}\n aria-label={`Open ${n.entityTitle}`}\n className=\"bg-transparent border-none p-0 cursor-pointer\"\n >\n <PlanIdStamp id={n.entityId} />\n </button>\n <span className=\"flex-1 min-w-0 truncate\">{n.text}</span>\n <span className=\"opacity-50 text-xs whitespace-nowrap\">\n {formatAge(n.ageDays)}\n </span>\n </div>\n );\n }\n return (\n <QuestionRow\n key={key}\n question={n}\n plan={plan}\n expanded={expandedKey === key}\n onToggle={() => setExpandedKey((cur) => (cur === key ? null : key))}\n onOpen={onOpen}\n reload={reload}\n />\n );\n })}\n </div>\n )}\n </div>\n );\n};\n"],"names":["ageInDays","date","notificationAgeDays","notification","formatAge","ageDays","KIND_LABELS","NotificationRow","onOpen","jsxs","jsx","PlanIdStamp","QuestionRow","question","plan","expanded","onToggle","reload","toast","useToast","input","setInput","useState","sending","send","undo","undoing","undoEdit","useSendFeedbackMessage","draftKey","useEffect","draft","readLocalDraft","writeLocalDraft","handleSend","removeLocalDraft","FeedbackThread","Textarea","e","Spinner","Button","InboxPage","expandedKey","setExpandedKey","notifications","useAppStore","s","loadFailed","plans","loadPlans","loadNotifications","markRead","openEntity","useOpenEntity","n","PageTitle","i","key","p","cur"],"mappings":"4IAEA,SAASA,EAAUC,EAAsB,CACvC,OAAO,KAAK,OAAO,KAAK,MAAQ,KAAK,MAAMA,CAAI,GAAK,KAAU,CAChE,CAEO,SAASC,EAAoBC,EAAoC,CACtE,OAAOA,EAAa,OAAS,WAAaA,EAAa,QAAUH,EAAUG,EAAa,IAAI,CAC9F,CCRO,MAAMC,EAAaC,GACnB,OAAO,SAASA,CAAO,EACxBA,GAAW,EAAU,QAClBA,IAAY,EAAI,YAAc,GAAGA,CAAO,YAFT,cCGlCC,EAA0D,CAC9D,UAAW,YACX,MAAO,OACT,EAQaC,EAAkB,CAAC,CAAE,aAAAJ,EAAc,QAAAE,EAAS,OAAAG,KACvDC,EAAAA,KAAC,MAAA,CAAI,UAAU,0EACb,SAAA,CAAAC,EAAAA,IAAC,SAAA,CACC,KAAK,SACL,QAASF,EACT,aAAY,QAAQL,EAAa,WAAW,GAC5C,UAAU,gDAEV,SAAAO,EAAAA,IAACC,EAAA,CAAY,GAAIR,EAAa,QAAA,CAAU,CAAA,CAAA,QAEzC,OAAA,CAAK,UAAU,+DACb,SAAAG,EAAYH,EAAa,IAAI,EAChC,EACAO,EAAAA,IAAC,OAAA,CAAK,UAAU,0BAA2B,WAAa,KAAK,QAC5D,OAAA,CAAK,UAAU,uCAAwC,SAAAN,EAAUC,CAAO,CAAA,CAAE,CAAA,EAC7E,ECbWO,EAAc,CAAC,CAC1B,SAAAC,EACA,KAAAC,EACA,SAAAC,EACA,SAAAC,EACA,OAAAR,EACA,OAAAS,CACF,IAAwB,CACtB,KAAM,CAAE,MAAAC,CAAA,EAAUC,EAAA,EACZ,CAACC,EAAOC,CAAQ,EAAIC,EAAAA,SAAS,EAAE,EAC/B,CAAE,QAAAC,EAAS,KAAAC,EAAM,KAAAC,EAAM,QAAAC,EAAS,SAAAC,CAAA,EAAaC,EAAuBd,EAAM,CAC9E,OAAAG,EACA,OAAQC,CAAA,CACT,EAEKW,EAAW,kBAAkBhB,EAAS,QAAQ,GAEpDiB,EAAAA,UAAU,IAAM,CACd,MAAMC,EAAQC,EAAuBH,CAAQ,EACzCE,KAAgBA,CAAK,CAC3B,EAAG,CAACF,CAAQ,CAAC,EAEbC,EAAAA,UAAU,IAAM,CACTV,GACLa,EAAgBJ,EAAUT,CAAK,CACjC,EAAG,CAACS,EAAUT,CAAK,CAAC,EAEpB,MAAMc,EAAa,SAAY,CACxBd,EAAM,QACP,MAAMI,EAAKJ,EAAM,KAAA,CAAM,IACzBC,EAAS,EAAE,EACXc,EAAiBN,CAAQ,EAE7B,EAEA,OACEpB,EAAAA,KAAC,MAAA,CAAI,UAAU,2CACb,SAAA,CAAAA,EAAAA,KAAC,MAAA,CAAI,UAAU,iCACb,SAAA,CAAAC,EAAAA,IAAC,SAAA,CACC,KAAK,SACL,QAASF,EACT,aAAY,QAAQK,EAAS,WAAW,GACxC,UAAU,gDAEV,SAAAH,EAAAA,IAACC,EAAA,CAAY,GAAIE,EAAS,QAAA,CAAU,CAAA,CAAA,EAEtCH,EAAAA,IAAC,SAAA,CACC,KAAK,SACL,QAASM,EACT,gBAAeD,EACf,UAAU,kFAET,SAAAF,EAAS,IAAA,CAAA,QAEX,OAAA,CAAK,UAAU,uCAAwC,SAAAT,EAAUS,EAAS,OAAO,CAAA,CAAE,CAAA,EACtF,EACCE,GACCN,EAAAA,KAAC,MAAA,CAAI,UAAU,2BACb,SAAA,CAAAC,EAAAA,IAAC0B,EAAA,CACC,SAAUtB,EAAK,QAAU,CAAA,EACzB,KAAAW,EACA,QAAAC,EACA,OAAQC,CAAA,CAAA,EAEVjB,EAAAA,IAAC2B,EAAA,CACC,MAAOjB,EACP,SAAWkB,GAAMjB,EAASiB,EAAE,OAAO,KAAK,EACxC,aAAY,YAAYzB,EAAS,WAAW,GAC5C,YAAY,+BACZ,KAAM,EACN,SAAUU,CAAA,CAAA,EAEZd,EAAAA,KAAC,MAAA,CAAI,UAAU,sCACb,SAAA,CAAAC,EAAAA,IAAC,OAAA,CAAK,UAAWa,EAAU,UAAY,YACrC,SAAAb,EAAAA,IAAC6B,EAAA,CAAQ,KAAK,QAAQ,MAAM,iBAAA,CAAkB,EAChD,EACA7B,EAAAA,IAAC8B,EAAA,CAAO,KAAK,QAAQ,QAASN,EAAY,SAAUX,GAAW,CAACH,EAAM,KAAA,EAAQ,SAAA,OAAA,CAE9E,CAAA,CAAA,CACF,CAAA,CAAA,CACF,CAAA,EAEJ,CAEJ,EC3FaqB,EAAY,IAAM,CAC7B,KAAM,CAACC,EAAaC,CAAc,EAAIrB,EAAAA,SAAwB,IAAI,EAC5DsB,EAAgBC,EAAaC,GAAMA,EAAE,aAAa,EAClDC,EAAaF,EAAaC,GAAMA,EAAE,qBAAuB,IAAI,EAC7DE,EAAQH,EAAaC,GAAMA,EAAE,KAAK,EAClCG,EAAYJ,EAAaC,GAAMA,EAAE,SAAS,EAC1CI,EAAoBL,EAAaC,GAAMA,EAAE,iBAAiB,EAC1DK,EAAWN,EAAaC,GAAMA,EAAE,QAAQ,EACxCM,EAAaC,EAAA,EAEnBvB,EAAAA,UAAU,IAAM,CACdoB,EAAA,CACF,EAAG,CAACA,CAAiB,CAAC,EAEtBpB,EAAAA,UAAU,IAAM,CACd,GAAKc,EACL,UAAWU,KAAKV,EACVU,EAAE,OAAS,YAAc,CAACA,EAAE,MAAMH,EAASG,EAAE,EAAE,CAEvD,EAAG,CAACV,EAAeO,CAAQ,CAAC,EAE5B,MAAMlC,EAAS,SAAY,CACzB,MAAM,QAAQ,IAAI,CAACgC,IAAaC,EAAA,CAAmB,CAAC,CACtD,EAEA,cACG,MAAA,CACC,SAAA,CAAAxC,EAAAA,IAAC6C,GAAU,SAAA,OAAA,CAAK,EAChB7C,EAAAA,IAAC,IAAA,CAAE,UAAU,kBAAkB,SAAA,uEAE/B,EACCqC,GAAcrC,EAAAA,IAAC,IAAA,CAAE,UAAU,aAAa,SAAA,+BAA4B,EACpE,CAACqC,GAAc,CAACH,SAAkB,IAAA,CAAE,UAAU,aAAa,SAAA,WAAQ,EACnEA,GAAiBA,EAAc,SAAW,SACxC,IAAA,CAAE,UAAU,aAAa,SAAA,wDAAA,CAAsD,EAEjFA,GAAiBA,EAAc,OAAS,GACvClC,EAAAA,IAAC,MAAA,CAAI,UAAU,gBACZ,SAAAkC,EAAc,IAAI,CAACU,EAAGE,IAAM,CAC3B,MAAMC,EAAM,GAAGH,EAAE,QAAQ,IAAIA,EAAE,IAAI,IAAIE,CAAC,GAClChD,EAAS,IAAM4C,EAAWE,EAAE,SAAUA,EAAE,WAAW,EAEzD,GAAIA,EAAE,OAAS,WACb,OACE5C,EAAAA,IAACH,EAAA,CAEC,aAAc+C,EACd,QAASpD,EAAoBoD,CAAC,EAC9B,OAAA9C,CAAA,EAHKiD,CAAA,EAQX,MAAM3C,EAAOkC,GAAA,YAAAA,EAAO,QAAQ,KAAMU,GAAMA,EAAE,KAAOJ,EAAE,UACnD,OAAKxC,EAsBHJ,EAAAA,IAACE,EAAA,CAEC,SAAU0C,EACV,KAAAxC,EACA,SAAU4B,IAAgBe,EAC1B,SAAU,IAAMd,EAAgBgB,GAASA,IAAQF,EAAM,KAAOA,CAAI,EAClE,OAAAjD,EACA,OAAAS,CAAA,EANKwC,CAAA,EArBLhD,EAAAA,KAAC,MAAA,CAEC,UAAU,0EAEV,SAAA,CAAAC,EAAAA,IAAC,SAAA,CACC,KAAK,SACL,QAASF,EACT,aAAY,QAAQ8C,EAAE,WAAW,GACjC,UAAU,gDAEV,SAAA5C,EAAAA,IAACC,EAAA,CAAY,GAAI2C,EAAE,QAAA,CAAU,CAAA,CAAA,EAE/B5C,EAAAA,IAAC,OAAA,CAAK,UAAU,0BAA2B,WAAE,KAAK,QACjD,OAAA,CAAK,UAAU,uCACb,SAAAN,EAAUkD,EAAE,OAAO,CAAA,CACtB,CAAA,CAAA,EAdKG,CAAA,CA6Bb,CAAC,CAAA,CACH,CAAA,EAEJ,CAEJ"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{j as a,ag as L,ak as R,J as v,a7 as N,r as p,u as i,a4 as C,K,P as S,an as D,ao as M}from"./main-s2b5XHSS.js";const w=(o,t)=>`${o}:${t}`;function z(o){return o.filter(t=>t.outcome==="error").map(t=>{const n=t.planId??t.id;return{id:w("agent-run",n),sourceKind:"agent-run",sourceKey:n,entityId:t.planId,entityTitle:t.planTitle,title:`${t.planTitle} — ${t.taskKind} failed`,reason:t.reason??"The run ended in error with no reason recorded.",occurredAt:t.endedAt,thread:[],status:"open"}})}function O(o){return o.filter(t=>t.status==="fail").map(t=>({id:w("check",t.name),sourceKind:"check",sourceKey:t.name,title:`"${t.name}" check is failing`,reason:`The command was \`${t.cmd}\`.`,output:t.output||void 0,occurredAt:t.lastRun??void 0,thread:[],status:"open"}))}function q(o){return o.filter(t=>{var n;return((n=t.pr)==null?void 0:n.reviewDecision)==="changes-requested"}).map(t=>{const n=t.pr,r=`${n.number}:${n.headSha??"unknown"}`;return{id:w("pr-review",r),sourceKind:"pr-review",sourceKey:r,entityId:t.id,entityTitle:t.title,title:`${t.title} — PR review requested changes`,reason:`PR #${n.number} has unresolved change requests.`,thread:[],status:"open"}})}function J(o,t){return o.map(n=>{var l;const r=t.find(s=>s.issueSource===n.id);if(r!=null&&r.id)return{...n,promotedFixId:r.id};const d=n.entityId?t.find(s=>s.id===n.entityId):void 0;return((l=d==null?void 0:d.fixes)==null?void 0:l.some(s=>s.source==="issue"&&s.text===n.title))&&(d!=null&&d.id)?{...n,promotedFixId:d.id}:n}).filter(n=>{const r=n.promotedFixId&&t.find(d=>d.id===n.promotedFixId);return!(r&&r.status==="done")})}function B(o,t){return t.filter(n=>n.issueId===o.id).sort((n,r)=>n.endedAt.localeCompare(r.endedAt)).map(n=>({kind:"log",date:n.endedAt.slice(0,10),from:"agent",text:n.outcome==="done"?"Ran a fix agent — finished.":n.outcome==="error"?`Ran a fix agent — failed: ${n.reason??"no reason recorded"}`:"Ran a fix agent — superseded by a newer run."}))}const U=o=>{if(!o)return"";const t=Math.floor((Date.now()-Date.parse(o))/864e5);return Number.isFinite(t)?t<=0?"today":t===1?"1 day ago":`${t} days ago`:""},$="9999-12-31T23:59:59.999Z",V=o=>[...o].sort((t,n)=>(t.occurredAt??$).localeCompare(n.occurredAt??$)),Z=({issue:o,expanded:t,onToggle:n,onOpen:r,fixing:d,fixDisabled:m,onFix:l,promoteLabel:s,promoting:u,promoteDisabled:I,onPromote:b,onOpenPromoted:y})=>{const j=async()=>{try{await l()}catch(c){alert(c.message)}},g=async()=>{try{await b()}catch(c){alert(c.message)}};return a.jsxs("div",{className:"border-b border-black/10 last:border-b-0",children:[a.jsxs("div",{className:"flex items-center gap-3 py-2.5",children:[r&&a.jsx("button",{type:"button",onClick:r,"aria-label":`Open ${o.entityTitle}`,className:"bg-transparent border-none p-0 cursor-pointer",children:a.jsx(L,{id:o.entityId})}),a.jsx("button",{type:"button",onClick:n,"aria-expanded":t,className:"flex-1 min-w-0 truncate text-left bg-transparent border-none p-0 cursor-pointer",children:o.title}),a.jsx("span",{className:"opacity-50 text-xs whitespace-nowrap",children:U(o.occurredAt)})]}),t&&a.jsxs("div",{className:"flex flex-col gap-3 pb-4",children:[a.jsx("p",{children:o.reason}),o.output&&a.jsx("pre",{className:"max-h-48 overflow-auto whitespace-pre-wrap text-left font-mono text-2xs opacity-70",children:o.output}),o.thread.length>0&&a.jsx(R,{messages:o.thread,undo:null,undoing:!1,onUndo:()=>{}}),a.jsx("div",{className:"flex items-center gap-3",children:o.promotedFixId?a.jsxs(v,{size:"small",onClick:y,children:["View ",o.promotedFixId]}):a.jsxs(a.Fragment,{children:[a.jsx(v,{size:"small",onClick:j,disabled:m,children:d?"Fixing…":"Fix it here"}),d&&a.jsx(N,{size:"small",label:"Agent fixing…"}),a.jsx(v,{size:"small",onClick:g,disabled:I,children:u?"Promoting…":s}),u&&a.jsx(N,{size:"small",label:"Promoting…"})]})})]})]})},_=(o,t)=>{const n=o.entityId?t.find(r=>r.id===o.entityId):void 0;return n?M(n)?"Promote to fix":`Add to ${n.id}'s fixes`:"Promote to idea"},H=()=>{var T;const[o,t]=p.useState(null),[n,r]=p.useState(null),d=i(e=>e.taskLog),m=i(e=>e.taskLogLoading),l=i(e=>e.loadTaskLog),s=i(e=>e.plans),u=i(e=>e.ideaEntries),I=i(e=>e.agentStatus),b=i(e=>e.launchIssueFix),y=i(e=>e.loadPlans),j=i(e=>e.loadIdeas),{checks:g}=C(),c=K();p.useEffect(()=>{l()},[l]);const P=p.useMemo(()=>((s==null?void 0:s.entries)??[]).filter(e=>e.id!=null),[s]),x=p.useMemo(()=>[...P,...u].filter(e=>e.id!=null),[P,u]),f=p.useMemo(()=>{const e=V([...z(d),...O(g),...q(x)]);return J(e,x).map(h=>({...h,thread:B(h,d)}))},[d,g,x]),E=async e=>{r(e.id);try{await D(e),await Promise.all([y(),j()])}finally{r(null)}},F=(T=I.find(e=>e.taskKind==="issue-fix"&&e.status!=="done"&&e.status!=="error"&&e.status!=="superseded"))==null?void 0:T.issueId;return a.jsxs("div",{children:[a.jsx(S,{children:"Issues"}),a.jsx("p",{className:"opacity-50 mb-6",children:"Every failing agent run, red check, and PR review, oldest first."}),m&&f.length===0&&a.jsx("p",{className:"opacity-50",children:"Loading…"}),!m&&f.length===0&&a.jsx("p",{className:"opacity-50",children:"Nothing broken right now."}),f.length>0&&a.jsx("div",{className:"flex flex-col",children:f.map(e=>{var A;const h=e.promotedFixId?((A=x.find(k=>k.id===e.promotedFixId))==null?void 0:A.title)??e.promotedFixId:void 0;return a.jsx(Z,{issue:e,expanded:o===e.id,onToggle:()=>t(k=>k===e.id?null:e.id),onOpen:e.entityId&&e.entityTitle?()=>c(e.entityId,e.entityTitle):void 0,fixing:F===e.id,fixDisabled:F!==void 0,onFix:()=>b(e.id,e.title,e.reason,e.output),promoteLabel:_(e,x),promoting:n===e.id,promoteDisabled:n!==null,onPromote:()=>E(e),onOpenPromoted:e.promotedFixId?()=>c(e.promotedFixId,h):void 0},e.id)})})]})};export{H as IssuesPage};
|
|
2
|
+
//# sourceMappingURL=index-BxQBW2Oi.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index-BxQBW2Oi.js","sources":["../../../src/core/issues.ts","../../../src/app/features/issues/helpers.ts","../../../src/app/features/issues/issue-row.tsx","../../../src/app/features/issues/issues-page.tsx"],"sourcesContent":["import type {\n DeskCheckState,\n EntityStatus,\n GitSyncFailure,\n Issue,\n PhaseItem,\n PrInfo,\n TaskLogEntry,\n ThreadMessage,\n} from '../types/index';\n\nconst issueId = (sourceKind: Issue['sourceKind'], sourceKey: string): string =>\n `${sourceKind}:${sourceKey}`;\n\n/** A failed agent run (`outcome: 'error'` in tasks.log), keyed by the plan it ran\n * against so a repeat failure on the same plan continues one issue. Runs with no\n * `planId` (e.g. a batch task) key off their own task id instead. */\nexport function collectAgentRunIssues(taskLog: TaskLogEntry[]): Issue[] {\n return taskLog\n .filter((entry) => entry.outcome === 'error')\n .map((entry) => {\n const sourceKey = entry.planId ?? entry.id;\n return {\n id: issueId('agent-run', sourceKey),\n sourceKind: 'agent-run',\n sourceKey,\n entityId: entry.planId,\n entityTitle: entry.planTitle,\n title: `${entry.planTitle} — ${entry.taskKind} failed`,\n reason: entry.reason ?? 'The run ended in error with no reason recorded.',\n occurredAt: entry.endedAt,\n thread: [],\n status: 'open',\n };\n });\n}\n\n/** A red project check (Quality/Tests/Consistency — Docs keeps its own browse flow\n * and is excluded, same carve-out as `buildCheckFixes`), keyed by check name. */\nexport function collectCheckIssues(checks: DeskCheckState[]): Issue[] {\n return checks\n .filter((check) => check.status === 'fail')\n .map((check) => ({\n id: issueId('check', check.name),\n sourceKind: 'check',\n sourceKey: check.name,\n title: `\"${check.name}\" check is failing`,\n reason: `The command was \\`${check.cmd}\\`.`,\n output: check.output || undefined,\n occurredAt: check.lastRun ?? undefined,\n thread: [],\n status: 'open',\n }));\n}\n\n/** A PR review that requested changes, keyed by PR number + head sha so a fresh\n * push that gets re-reviewed continues the same issue rather than a new one. */\nexport function collectPrReviewIssues(\n entities: { id: string; title: string; pr?: PrInfo }[],\n): Issue[] {\n return entities\n .filter((entity) => entity.pr?.reviewDecision === 'changes-requested')\n .map((entity) => {\n const pr = entity.pr as PrInfo;\n const sourceKey = `${pr.number}:${pr.headSha ?? 'unknown'}`;\n return {\n id: issueId('pr-review', sourceKey),\n sourceKind: 'pr-review',\n sourceKey,\n entityId: entity.id,\n entityTitle: entity.title,\n title: `${entity.title} — PR review requested changes`,\n reason: `PR #${pr.number} has unresolved change requests.`,\n thread: [],\n status: 'open',\n };\n });\n}\n\n/** A git rebase/sync failure on the currently checked-out branch, keyed by branch +\n * the ref it failed to reconcile against so a repeat failure of the same rebase\n * continues one issue. */\nexport function collectSyncIssues(\n failure: GitSyncFailure | undefined,\n branch: string,\n entity?: { id: string; title: string },\n): Issue[] {\n if (!failure) return [];\n const sourceKey = `${branch}:${failure.conflictRef ?? failure.stage}`;\n return [\n {\n id: issueId('sync', sourceKey),\n sourceKind: 'sync',\n sourceKey,\n entityId: entity?.id,\n entityTitle: entity?.title,\n title: `${branch} failed to sync`,\n reason: failure.message,\n output: failure.conflictedFiles?.length\n ? `Conflicted files: ${failure.conflictedFiles.join(', ')}`\n : undefined,\n thread: [],\n status: 'open',\n },\n ];\n}\n\n/** Reconciles freshly-collected issues (every one currently detected as failing, so\n * always 'open') against the existing set, keyed by `id`. A repeat failure of the\n * same thing replaces that issue's title/reason/output in place, preserving its\n * position and its thread. An existing issue absent from `fresh` has stopped\n * failing, so it closes — unless it was promoted to a fix entity that hasn't\n * shipped yet (`fixEntityStatus` reports its status), which keeps it open until\n * that entity reaches 'done'. Closure is derived here, never a mark-read button. */\nexport function reconcileIssues(\n existing: Issue[],\n fresh: Issue[],\n fixEntityStatus: (fixId: string) => EntityStatus | undefined = () => undefined,\n): Issue[] {\n const freshById = new Map(fresh.map((issue) => [issue.id, issue]));\n const merged = existing.map((issue): Issue => {\n if (issue.promotedFixId && fixEntityStatus(issue.promotedFixId) === 'done') {\n freshById.delete(issue.id);\n return { ...issue, status: 'closed' };\n }\n const next = freshById.get(issue.id);\n if (!next) return { ...issue, status: 'closed' };\n freshById.delete(issue.id);\n return { ...next, thread: issue.thread, promotedFixId: issue.promotedFixId };\n });\n return [...merged, ...freshById.values()];\n}\n\n/** A spawned fix/idea entity from Promote (IDEA-192 phase 6) stamps `issueSource`;\n * an open parent instead gets a `source: 'issue'` item appended to its inline Fixes list. */\ntype PromotionTarget = {\n id?: string;\n status?: EntityStatus;\n issueSource?: string;\n fixes?: PhaseItem[];\n};\n\n/** Matches every issue against the corpus for a prior Promote (IDEA-192 phase 6),\n * re-derived from disk on every read since issues carry no store of their own —\n * the promotion survives a reload because git holds it, not a stored issue. Once\n * matched, an issue whose target has shipped (`status: 'done'`) closes, even if\n * its original source is still detected as failing. */\nexport function applyPromotions(issues: Issue[], entities: PromotionTarget[]): Issue[] {\n return issues\n .map((issue): Issue => {\n const spawned = entities.find((e) => e.issueSource === issue.id);\n if (spawned?.id) return { ...issue, promotedFixId: spawned.id };\n const parent = issue.entityId ? entities.find((e) => e.id === issue.entityId) : undefined;\n const appended = parent?.fixes?.some((f) => f.source === 'issue' && f.text === issue.title);\n return appended && parent?.id ? { ...issue, promotedFixId: parent.id } : issue;\n })\n .filter((issue) => {\n const target = issue.promotedFixId && entities.find((e) => e.id === issue.promotedFixId);\n return !(target && target.status === 'done');\n });\n}\n\n/** \"Fix it here\" (IDEA-192) launches an `issue-fix` task carrying the issue's id;\n * this reconstructs that issue's thread from every tasks.log entry carrying it,\n * oldest first, so a repeat fix attempt reads as a continuation, not a fresh mystery. */\nexport function issueThreadFromTaskLog(issue: Issue, taskLog: TaskLogEntry[]): ThreadMessage[] {\n return taskLog\n .filter((entry) => entry.issueId === issue.id)\n .sort((a, b) => a.endedAt.localeCompare(b.endedAt))\n .map((entry) => ({\n kind: 'log',\n date: entry.endedAt.slice(0, 10),\n from: 'agent',\n text:\n entry.outcome === 'done'\n ? 'Ran a fix agent — finished.'\n : entry.outcome === 'error'\n ? `Ran a fix agent — failed: ${entry.reason ?? 'no reason recorded'}`\n : 'Ran a fix agent — superseded by a newer run.',\n }));\n}\n","export const formatIssueAge = (occurredAt: string | undefined): string => {\n if (!occurredAt) return '';\n const ageDays = Math.floor((Date.now() - Date.parse(occurredAt)) / 86_400_000);\n if (!Number.isFinite(ageDays)) return '';\n if (ageDays <= 0) return 'today';\n return ageDays === 1 ? '1 day ago' : `${ageDays} days ago`;\n};\n\n// Missing timestamps (a live PR review decision) sort last, not first.\nconst NO_TIMESTAMP = '9999-12-31T23:59:59.999Z';\nexport const sortIssuesByAge = <T extends { occurredAt?: string }>(issues: T[]): T[] =>\n [...issues].sort((a, b) =>\n (a.occurredAt ?? NO_TIMESTAMP).localeCompare(b.occurredAt ?? NO_TIMESTAMP),\n );\n","import { FeedbackThread, PlanIdStamp } from '@/app/features/plans/components';\nimport type { Issue } from '@/types/index';\nimport { Button, Spinner } from '@dendelion/paper-ui';\nimport { formatIssueAge } from './helpers';\n\ninterface IssueRowProps {\n issue: Issue;\n expanded: boolean;\n onToggle: () => void;\n onOpen?: () => void;\n fixing: boolean;\n fixDisabled: boolean;\n onFix: () => Promise<void>;\n promoteLabel: string;\n promoting: boolean;\n promoteDisabled: boolean;\n onPromote: () => Promise<void>;\n onOpenPromoted?: () => void;\n}\n\nexport const IssueRow = ({\n issue,\n expanded,\n onToggle,\n onOpen,\n fixing,\n fixDisabled,\n onFix,\n promoteLabel,\n promoting,\n promoteDisabled,\n onPromote,\n onOpenPromoted,\n}: IssueRowProps) => {\n const handleFix = async () => {\n try {\n await onFix();\n } catch (err) {\n alert((err as Error).message);\n }\n };\n\n const handlePromote = async () => {\n try {\n await onPromote();\n } catch (err) {\n alert((err as Error).message);\n }\n };\n\n return (\n <div className=\"border-b border-black/10 last:border-b-0\">\n <div className=\"flex items-center gap-3 py-2.5\">\n {onOpen && (\n <button\n type=\"button\"\n onClick={onOpen}\n aria-label={`Open ${issue.entityTitle}`}\n className=\"bg-transparent border-none p-0 cursor-pointer\"\n >\n <PlanIdStamp id={issue.entityId} />\n </button>\n )}\n <button\n type=\"button\"\n onClick={onToggle}\n aria-expanded={expanded}\n className=\"flex-1 min-w-0 truncate text-left bg-transparent border-none p-0 cursor-pointer\"\n >\n {issue.title}\n </button>\n <span className=\"opacity-50 text-xs whitespace-nowrap\">\n {formatIssueAge(issue.occurredAt)}\n </span>\n </div>\n {expanded && (\n <div className=\"flex flex-col gap-3 pb-4\">\n <p>{issue.reason}</p>\n {issue.output && (\n <pre className=\"max-h-48 overflow-auto whitespace-pre-wrap text-left font-mono text-2xs opacity-70\">\n {issue.output}\n </pre>\n )}\n {issue.thread.length > 0 && (\n <FeedbackThread messages={issue.thread} undo={null} undoing={false} onUndo={() => {}} />\n )}\n <div className=\"flex items-center gap-3\">\n {issue.promotedFixId ? (\n <Button size=\"small\" onClick={onOpenPromoted}>\n View {issue.promotedFixId}\n </Button>\n ) : (\n <>\n <Button size=\"small\" onClick={handleFix} disabled={fixDisabled}>\n {fixing ? 'Fixing…' : 'Fix it here'}\n </Button>\n {fixing && <Spinner size=\"small\" label=\"Agent fixing…\" />}\n <Button size=\"small\" onClick={handlePromote} disabled={promoteDisabled}>\n {promoting ? 'Promoting…' : promoteLabel}\n </Button>\n {promoting && <Spinner size=\"small\" label=\"Promoting…\" />}\n </>\n )}\n </div>\n </div>\n )}\n </div>\n );\n};\n","import { PageTitle } from '@/app/components/page-title';\nimport { useOpenEntity } from '@/app/hooks';\nimport { useDeskChecks } from '@/app/hooks/use-desk-checks';\nimport { promoteIssue } from '@/app/services/agent-api';\nimport { useAppStore } from '@/app/stores/app-store';\nimport {\n applyPromotions,\n collectAgentRunIssues,\n collectCheckIssues,\n collectPrReviewIssues,\n issueThreadFromTaskLog,\n} from '@/core/issues';\nimport { isClosedEntity } from '@/core/status';\nimport type { EntityStatus, Issue, PlanEntry } from '@/types/index';\nimport { useEffect, useMemo, useState } from 'react';\nimport { sortIssuesByAge } from './helpers';\nimport { IssueRow } from './issue-row';\n\nconst promoteLabel = (\n issue: Issue,\n entities: { id: string; status?: EntityStatus; archived?: boolean }[],\n): string => {\n const parent = issue.entityId ? entities.find((p) => p.id === issue.entityId) : undefined;\n if (!parent) return 'Promote to idea';\n return isClosedEntity(parent) ? 'Promote to fix' : `Add to ${parent.id}'s fixes`;\n};\n\nexport const IssuesPage = () => {\n const [expandedId, setExpandedId] = useState<string | null>(null);\n const [promotingId, setPromotingId] = useState<string | null>(null);\n const taskLog = useAppStore((s) => s.taskLog);\n const taskLogLoading = useAppStore((s) => s.taskLogLoading);\n const loadTaskLog = useAppStore((s) => s.loadTaskLog);\n const plans = useAppStore((s) => s.plans);\n const ideaEntries = useAppStore((s) => s.ideaEntries);\n const agentStatus = useAppStore((s) => s.agentStatus);\n const launchIssueFix = useAppStore((s) => s.launchIssueFix);\n const loadPlans = useAppStore((s) => s.loadPlans);\n const loadIdeas = useAppStore((s) => s.loadIdeas);\n const { checks } = useDeskChecks();\n const openEntity = useOpenEntity();\n\n useEffect(() => {\n loadTaskLog();\n }, [loadTaskLog]);\n\n const planEntities = useMemo(\n () => (plans?.entries ?? []).filter((p): p is PlanEntry & { id: string } => p.id != null),\n [plans],\n );\n\n const entities = useMemo(\n () =>\n [...planEntities, ...ideaEntries].filter(\n (entity): entity is typeof entity & { id: string } => entity.id != null,\n ),\n [planEntities, ideaEntries],\n );\n\n const issues = useMemo(() => {\n const collected = sortIssuesByAge([\n ...collectAgentRunIssues(taskLog),\n ...collectCheckIssues(checks),\n ...collectPrReviewIssues(entities),\n ]);\n return applyPromotions(collected, entities).map((issue) => ({\n ...issue,\n thread: issueThreadFromTaskLog(issue, taskLog),\n }));\n }, [taskLog, checks, entities]);\n\n const handlePromote = async (issue: Issue) => {\n setPromotingId(issue.id);\n try {\n await promoteIssue(issue);\n await Promise.all([loadPlans(), loadIdeas()]);\n } finally {\n setPromotingId(null);\n }\n };\n\n const fixingIssueId = agentStatus.find(\n (t) =>\n t.taskKind === 'issue-fix' &&\n t.status !== 'done' &&\n t.status !== 'error' &&\n t.status !== 'superseded',\n )?.issueId;\n\n return (\n <div>\n <PageTitle>Issues</PageTitle>\n <p className=\"opacity-50 mb-6\">\n Every failing agent run, red check, and PR review, oldest first.\n </p>\n {taskLogLoading && issues.length === 0 && <p className=\"opacity-50\">Loading…</p>}\n {!taskLogLoading && issues.length === 0 && (\n <p className=\"opacity-50\">Nothing broken right now.</p>\n )}\n {issues.length > 0 && (\n <div className=\"flex flex-col\">\n {issues.map((issue) => {\n const promotedTitle = issue.promotedFixId\n ? (entities.find((p) => p.id === issue.promotedFixId)?.title ?? issue.promotedFixId)\n : undefined;\n return (\n <IssueRow\n key={issue.id}\n issue={issue}\n expanded={expandedId === issue.id}\n onToggle={() => setExpandedId((cur) => (cur === issue.id ? null : issue.id))}\n onOpen={\n issue.entityId && issue.entityTitle\n ? () => openEntity(issue.entityId as string, issue.entityTitle as string)\n : undefined\n }\n fixing={fixingIssueId === issue.id}\n fixDisabled={fixingIssueId !== undefined}\n onFix={() => launchIssueFix(issue.id, issue.title, issue.reason, issue.output)}\n promoteLabel={promoteLabel(issue, entities)}\n promoting={promotingId === issue.id}\n promoteDisabled={promotingId !== null}\n onPromote={() => handlePromote(issue)}\n onOpenPromoted={\n issue.promotedFixId\n ? () => openEntity(issue.promotedFixId as string, promotedTitle as string)\n : undefined\n }\n />\n );\n })}\n </div>\n )}\n </div>\n );\n};\n"],"names":["issueId","sourceKind","sourceKey","collectAgentRunIssues","taskLog","entry","collectCheckIssues","checks","check","collectPrReviewIssues","entities","entity","_a","pr","applyPromotions","issues","issue","spawned","e","parent","f","target","issueThreadFromTaskLog","a","b","formatIssueAge","occurredAt","ageDays","NO_TIMESTAMP","sortIssuesByAge","IssueRow","expanded","onToggle","onOpen","fixing","fixDisabled","onFix","promoteLabel","promoting","promoteDisabled","onPromote","onOpenPromoted","handleFix","err","handlePromote","jsxs","jsx","PlanIdStamp","FeedbackThread","Button","Fragment","Spinner","p","isClosedEntity","IssuesPage","expandedId","setExpandedId","useState","promotingId","setPromotingId","useAppStore","s","taskLogLoading","loadTaskLog","plans","ideaEntries","agentStatus","launchIssueFix","loadPlans","loadIdeas","useDeskChecks","openEntity","useOpenEntity","useEffect","planEntities","useMemo","collected","promoteIssue","fixingIssueId","t","PageTitle","promotedTitle","cur"],"mappings":"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"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import{j as s,g as v,u as x,a2 as y,a3 as $,a4 as L,r as p,a5 as D,J as C,$ as k,a6 as E,a7 as A,a8 as T,w as I,P as R,a9 as _,aa as M,C as U,ab as G,ac as O,ad as z,p as B,ae as N,af as S,I as H}from"./main-s2b5XHSS.js";const W=/^@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@/;function K(e){if(!e)return[];const a=(e.endsWith(`
|
|
2
|
+
`)?e.slice(0,-1):e).split(`
|
|
3
|
+
`),t=[];let i=null,l=0,d=0;for(const r of a){const f=r.match(W);if(f){i={header:r,lines:[]},t.push(i),l=Number(f[1]),d=Number(f[2]);continue}i&&(r.startsWith("\\")||(r.startsWith("+")?(i.lines.push({type:"add",text:r.slice(1),oldLine:null,newLine:d}),d++):r.startsWith("-")?(i.lines.push({type:"remove",text:r.slice(1),oldLine:l,newLine:null}),l++):(i.lines.push({type:"context",text:r.startsWith(" ")?r.slice(1):r,oldLine:l,newLine:d}),l++,d++)))}return t}function q(e){return e?[{header:"",lines:(e.endsWith(`
|
|
4
|
+
`)?e.slice(0,-1):e).split(`
|
|
5
|
+
`).map((t,i)=>({type:"add",text:t,oldLine:null,newLine:i+1}))}]:[]}const F=({additions:e,deletions:a})=>s.jsxs("span",{className:"inline-flex shrink-0 gap-1.5 font-mono text-3xs",children:[s.jsxs("span",{className:"text-watercolor-green-dark",children:["+",e]}),s.jsxs("span",{className:"text-watercolor-rose-dark",children:["-",a]})]});function b(e){const a=e.lastIndexOf("/");return a===-1?{dir:"",base:e}:{dir:e.slice(0,a+1),base:e.slice(a+1)}}const J=({path:e,prefix:a,className:t=""})=>{const{dir:i,base:l}=b(e),d=`${a??""}${i}`;return s.jsxs("span",{className:`flex min-w-0 items-center font-mono ${t}`,children:[d&&s.jsx("span",{className:"min-w-0 overflow-hidden text-ellipsis whitespace-nowrap opacity-50",children:d}),s.jsx("span",{className:"shrink-0 whitespace-nowrap",children:l})]})},Q={success:"text-watercolor-green-dark",info:"text-watercolor-blue-dark",error:"text-watercolor-rose-dark",warning:"text-watercolor-amber-dark",neutral:"text-watercolor-slate-dark"},V={A:{letter:"A",label:"Added",variant:"success"},M:{letter:"M",label:"Modified",variant:"info"},D:{letter:"D",label:"Deleted",variant:"error"},R:{letter:"R",label:"Renamed",variant:"info"},C:{letter:"C",label:"Copied",variant:"info"},U:{letter:"U",label:"Conflicted",variant:"warning"}},X=e=>{if(e==="??")return{letter:"??",label:"Untracked",variant:"neutral"};const[a,t]=e,i=a!==" "&&a!=="?"?a:t;return V[i]??{letter:i??"?",label:i??"Unknown",variant:"neutral"}},P=({status:e,compact:a=!1})=>{const t=X(e);return a?s.jsx("span",{title:t.label,className:`w-4 shrink-0 text-center font-mono text-3xs font-semibold leading-none ${Q[t.variant]??""}`,children:t.letter}):s.jsx("span",{title:t.label,children:s.jsx(v,{size:"small",variant:t.variant,children:t.letter})})},Y=60,Z={add:"bg-watercolor-green/[18%] text-watercolor-green-dark",remove:"bg-watercolor-rose/[18%] text-watercolor-rose-dark",context:""},ee={add:"+",remove:"-",context:" "},se=({entry:e})=>s.jsxs("div",{className:"flex w-full min-w-0 items-center gap-3",children:[s.jsx(P,{status:e.status}),s.jsx(J,{path:e.path,prefix:e.renameSource?`${e.renameSource} → `:void 0,className:"min-w-0 flex-1 text-xs"}),s.jsxs("span",{className:"flex shrink-0 items-center gap-2",children:[e.staged&&s.jsx(v,{size:"small",children:"staged"}),!e.binary&&s.jsx(F,{additions:e.additions,deletions:e.deletions})]})]}),te=({entry:e})=>{if(e.binary)return s.jsx("p",{className:"m-0 opacity-60",children:"Binary file not shown."});if(e.contentKind==="too-large")return s.jsx("p",{className:"m-0 opacity-60",children:"File too large to preview."});const a=e.contentKind==="raw"?q(e.patch):K(e.patch);return a.length===0?e.renameSource?s.jsx("p",{className:"m-0 opacity-60",children:e.additions===0&&e.deletions===0?"Renamed, no content changes.":"Renamed with unrelated content — diff omitted."}):s.jsx("p",{className:"m-0 opacity-60",children:"No changes to preview."}):s.jsx("div",{className:"flex min-w-0 max-w-full flex-col gap-3",children:a.map((t,i)=>s.jsxs("div",{className:"min-w-0 max-w-full",children:[t.header&&s.jsx("div",{className:"mb-1 font-mono text-2xs opacity-50",children:t.header}),s.jsx("pre",{className:"m-0 min-w-0 max-w-full overflow-x-auto font-mono text-2xs leading-normal",children:t.lines.map((l,d)=>s.jsxs("span",{className:`flex ${Z[l.type]}`,children:[s.jsxs("span",{className:"inline-flex shrink-0 select-none gap-2 pr-2 opacity-40","aria-hidden":"true",children:[s.jsx("span",{className:"w-8 text-right",children:l.oldLine??""}),s.jsx("span",{className:"w-8 text-right",children:l.newLine??""})]}),s.jsxs("span",{children:[ee[l.type],l.text]})]},`${l.type}-${d}`))})]},`${t.header}-${i}`))})},ae=({entry:e})=>{const a=x(r=>r.manuallyExpandedDiffPaths.has(e.path)),t=x(r=>r.manuallyCollapsedDiffPaths.has(e.path)),i=x(r=>r.setDiffCollapsed),l=t||!a&&e.additions+e.deletions>Y,d=e.renameSource?`${e.renameSource} → ${e.path}`:e.path;return s.jsxs("div",{"data-diff-path":e.path,className:"min-w-0 max-w-full scroll-mt-4",children:[s.jsx("h2",{className:"sr-only",children:d}),s.jsx(y,{title:s.jsx(se,{entry:e}),expanded:!l,onToggle:()=>i(e.path,!l),children:s.jsx(te,{entry:e})})]})},ie="__git__",ne=()=>!0,le=e=>$(e,{formKey:ie,matchesSuggestionTask:ne}),oe=({state:e,filesEmpty:a})=>{const t=x(n=>n.status),{checks:i}=L(),{qualityStatus:l,testStatus:d,consistencyStatus:r}=p.useMemo(()=>D(t,i),[t,i]),f=[l==="fail"&&"Quality",d==="fail"&&"Tests",r==="fail"&&"Consistency"].filter(n=>n!==!1),u=s.jsx(C,{size:"small",disabled:a||!e.commitTitle.trim()||e.committing||e.commitInFlight,onClick:e.handleCommit,children:e.committing||e.commitInFlight?"Committing…":e.stagedCount>0?`Commit ${e.stagedCount} staged`:"Commit"});return f.length===0?u:s.jsxs("div",{className:"flex items-center gap-2",children:[s.jsx(k,{content:`${f.join(", ")} failing`,children:s.jsx(v,{size:"small",variant:"warning",children:"!"})}),u]})},he=()=>{const e=x(n=>n.diffFiles),a=x(n=>n.diffLoadFailed),t=x(n=>n.loadDiffFiles),i=x(n=>n.setActiveDiffPath),l=p.useRef(null),d=p.useMemo(()=>(e==null?void 0:e.map(n=>({path:n.path,staged:n.staged})))??[],[e]),r=le(d);p.useEffect(()=>{t()},[t]),p.useEffect(()=>{let n;const h=E(w=>{w.message==="changed"&&(n&&clearTimeout(n),n=setTimeout(t,250))}),g=()=>t();return window.addEventListener("focus",g),()=>{n&&clearTimeout(n),window.removeEventListener("focus",g),h()}},[t]),p.useEffect(()=>{const n=l.current;if(!n)return;const h=Array.from(n.querySelectorAll("[data-diff-path]"));if(h.length===0)return;const g=new Set,w=new IntersectionObserver(o=>{for(const m of o)m.isIntersecting?g.add(m.target):g.delete(m.target);const c=h.find(m=>g.has(m));c&&i(c.dataset.diffPath??null)},{rootMargin:"0px 0px -70% 0px"});for(const o of h)w.observe(o);return()=>w.disconnect()},[e,i]);const f="min-h-page",u=s.jsxs("div",{className:"mb-4 flex flex-wrap items-center justify-between gap-x-4 gap-y-2",children:[s.jsx(R,{className:"!mb-0",children:"Git"}),s.jsxs("div",{className:"flex items-center gap-2",children:[s.jsx(_,{}),s.jsx(M,{})]})]});return a?s.jsxs("div",{children:[u,s.jsxs("div",{className:`${f} flex flex-col items-start gap-3`,children:[s.jsx("p",{className:"opacity-50 m-0",children:"Couldn't load the working-tree diff."}),s.jsx(C,{variant:"secondary",size:"small",onClick:t,children:"Retry"})]})]}):e?s.jsxs("div",{children:[u,e.length===0?s.jsx("div",{className:f,children:s.jsx("p",{className:"opacity-50",children:"No changed files."})}):s.jsxs(s.Fragment,{children:[s.jsxs("div",{className:"sticky top-0 z-10 mb-4 flex items-center gap-2 py-2",children:[s.jsx("div",{className:"flex-1",children:s.jsx(T,{state:r,filesEmpty:!1})}),s.jsx(oe,{state:r,filesEmpty:!1})]}),s.jsx("div",{ref:l,className:`flex min-w-0 flex-col gap-6 ${f}`,children:e.map((n,h)=>s.jsxs(p.Fragment,{children:[h>0&&s.jsx(I,{}),s.jsx(ae,{entry:n})]},n.path))})]})]}):s.jsxs("div",{children:[u,s.jsx("div",{className:f,children:s.jsx(A,{label:"Loading the working-tree diff…"})})]})},ce="font-handwritten text-xs font-semibold leading-none opacity-[0.45]",re=(e,a)=>{a(e),requestAnimationFrame(()=>{var t;(t=document.querySelector(`[data-diff-path="${CSS.escape(e)}"]`))==null||t.scrollIntoView({behavior:"smooth",block:"start"})})},de=e=>{const[a,t]=e;return a!==" "&&a!=="?"&&t!==" "&&t!=="?"};function me(e){const a=new Map;for(const t of e){const{dir:i}=b(t.path),l=i||"./",d=a.get(l);d?d.push(t):a.set(l,[t])}return[...a].map(([t,i])=>({dir:t,entries:i}))}const xe=()=>{const e=x(o=>o.diffFiles),a=x(o=>o.activeDiffPath),t=x(o=>o.loadDiffFiles),i=x(o=>o.expandDiffPath),[l,d]=p.useState(new Set),[r,f]=p.useState(!1),{toast:u}=U(),n=p.useMemo(()=>me(e??[]),[e]),h=(e??[]).length>0&&(e??[]).every(o=>o.staged);if(!e||e.length===0)return null;const g=async()=>{const o=e.filter(c=>c.staged===h);f(!0);try{const m=(await Promise.allSettled(o.map(j=>h?N(j.path):S(j.path)))).filter(j=>j.status==="rejected").length;m>0&&u({title:h?"Unstage failed":"Stage failed",description:`${m} of ${o.length} file(s) could not be updated.`,variant:"error"})}finally{await t(),f(!1)}},w=async(o,c)=>{d(m=>new Set(m).add(o.path));try{await(c?S(o.path):N(o.path)),await t()}catch(m){u({title:c?"Stage failed":"Unstage failed",description:H(m.message),variant:"error"})}finally{d(m=>{const j=new Set(m);return j.delete(o.path),j})}};return s.jsxs("nav",{"aria-label":"Changed files",className:"flex flex-col",children:[s.jsxs("div",{className:`${ce} flex h-[32px] items-end justify-between pb-1`,children:[s.jsx("span",{children:"Changed files"}),s.jsx(k,{content:h?"Unstage every file":"Stage every file",children:s.jsx(G,{variant:"ghost",size:"tiny",className:"-mb-1",disabled:r,label:h?"Unstage all files":"Stage all files",onClick:g,icon:s.jsx(O,{})})})]}),s.jsx("ul",{className:"m-0 flex list-none flex-col p-0",children:n.map(o=>s.jsxs("li",{children:[s.jsx("div",{className:"flex h-[32px] items-end overflow-hidden text-ellipsis whitespace-nowrap pb-1 font-mono text-3xs leading-none opacity-50",title:o.dir,children:o.dir}),s.jsx("ul",{className:"m-0 flex list-none flex-col p-0",children:o.entries.map(c=>s.jsxs("li",{className:"pc-git-file-row flex h-[32px] min-w-0 items-end gap-1.5 pb-1",children:[s.jsx(z,{checked:c.staged,indeterminate:de(c.status),disabled:l.has(c.path),onChange:m=>w(c,m.target.checked),"aria-label":c.staged?`Unstage ${c.path}`:`Stage ${c.path}`}),s.jsx(P,{status:c.status,compact:!0}),s.jsx(B,{size:"small",active:c.path===a,onClick:()=>re(c.path,i),className:"min-w-0 flex-1 items-end py-0 text-3xs",action:!c.binary&&s.jsx(F,{additions:c.additions,deletions:c.deletions}),children:s.jsx("span",{className:"block min-w-0 overflow-hidden text-ellipsis whitespace-nowrap font-mono text-3xs",title:c.path,children:b(c.path).base})})]},c.path))})]},o.dir))})]})};export{xe as GitFileList,he as GitPage};
|
|
6
|
+
//# sourceMappingURL=index-COEivZHG.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index-COEivZHG.js","sources":["../../../src/app/utils/parse-diff.ts","../../../src/app/features/git/count-badge.tsx","../../../src/app/utils/path-display.ts","../../../src/app/features/git/file-path.tsx","../../../src/app/features/git/git-status-marker.tsx","../../../src/app/features/git/file-diff-section.tsx","../../../src/app/features/git/git-commit-controls.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 oldLine: number | null;\n newLine: number | null;\n}\n\nexport interface DiffHunk {\n header: string;\n lines: DiffLine[];\n}\n\nconst HUNK_HEADER = /^@@ -(\\d+)(?:,\\d+)? \\+(\\d+)(?:,\\d+)? @@/;\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 let oldLine = 0;\n let newLine = 0;\n for (const line of lines) {\n const header = line.match(HUNK_HEADER);\n if (header) {\n current = { header: line, lines: [] };\n hunks.push(current);\n oldLine = Number(header[1]);\n newLine = Number(header[2]);\n continue;\n }\n if (!current) continue; // preamble: diff --git / index / --- / +++\n if (line.startsWith('\\\\')) continue; // \"\\"\n if (line.startsWith('+')) {\n current.lines.push({ type: 'add', text: line.slice(1), oldLine: null, newLine });\n newLine++;\n } else if (line.startsWith('-')) {\n current.lines.push({ type: 'remove', text: line.slice(1), oldLine, newLine: null });\n oldLine++;\n } else {\n current.lines.push({\n type: 'context',\n text: line.startsWith(' ') ? line.slice(1) : line,\n oldLine,\n newLine,\n });\n oldLine++;\n newLine++;\n }\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 [\n {\n header: '',\n lines: lines.map((text, i) => ({ type: 'add', text, oldLine: null, newLine: i + 1 })),\n },\n ];\n}\n","interface CountBadgeProps {\n additions: number;\n deletions: number;\n}\n\nexport const CountBadge = ({ additions, deletions }: CountBadgeProps) => (\n <span className=\"inline-flex shrink-0 gap-1.5 font-mono text-3xs\">\n <span className=\"text-watercolor-green-dark\">+{additions}</span>\n <span className=\"text-watercolor-rose-dark\">-{deletions}</span>\n </span>\n);\n","export interface SplitPath {\n dir: string;\n base: string;\n}\n\nexport function splitPathForDisplay(path: string): SplitPath {\n const slash = path.lastIndexOf('/');\n return slash === -1\n ? { dir: '', base: path }\n : { dir: path.slice(0, slash + 1), base: path.slice(slash + 1) };\n}\n","import { splitPathForDisplay } from '@/app/utils/path-display';\n\ninterface FilePathProps {\n path: string;\n prefix?: string;\n className?: string;\n}\n\nexport const FilePath = ({ path, prefix, className = '' }: FilePathProps) => {\n const { dir, base } = splitPathForDisplay(path);\n const dimmed = `${prefix ?? ''}${dir}`;\n return (\n <span className={`flex min-w-0 items-center font-mono ${className}`}>\n {dimmed && (\n <span className=\"min-w-0 overflow-hidden text-ellipsis whitespace-nowrap opacity-50\">\n {dimmed}\n </span>\n )}\n <span className=\"shrink-0 whitespace-nowrap\">{base}</span>\n </span>\n );\n};\n","import { Stamp, type StampVariant } from '@dendelion/paper-ui';\n\ninterface StatusInfo {\n letter: string;\n label: string;\n variant: StampVariant;\n}\n\n// Compact rows can't spare the ~39px a Stamp's blob costs for one letter; the colour\n// alone carries the same meaning in a fraction of the width.\nconst COMPACT_COLOR: Record<StampVariant, string> = {\n success: 'text-watercolor-green-dark',\n info: 'text-watercolor-blue-dark',\n error: 'text-watercolor-rose-dark',\n warning: 'text-watercolor-amber-dark',\n neutral: 'text-watercolor-slate-dark',\n};\n\nconst STATUS_INFO: Record<string, StatusInfo> = {\n A: { letter: 'A', label: 'Added', variant: 'success' },\n M: { letter: 'M', label: 'Modified', variant: 'info' },\n D: { letter: 'D', label: 'Deleted', variant: 'error' },\n R: { letter: 'R', label: 'Renamed', variant: 'info' },\n C: { letter: 'C', label: 'Copied', variant: 'info' },\n U: { letter: 'U', label: 'Conflicted', variant: 'warning' },\n};\n\n// Git's porcelain code is two characters: index status then worktree status.\n// The index one wins when both are set — it's the state that would actually commit.\nexport const gitStatusInfo = (status: string): StatusInfo => {\n if (status === '??') return { letter: '??', label: 'Untracked', variant: 'neutral' };\n const [x, y] = status;\n const code = x !== ' ' && x !== '?' ? x : y;\n return STATUS_INFO[code] ?? { letter: code ?? '?', label: code ?? 'Unknown', variant: 'neutral' };\n};\n\ninterface GitStatusMarkerProps {\n status: string;\n compact?: boolean;\n}\n\nexport const GitStatusMarker = ({ status, compact = false }: GitStatusMarkerProps) => {\n const info = gitStatusInfo(status);\n if (compact) {\n return (\n <span\n title={info.label}\n className={`w-4 shrink-0 text-center font-mono text-3xs font-semibold leading-none ${COMPACT_COLOR[info.variant] ?? ''}`}\n >\n {info.letter}\n </span>\n );\n }\n return (\n <span title={info.label}>\n <Stamp size=\"small\" variant={info.variant}>\n {info.letter}\n </Stamp>\n </span>\n );\n};\n","import { useAppStore } from '@/app/stores/app-store';\nimport { type DiffLineType, parsePatch, rawContentHunks } from '@/app/utils/parse-diff';\nimport type { FileDiffEntry } from '@/types/index';\nimport { Accordion, Stamp } from '@dendelion/paper-ui';\nimport { CountBadge } from './count-badge';\nimport { FilePath } from './file-path';\nimport { GitStatusMarker } from './git-status-marker';\n\nconst COLLAPSE_LINE_THRESHOLD = 60;\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 FileHeaderProps {\n entry: FileDiffEntry;\n}\n\nconst FileHeader = ({ entry }: FileHeaderProps) => (\n <div className=\"flex w-full min-w-0 items-center gap-3\">\n <GitStatusMarker status={entry.status} />\n <FilePath\n path={entry.path}\n prefix={entry.renameSource ? `${entry.renameSource} → ` : undefined}\n className=\"min-w-0 flex-1 text-xs\"\n />\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 leading-normal\">\n {hunk.lines.map((line, j) => (\n <span key={`${line.type}-${j}`} className={`flex ${LINE_CLASS[line.type]}`}>\n <span\n className=\"inline-flex shrink-0 select-none gap-2 pr-2 opacity-40\"\n aria-hidden=\"true\"\n >\n <span className=\"w-8 text-right\">{line.oldLine ?? ''}</span>\n <span className=\"w-8 text-right\">{line.newLine ?? ''}</span>\n </span>\n <span>\n {LINE_PREFIX[line.type]}\n {line.text}\n </span>\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 const manuallyExpanded = useAppStore((s) => s.manuallyExpandedDiffPaths.has(entry.path));\n const manuallyCollapsed = useAppStore((s) => s.manuallyCollapsedDiffPaths.has(entry.path));\n const setDiffCollapsed = useAppStore((s) => s.setDiffCollapsed);\n const collapsed =\n manuallyCollapsed ||\n (!manuallyExpanded && entry.additions + entry.deletions > COLLAPSE_LINE_THRESHOLD);\n\n const headingText = entry.renameSource ? `${entry.renameSource} → ${entry.path}` : entry.path;\n\n return (\n <div data-diff-path={entry.path} className=\"min-w-0 max-w-full scroll-mt-4\">\n <h2 className=\"sr-only\">{headingText}</h2>\n <Accordion\n title={<FileHeader entry={entry} />}\n expanded={!collapsed}\n onToggle={() => setDiffCollapsed(entry.path, !collapsed)}\n >\n <DiffBody entry={entry} />\n </Accordion>\n </div>\n );\n};\n","import { type CommitFormFile, useCommitForm } from '@/app/hooks/use-commit-form';\nimport { useDeskChecks } from '@/app/hooks/use-desk-checks';\nimport { useAppStore } from '@/app/stores/app-store';\nimport { deriveCheckStatuses } from '@/app/utils/check-status';\nimport { Button, Stamp, Tooltip } from '@dendelion/paper-ui';\nimport { useMemo } from 'react';\n\n// Stable across renders, and the same value used before this idea's staging/body\n// work — keeps drafts saved under the git page's key from being orphaned.\nconst GIT_PAGE_FORM_KEY = '__git__';\n\nconst matchAnySuggestionTask = () => true;\n\n// No plan on the git page, so this is bare commit mechanics — no phase recording, no Fix.\nexport const useGitCommitForm = (files: CommitFormFile[]) =>\n useCommitForm(files, {\n formKey: GIT_PAGE_FORM_KEY,\n matchesSuggestionTask: matchAnySuggestionTask,\n });\n\nexport type GitCommitFormState = ReturnType<typeof useGitCommitForm>;\n\n// Never Fix (plan-scoped, and the git page never has one) — a failing check is a warning instead.\nexport const GitCommitButton = ({\n state,\n filesEmpty,\n}: {\n state: GitCommitFormState;\n filesEmpty: boolean;\n}) => {\n const status = useAppStore((s) => s.status);\n const { checks: deskChecks } = useDeskChecks();\n const { qualityStatus, testStatus, consistencyStatus } = useMemo(\n () => deriveCheckStatuses(status, deskChecks),\n [status, deskChecks],\n );\n const failingChecks = [\n qualityStatus === 'fail' && 'Quality',\n testStatus === 'fail' && 'Tests',\n consistencyStatus === 'fail' && 'Consistency',\n ].filter((label): label is Exclude<typeof label, false> => label !== false);\n\n const commitButton = (\n <Button\n size=\"small\"\n disabled={filesEmpty || !state.commitTitle.trim() || state.committing || state.commitInFlight}\n onClick={state.handleCommit}\n >\n {state.committing || state.commitInFlight\n ? 'Committing…'\n : state.stagedCount > 0\n ? `Commit ${state.stagedCount} staged`\n : 'Commit'}\n </Button>\n );\n\n if (failingChecks.length === 0) return commitButton;\n\n return (\n <div className=\"flex items-center gap-2\">\n <Tooltip content={`${failingChecks.join(', ')} failing`}>\n <Stamp size=\"small\" variant=\"warning\">\n !\n </Stamp>\n </Tooltip>\n {commitButton}\n </div>\n );\n};\n","import { CommitMessageFields, GitStashSurface, GitSyncActions } from '@/app/components';\nimport { PageTitle } from '@/app/components/page-title';\nimport { FileDiffSection } from '@/app/features/git/file-diff-section';\nimport { GitCommitButton, useGitCommitForm } from '@/app/features/git/git-commit-controls';\nimport { subscribeToActivityStream } from '@/app/services/activity-stream';\nimport { useAppStore } from '@/app/stores/app-store';\nimport { Button, Divider, Spinner } from '@dendelion/paper-ui';\nimport { Fragment, useEffect, useMemo, useRef } from 'react';\n\nexport const GitPage = () => {\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(\n () => files?.map((entry) => ({ path: entry.path, staged: entry.staged })) ?? [],\n [files],\n );\n const commitForm = useGitCommitForm(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 contentClass = 'min-h-page';\n\n // Title and repo actions share a row: stacked, they cost three bands of vertical\n // space before any diff shows.\n const header = (\n <div className=\"mb-4 flex flex-wrap items-center justify-between gap-x-4 gap-y-2\">\n <PageTitle className=\"!mb-0\">Git</PageTitle>\n <div className=\"flex items-center gap-2\">\n <GitSyncActions />\n <GitStashSurface />\n </div>\n </div>\n );\n\n if (loadFailed) {\n return (\n <div>\n {header}\n <div className={`${contentClass} flex flex-col items-start gap-3`}>\n <p className=\"opacity-50 m-0\">Couldn't load the working-tree diff.</p>\n <Button variant=\"secondary\" size=\"small\" onClick={loadDiffFiles}>\n Retry\n </Button>\n </div>\n </div>\n );\n }\n\n if (!files) {\n return (\n <div>\n {header}\n <div className={contentClass}>\n <Spinner label=\"Loading the working-tree diff…\" />\n </div>\n </div>\n );\n }\n\n return (\n <div>\n {header}\n {files.length === 0 ? (\n <div className={contentClass}>\n <p className=\"opacity-50\">No changed files.</p>\n </div>\n ) : (\n <>\n <div className=\"sticky top-0 z-10 mb-4 flex items-center gap-2 py-2\">\n <div className=\"flex-1\">\n <CommitMessageFields state={commitForm} filesEmpty={false} />\n </div>\n <GitCommitButton 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 { CheckAllIcon } from '@/app/components/icons';\nimport { CountBadge } from '@/app/features/git/count-badge';\nimport { GitStatusMarker } from '@/app/features/git/git-status-marker';\nimport { stagePath, unstagePath } from '@/app/services/git-api';\nimport { useAppStore } from '@/app/stores/app-store';\nimport { oneLineErrorSummary } from '@/app/utils/error-summary';\nimport { splitPathForDisplay } from '@/app/utils/path-display';\nimport type { FileDiffEntry } from '@/types/index';\nimport { Checkbox, IconButton, ListItem, Tooltip, useToast } from '@dendelion/paper-ui';\nimport { useMemo, useState } from 'react';\n\n// Matches SidebarSection (Docs/Settings sidebars) — no caps.\nconst sectionLabelClass = 'font-handwritten text-xs font-semibold leading-none opacity-[0.45]';\n\nconst scrollToFile = (path: string, expandDiffPath: (path: string) => void) => {\n expandDiffPath(path);\n requestAnimationFrame(() => {\n document\n .querySelector(`[data-diff-path=\"${CSS.escape(path)}\"]`)\n ?.scrollIntoView({ behavior: 'smooth', block: 'start' });\n });\n};\n\n// Both status characters set (and neither '?', which pairs up for untracked) means\n// part of the file is staged and part isn't — git can't be checked or unchecked.\nconst isPartiallyStaged = (status: string) => {\n const [x, y] = status;\n return x !== ' ' && x !== '?' && y !== ' ' && y !== '?';\n};\n\n// One row per folder carries the path once, so the file rows below it only spend\n// their width on the basename — the part that actually distinguishes them.\nfunction groupByFolder(files: FileDiffEntry[]): { dir: string; entries: FileDiffEntry[] }[] {\n const groups = new Map<string, FileDiffEntry[]>();\n for (const entry of files) {\n const { dir } = splitPathForDisplay(entry.path);\n const key = dir || './';\n const bucket = groups.get(key);\n if (bucket) bucket.push(entry);\n else groups.set(key, [entry]);\n }\n return [...groups].map(([dir, entries]) => ({ dir, entries }));\n}\n\nexport const GitFileList = () => {\n const files = useAppStore((s) => s.diffFiles);\n const activePath = useAppStore((s) => s.activeDiffPath);\n const loadDiffFiles = useAppStore((s) => s.loadDiffFiles);\n const expandDiffPath = useAppStore((s) => s.expandDiffPath);\n const [pending, setPending] = useState<Set<string>>(new Set());\n const [bulkPending, setBulkPending] = useState(false);\n const { toast } = useToast();\n const groups = useMemo(() => groupByFolder(files ?? []), [files]);\n const allStaged = (files ?? []).length > 0 && (files ?? []).every((f) => f.staged);\n\n if (!files || files.length === 0) return null;\n\n // No bulk endpoint: fans out over per-path calls and reloads once at the end, so a\n // partial failure still reports and whatever landed shows up in that single reload.\n const toggleAll = async () => {\n const targets = files.filter((f) => f.staged === allStaged);\n setBulkPending(true);\n try {\n const results = await Promise.allSettled(\n targets.map((f) => (allStaged ? unstagePath(f.path) : stagePath(f.path))),\n );\n const failed = results.filter((r) => r.status === 'rejected').length;\n if (failed > 0) {\n toast({\n title: allStaged ? 'Unstage failed' : 'Stage failed',\n description: `${failed} of ${targets.length} file(s) could not be updated.`,\n variant: 'error',\n });\n }\n } finally {\n await loadDiffFiles();\n setBulkPending(false);\n }\n };\n\n const toggleStaged = async (entry: FileDiffEntry, next: boolean) => {\n setPending((prev) => new Set(prev).add(entry.path));\n try {\n await (next ? stagePath(entry.path) : unstagePath(entry.path));\n await loadDiffFiles();\n } catch (err) {\n toast({\n title: next ? 'Stage failed' : 'Unstage failed',\n description: oneLineErrorSummary((err as Error).message),\n variant: 'error',\n });\n } finally {\n setPending((prev) => {\n const updated = new Set(prev);\n updated.delete(entry.path);\n return updated;\n });\n }\n };\n\n return (\n <nav aria-label=\"Changed files\" className=\"flex flex-col\">\n <div className={`${sectionLabelClass} flex h-[32px] items-end justify-between pb-1`}>\n <span>Changed files</span>\n <Tooltip content={allStaged ? 'Unstage every file' : 'Stage every file'}>\n <IconButton\n variant=\"ghost\"\n size=\"tiny\"\n className=\"-mb-1\"\n disabled={bulkPending}\n label={allStaged ? 'Unstage all files' : 'Stage all files'}\n onClick={toggleAll}\n icon={<CheckAllIcon />}\n />\n </Tooltip>\n </div>\n {/* Every row is exactly one 32px cell so the list tracks the ruled background. */}\n <ul className=\"m-0 flex list-none flex-col p-0\">\n {groups.map((group) => (\n <li key={group.dir}>\n <div\n className=\"flex h-[32px] items-end overflow-hidden text-ellipsis whitespace-nowrap pb-1 font-mono text-3xs leading-none opacity-50\"\n title={group.dir}\n >\n {group.dir}\n </div>\n <ul className=\"m-0 flex list-none flex-col p-0\">\n {group.entries.map((entry) => (\n <li\n key={entry.path}\n className=\"pc-git-file-row flex h-[32px] min-w-0 items-end gap-1.5 pb-1\"\n >\n <Checkbox\n checked={entry.staged}\n indeterminate={isPartiallyStaged(entry.status)}\n disabled={pending.has(entry.path)}\n onChange={(e) => toggleStaged(entry, e.target.checked)}\n aria-label={entry.staged ? `Unstage ${entry.path}` : `Stage ${entry.path}`}\n />\n <GitStatusMarker status={entry.status} compact />\n <ListItem\n size=\"small\"\n active={entry.path === activePath}\n onClick={() => scrollToFile(entry.path, expandDiffPath)}\n className=\"min-w-0 flex-1 items-end py-0 text-3xs\"\n action={\n !entry.binary && (\n <CountBadge additions={entry.additions} deletions={entry.deletions} />\n )\n }\n >\n <span\n className=\"block min-w-0 overflow-hidden text-ellipsis whitespace-nowrap font-mono text-3xs\"\n title={entry.path}\n >\n {splitPathForDisplay(entry.path).base}\n </span>\n </ListItem>\n </li>\n ))}\n </ul>\n </li>\n ))}\n </ul>\n </nav>\n );\n};\n"],"names":["HUNK_HEADER","parsePatch","patch","lines","hunks","current","oldLine","newLine","line","header","rawContentHunks","content","text","CountBadge","additions","deletions","jsxs","splitPathForDisplay","path","slash","FilePath","prefix","className","dir","base","dimmed","jsx","COMPACT_COLOR","STATUS_INFO","gitStatusInfo","status","x","y","code","GitStatusMarker","compact","info","Stamp","COLLAPSE_LINE_THRESHOLD","LINE_CLASS","LINE_PREFIX","FileHeader","entry","DiffBody","hunk","j","FileDiffSection","manuallyExpanded","useAppStore","s","manuallyCollapsed","setDiffCollapsed","collapsed","headingText","Accordion","GIT_PAGE_FORM_KEY","matchAnySuggestionTask","useGitCommitForm","files","useCommitForm","GitCommitButton","state","filesEmpty","deskChecks","useDeskChecks","qualityStatus","testStatus","consistencyStatus","useMemo","deriveCheckStatuses","failingChecks","label","commitButton","Button","Tooltip","GitPage","loadFailed","loadDiffFiles","setActiveDiffPath","sectionsRef","useRef","commitFiles","commitForm","useEffect","timer","unsubscribe","subscribeToActivityStream","payload","onFocus","root","sections","visible","observer","entries","topmost","el","contentClass","PageTitle","GitSyncActions","GitStashSurface","Fragment","CommitMessageFields","idx","Divider","Spinner","sectionLabelClass","scrollToFile","expandDiffPath","_a","isPartiallyStaged","groupByFolder","groups","key","bucket","GitFileList","activePath","pending","setPending","useState","bulkPending","setBulkPending","toast","useToast","allStaged","f","toggleAll","targets","failed","unstagePath","stagePath","r","toggleStaged","next","prev","err","oneLineErrorSummary","updated","IconButton","CheckAllIcon","group","Checkbox","e","ListItem"],"mappings":"6NAcA,MAAMA,EAAc,0CAIb,SAASC,EAAWC,EAA2B,CACpD,GAAI,CAACA,EAAO,MAAO,CAAA,EACnB,MAAMC,GAASD,EAAM,SAAS;AAAA,CAAI,EAAIA,EAAM,MAAM,EAAG,EAAE,EAAIA,GAAO,MAAM;AAAA,CAAI,EAEtEE,EAAoB,CAAA,EAC1B,IAAIC,EAA2B,KAC3BC,EAAU,EACVC,EAAU,EACd,UAAWC,KAAQL,EAAO,CACxB,MAAMM,EAASD,EAAK,MAAMR,CAAW,EACrC,GAAIS,EAAQ,CACVJ,EAAU,CAAE,OAAQG,EAAM,MAAO,CAAA,CAAC,EAClCJ,EAAM,KAAKC,CAAO,EAClBC,EAAU,OAAOG,EAAO,CAAC,CAAC,EAC1BF,EAAU,OAAOE,EAAO,CAAC,CAAC,EAC1B,QACF,CACKJ,IACDG,EAAK,WAAW,IAAI,IACpBA,EAAK,WAAW,GAAG,GACrBH,EAAQ,MAAM,KAAK,CAAE,KAAM,MAAO,KAAMG,EAAK,MAAM,CAAC,EAAG,QAAS,KAAM,QAAAD,EAAS,EAC/EA,KACSC,EAAK,WAAW,GAAG,GAC5BH,EAAQ,MAAM,KAAK,CAAE,KAAM,SAAU,KAAMG,EAAK,MAAM,CAAC,EAAG,QAAAF,EAAS,QAAS,KAAM,EAClFA,MAEAD,EAAQ,MAAM,KAAK,CACjB,KAAM,UACN,KAAMG,EAAK,WAAW,GAAG,EAAIA,EAAK,MAAM,CAAC,EAAIA,EAC7C,QAAAF,EACA,QAAAC,CAAA,CACD,EACDD,IACAC,MAEJ,CACA,OAAOH,CACT,CAIO,SAASM,EAAgBC,EAA6B,CAC3D,OAAKA,EAEE,CACL,CACE,OAAQ,GACR,OAJWA,EAAQ,SAAS;AAAA,CAAI,EAAIA,EAAQ,MAAM,EAAG,EAAE,EAAIA,GAAS,MAAM;AAAA,CAAI,EAIjE,IAAI,CAACC,EAAM,KAAO,CAAE,KAAM,MAAO,KAAAA,EAAM,QAAS,KAAM,QAAS,EAAI,GAAI,CAAA,CACtF,EANmB,CAAA,CAQvB,CC/DO,MAAMC,EAAa,CAAC,CAAE,UAAAC,EAAW,UAAAC,KACtCC,OAAC,OAAA,CAAK,UAAU,kDACd,SAAA,CAAAA,EAAAA,KAAC,OAAA,CAAK,UAAU,6BAA6B,SAAA,CAAA,IAAEF,CAAA,EAAU,EACzDE,EAAAA,KAAC,OAAA,CAAK,UAAU,4BAA4B,SAAA,CAAA,IAAED,CAAA,CAAA,CAAU,CAAA,EAC1D,ECJK,SAASE,EAAoBC,EAAyB,CAC3D,MAAMC,EAAQD,EAAK,YAAY,GAAG,EAClC,OAAOC,IAAU,GACb,CAAE,IAAK,GAAI,KAAMD,GACjB,CAAE,IAAKA,EAAK,MAAM,EAAGC,EAAQ,CAAC,EAAG,KAAMD,EAAK,MAAMC,EAAQ,CAAC,CAAA,CACjE,CCFO,MAAMC,EAAW,CAAC,CAAE,KAAAF,EAAM,OAAAG,EAAQ,UAAAC,EAAY,MAAwB,CAC3E,KAAM,CAAE,IAAAC,EAAK,KAAAC,GAASP,EAAoBC,CAAI,EACxCO,EAAS,GAAGJ,GAAU,EAAE,GAAGE,CAAG,GACpC,OACEP,EAAAA,KAAC,OAAA,CAAK,UAAW,uCAAuCM,CAAS,GAC9D,SAAA,CAAAG,GACCC,EAAAA,IAAC,OAAA,CAAK,UAAU,qEACb,SAAAD,EACH,EAEFC,EAAAA,IAAC,OAAA,CAAK,UAAU,6BAA8B,SAAAF,CAAA,CAAK,CAAA,EACrD,CAEJ,ECXMG,EAA8C,CAClD,QAAS,6BACT,KAAM,4BACN,MAAO,4BACP,QAAS,6BACT,QAAS,4BACX,EAEMC,EAA0C,CAC9C,EAAG,CAAE,OAAQ,IAAK,MAAO,QAAS,QAAS,SAAA,EAC3C,EAAG,CAAE,OAAQ,IAAK,MAAO,WAAY,QAAS,MAAA,EAC9C,EAAG,CAAE,OAAQ,IAAK,MAAO,UAAW,QAAS,OAAA,EAC7C,EAAG,CAAE,OAAQ,IAAK,MAAO,UAAW,QAAS,MAAA,EAC7C,EAAG,CAAE,OAAQ,IAAK,MAAO,SAAU,QAAS,MAAA,EAC5C,EAAG,CAAE,OAAQ,IAAK,MAAO,aAAc,QAAS,SAAA,CAClD,EAIaC,EAAiBC,GAA+B,CAC3D,GAAIA,IAAW,KAAM,MAAO,CAAE,OAAQ,KAAM,MAAO,YAAa,QAAS,SAAA,EACzE,KAAM,CAACC,EAAGC,CAAC,EAAIF,EACTG,EAAOF,IAAM,KAAOA,IAAM,IAAMA,EAAIC,EAC1C,OAAOJ,EAAYK,CAAI,GAAK,CAAE,OAAQA,GAAQ,IAAK,MAAOA,GAAQ,UAAW,QAAS,SAAA,CACxF,EAOaC,EAAkB,CAAC,CAAE,OAAAJ,EAAQ,QAAAK,EAAU,MAAkC,CACpF,MAAMC,EAAOP,EAAcC,CAAM,EACjC,OAAIK,EAEAT,EAAAA,IAAC,OAAA,CACC,MAAOU,EAAK,MACZ,UAAW,0EAA0ET,EAAcS,EAAK,OAAO,GAAK,EAAE,GAErH,SAAAA,EAAK,MAAA,CAAA,EAKVV,EAAAA,IAAC,OAAA,CAAK,MAAOU,EAAK,MAChB,SAAAV,EAAAA,IAACW,EAAA,CAAM,KAAK,QAAQ,QAASD,EAAK,QAC/B,SAAAA,EAAK,OACR,EACF,CAEJ,ECpDME,EAA0B,GAE1BC,EAA2C,CAC/C,IAAK,uDACL,OAAQ,qDACR,QAAS,EACX,EAEMC,GAA4C,CAAE,IAAK,IAAK,OAAQ,IAAK,QAAS,GAAA,EAM9EC,GAAa,CAAC,CAAE,MAAAC,KACpB1B,EAAAA,KAAC,MAAA,CAAI,UAAU,yCACb,SAAA,CAAAU,EAAAA,IAACQ,EAAA,CAAgB,OAAQQ,EAAM,MAAA,CAAQ,EACvChB,EAAAA,IAACN,EAAA,CACC,KAAMsB,EAAM,KACZ,OAAQA,EAAM,aAAe,GAAGA,EAAM,YAAY,MAAQ,OAC1D,UAAU,wBAAA,CAAA,EAEZ1B,EAAAA,KAAC,OAAA,CAAK,UAAU,mCACb,SAAA,CAAA0B,EAAM,QAAUhB,EAAAA,IAACW,EAAA,CAAM,KAAK,QAAQ,SAAA,SAAM,EAC1C,CAACK,EAAM,QAAUhB,EAAAA,IAACb,EAAA,CAAW,UAAW6B,EAAM,UAAW,UAAWA,EAAM,SAAA,CAAW,CAAA,CAAA,CACxF,CAAA,EACF,EAOIC,GAAW,CAAC,CAAE,MAAAD,KAA2B,CAC7C,GAAIA,EAAM,OACR,OAAOhB,EAAAA,IAAC,IAAA,CAAE,UAAU,iBAAiB,SAAA,yBAAsB,EAE7D,GAAIgB,EAAM,cAAgB,YACxB,OAAOhB,EAAAA,IAAC,IAAA,CAAE,UAAU,iBAAiB,SAAA,6BAA0B,EAEjE,MAAMtB,EACJsC,EAAM,cAAgB,MAAQhC,EAAgBgC,EAAM,KAAK,EAAIzC,EAAWyC,EAAM,KAAK,EACrF,OAAItC,EAAM,SAAW,EACfsC,EAAM,aAENhB,EAAAA,IAAC,IAAA,CAAE,UAAU,iBACV,SAAAgB,EAAM,YAAc,GAAKA,EAAM,YAAc,EAC1C,+BACA,gDAAA,CACN,EAGGhB,EAAAA,IAAC,IAAA,CAAE,UAAU,iBAAiB,SAAA,yBAAsB,EAG3DA,EAAAA,IAAC,MAAA,CAAI,UAAU,yCACZ,SAAAtB,EAAM,IAAI,CAACwC,EAAM,IAChB5B,OAAC,MAAA,CAAgC,UAAU,qBACxC,SAAA,CAAA4B,EAAK,QAAUlB,EAAAA,IAAC,MAAA,CAAI,UAAU,qCAAsC,WAAK,OAAO,QAEhF,MAAA,CAAI,UAAU,2EACZ,SAAAkB,EAAK,MAAM,IAAI,CAACpC,EAAMqC,IACrB7B,EAAAA,KAAC,QAA+B,UAAW,QAAQuB,EAAW/B,EAAK,IAAI,CAAC,GACtE,SAAA,CAAAQ,EAAAA,KAAC,OAAA,CACC,UAAU,yDACV,cAAY,OAEZ,SAAA,CAAAU,MAAC,OAAA,CAAK,UAAU,iBAAkB,SAAAlB,EAAK,SAAW,GAAG,QACpD,OAAA,CAAK,UAAU,iBAAkB,SAAAA,EAAK,SAAW,EAAA,CAAG,CAAA,CAAA,CAAA,SAEtD,OAAA,CACE,SAAA,CAAAgC,GAAYhC,EAAK,IAAI,EACrBA,EAAK,IAAA,CAAA,CACR,CAAA,CAAA,EAXS,GAAGA,EAAK,IAAI,IAAIqC,CAAC,EAY5B,CACD,CAAA,CACH,CAAA,GAnBQ,GAAGD,EAAK,MAAM,IAAI,CAAC,EAoB7B,CACD,EACH,CAEJ,EAMaE,GAAkB,CAAC,CAAE,MAAAJ,KAAkC,CAClE,MAAMK,EAAmBC,EAAaC,GAAMA,EAAE,0BAA0B,IAAIP,EAAM,IAAI,CAAC,EACjFQ,EAAoBF,EAAaC,GAAMA,EAAE,2BAA2B,IAAIP,EAAM,IAAI,CAAC,EACnFS,EAAmBH,EAAaC,GAAMA,EAAE,gBAAgB,EACxDG,EACJF,GACC,CAACH,GAAoBL,EAAM,UAAYA,EAAM,UAAYJ,EAEtDe,EAAcX,EAAM,aAAe,GAAGA,EAAM,YAAY,MAAMA,EAAM,IAAI,GAAKA,EAAM,KAEzF,cACG,MAAA,CAAI,iBAAgBA,EAAM,KAAM,UAAU,iCACzC,SAAA,CAAAhB,EAAAA,IAAC,KAAA,CAAG,UAAU,UAAW,SAAA2B,EAAY,EACrC3B,EAAAA,IAAC4B,EAAA,CACC,MAAO5B,EAAAA,IAACe,GAAA,CAAW,MAAAC,CAAA,CAAc,EACjC,SAAU,CAACU,EACX,SAAU,IAAMD,EAAiBT,EAAM,KAAM,CAACU,CAAS,EAEvD,SAAA1B,EAAAA,IAACiB,IAAS,MAAAD,CAAA,CAAc,CAAA,CAAA,CAC1B,EACF,CAEJ,EC5GMa,GAAoB,UAEpBC,GAAyB,IAAM,GAGxBC,GAAoBC,GAC/BC,EAAcD,EAAO,CACnB,QAASH,GACT,sBAAuBC,EACzB,CAAC,EAKUI,GAAkB,CAAC,CAC9B,MAAAC,EACA,WAAAC,CACF,IAGM,CACJ,MAAMhC,EAASkB,EAAaC,GAAMA,EAAE,MAAM,EACpC,CAAE,OAAQc,CAAA,EAAeC,EAAA,EACzB,CAAE,cAAAC,EAAe,WAAAC,EAAY,kBAAAC,CAAA,EAAsBC,EAAAA,QACvD,IAAMC,EAAoBvC,EAAQiC,CAAU,EAC5C,CAACjC,EAAQiC,CAAU,CAAA,EAEfO,EAAgB,CACpBL,IAAkB,QAAU,UAC5BC,IAAe,QAAU,QACzBC,IAAsB,QAAU,aAAA,EAChC,OAAQI,GAAiDA,IAAU,EAAK,EAEpEC,EACJ9C,EAAAA,IAAC+C,EAAA,CACC,KAAK,QACL,SAAUX,GAAc,CAACD,EAAM,YAAY,KAAA,GAAUA,EAAM,YAAcA,EAAM,eAC/E,QAASA,EAAM,aAEd,SAAAA,EAAM,YAAcA,EAAM,eACvB,cACAA,EAAM,YAAc,EAClB,UAAUA,EAAM,WAAW,UAC3B,QAAA,CAAA,EAIV,OAAIS,EAAc,SAAW,EAAUE,EAGrCxD,EAAAA,KAAC,MAAA,CAAI,UAAU,0BACb,SAAA,CAAAU,MAACgD,EAAA,CAAQ,QAAS,GAAGJ,EAAc,KAAK,IAAI,CAAC,WAC3C,SAAA5C,EAAAA,IAACW,GAAM,KAAK,QAAQ,QAAQ,UAAU,aAEtC,EACF,EACCmC,CAAA,EACH,CAEJ,EC3DaG,GAAU,IAAM,CAC3B,MAAMjB,EAAQV,EAAaC,GAAMA,EAAE,SAAS,EACtC2B,EAAa5B,EAAaC,GAAMA,EAAE,cAAc,EAChD4B,EAAgB7B,EAAaC,GAAMA,EAAE,aAAa,EAClD6B,EAAoB9B,EAAaC,GAAMA,EAAE,iBAAiB,EAC1D8B,EAAcC,EAAAA,OAAuB,IAAI,EACzCC,EAAcb,EAAAA,QAClB,KAAMV,GAAA,YAAAA,EAAO,IAAKhB,IAAW,CAAE,KAAMA,EAAM,KAAM,OAAQA,EAAM,MAAA,MAAc,CAAA,EAC7E,CAACgB,CAAK,CAAA,EAEFwB,EAAazB,GAAiBwB,CAAW,EAE/CE,EAAAA,UAAU,IAAM,CACdN,EAAA,CACF,EAAG,CAACA,CAAa,CAAC,EAElBM,EAAAA,UAAU,IAAM,CACd,IAAIC,EACJ,MAAMC,EAAcC,EAA2BC,GAAY,CACrDA,EAAQ,UAAY,YACpBH,gBAAoBA,CAAK,EAC7BA,EAAQ,WAAWP,EAAe,GAAG,EACvC,CAAC,EACKW,EAAU,IAAMX,EAAA,EACtB,cAAO,iBAAiB,QAASW,CAAO,EACjC,IAAM,CACPJ,gBAAoBA,CAAK,EAC7B,OAAO,oBAAoB,QAASI,CAAO,EAC3CH,EAAA,CACF,CACF,EAAG,CAACR,CAAa,CAAC,EAGlBM,EAAAA,UAAU,IAAM,CACd,MAAMM,EAAOV,EAAY,QACzB,GAAI,CAACU,EAAM,OACX,MAAMC,EAAW,MAAM,KAAKD,EAAK,iBAA8B,kBAAkB,CAAC,EAClF,GAAIC,EAAS,SAAW,EAAG,OAC3B,MAAMC,MAAc,IACdC,EAAW,IAAI,qBAClBC,GAAY,CACX,UAAWnD,KAASmD,EACdnD,EAAM,eAAgBiD,EAAQ,IAAIjD,EAAM,MAAqB,EAC5DiD,EAAQ,OAAOjD,EAAM,MAAqB,EAEjD,MAAMoD,EAAUJ,EAAS,KAAMK,GAAOJ,EAAQ,IAAII,CAAE,CAAC,EACjDD,GAAShB,EAAkBgB,EAAQ,QAAQ,UAAY,IAAI,CACjE,EACA,CAAE,WAAY,kBAAA,CAAmB,EAEnC,UAAWC,KAAML,EAAUE,EAAS,QAAQG,CAAE,EAC9C,MAAO,IAAMH,EAAS,WAAA,CACxB,EAAG,CAAClC,EAAOoB,CAAiB,CAAC,EAE7B,MAAMkB,EAAe,aAIfvF,EACJO,EAAAA,KAAC,MAAA,CAAI,UAAU,mEACb,SAAA,CAAAU,EAAAA,IAACuE,EAAA,CAAU,UAAU,QAAQ,SAAA,MAAG,EAChCjF,EAAAA,KAAC,MAAA,CAAI,UAAU,0BACb,SAAA,CAAAU,EAAAA,IAACwE,EAAA,EAAe,QACfC,EAAA,CAAA,CAAgB,CAAA,CAAA,CACnB,CAAA,EACF,EAGF,OAAIvB,SAEC,MAAA,CACE,SAAA,CAAAnE,EACDO,EAAAA,KAAC,MAAA,CAAI,UAAW,GAAGgF,CAAY,mCAC7B,SAAA,CAAAtE,EAAAA,IAAC,IAAA,CAAE,UAAU,iBAAiB,SAAA,uCAAoC,EAClEA,EAAAA,IAAC+C,GAAO,QAAQ,YAAY,KAAK,QAAQ,QAASI,EAAe,SAAA,OAAA,CAEjE,CAAA,CAAA,CACF,CAAA,EACF,EAICnB,SAYF,MAAA,CACE,SAAA,CAAAjD,EACAiD,EAAM,SAAW,EAChBhC,MAAC,OAAI,UAAWsE,EACd,SAAAtE,MAAC,IAAA,CAAE,UAAU,aAAa,SAAA,mBAAA,CAAiB,CAAA,CAC7C,EAEAV,EAAAA,KAAAoF,WAAA,CACE,SAAA,CAAApF,EAAAA,KAAC,MAAA,CAAI,UAAU,sDACb,SAAA,CAAAU,EAAAA,IAAC,MAAA,CAAI,UAAU,SACb,SAAAA,EAAAA,IAAC2E,GAAoB,MAAOnB,EAAY,WAAY,EAAA,CAAO,CAAA,CAC7D,EACAxD,EAAAA,IAACkC,GAAA,CAAgB,MAAOsB,EAAY,WAAY,EAAA,CAAO,CAAA,EACzD,EACAxD,EAAAA,IAAC,MAAA,CAAI,IAAKqD,EAAa,UAAW,+BAA+BiB,CAAY,GAC1E,SAAAtC,EAAM,IAAI,CAAChB,EAAO4D,IACjBtF,OAACoF,EAAAA,SAAA,CACE,SAAA,CAAAE,EAAM,SAAMC,EAAA,CAAA,CAAQ,EACrB7E,MAACoB,IAAgB,MAAAJ,CAAA,CAAc,CAAA,GAFlBA,EAAM,IAGrB,CACD,CAAA,CACH,CAAA,CAAA,CACF,CAAA,EAEJ,SAlCG,MAAA,CACE,SAAA,CAAAjC,EACDiB,EAAAA,IAAC,OAAI,UAAWsE,EACd,eAACQ,EAAA,CAAQ,MAAM,iCAAiC,CAAA,CAClD,CAAA,EACF,CA+BN,ECrHMC,GAAoB,qEAEpBC,GAAe,CAACxF,EAAcyF,IAA2C,CAC7EA,EAAezF,CAAI,EACnB,sBAAsB,IAAM,QAC1B0F,EAAA,SACG,cAAc,oBAAoB,IAAI,OAAO1F,CAAI,CAAC,IAAI,IADzD,MAAA0F,EAEI,eAAe,CAAE,SAAU,SAAU,MAAO,SAClD,CAAC,CACH,EAIMC,GAAqB/E,GAAmB,CAC5C,KAAM,CAACC,EAAGC,CAAC,EAAIF,EACf,OAAOC,IAAM,KAAOA,IAAM,KAAOC,IAAM,KAAOA,IAAM,GACtD,EAIA,SAAS8E,GAAcpD,EAAqE,CAC1F,MAAMqD,MAAa,IACnB,UAAWrE,KAASgB,EAAO,CACzB,KAAM,CAAE,IAAAnC,CAAA,EAAQN,EAAoByB,EAAM,IAAI,EACxCsE,EAAMzF,GAAO,KACb0F,EAASF,EAAO,IAAIC,CAAG,EACzBC,EAAQA,EAAO,KAAKvE,CAAK,EACxBqE,EAAO,IAAIC,EAAK,CAACtE,CAAK,CAAC,CAC9B,CACA,MAAO,CAAC,GAAGqE,CAAM,EAAE,IAAI,CAAC,CAACxF,EAAKsE,CAAO,KAAO,CAAE,IAAAtE,EAAK,QAAAsE,GAAU,CAC/D,CAEO,MAAMqB,GAAc,IAAM,CAC/B,MAAMxD,EAAQV,EAAaC,GAAMA,EAAE,SAAS,EACtCkE,EAAanE,EAAaC,GAAMA,EAAE,cAAc,EAChD4B,EAAgB7B,EAAaC,GAAMA,EAAE,aAAa,EAClD0D,EAAiB3D,EAAaC,GAAMA,EAAE,cAAc,EACpD,CAACmE,EAASC,CAAU,EAAIC,EAAAA,SAAsB,IAAI,GAAK,EACvD,CAACC,EAAaC,CAAc,EAAIF,EAAAA,SAAS,EAAK,EAC9C,CAAE,MAAAG,CAAA,EAAUC,EAAA,EACZX,EAAS3C,EAAAA,QAAQ,IAAM0C,GAAcpD,GAAS,EAAE,EAAG,CAACA,CAAK,CAAC,EAC1DiE,GAAajE,GAAS,CAAA,GAAI,OAAS,IAAMA,GAAS,CAAA,GAAI,MAAOkE,GAAMA,EAAE,MAAM,EAEjF,GAAI,CAAClE,GAASA,EAAM,SAAW,EAAG,OAAO,KAIzC,MAAMmE,EAAY,SAAY,CAC5B,MAAMC,EAAUpE,EAAM,OAAQkE,GAAMA,EAAE,SAAWD,CAAS,EAC1DH,EAAe,EAAI,EACnB,GAAI,CAIF,MAAMO,GAHU,MAAM,QAAQ,WAC5BD,EAAQ,IAAKF,GAAOD,EAAYK,EAAYJ,EAAE,IAAI,EAAIK,EAAUL,EAAE,IAAI,CAAE,CAAA,GAEnD,OAAQM,GAAMA,EAAE,SAAW,UAAU,EAAE,OAC1DH,EAAS,GACXN,EAAM,CACJ,MAAOE,EAAY,iBAAmB,eACtC,YAAa,GAAGI,CAAM,OAAOD,EAAQ,MAAM,iCAC3C,QAAS,OAAA,CACV,CAEL,QAAA,CACE,MAAMjD,EAAA,EACN2C,EAAe,EAAK,CACtB,CACF,EAEMW,EAAe,MAAOzF,EAAsB0F,IAAkB,CAClEf,EAAYgB,GAAS,IAAI,IAAIA,CAAI,EAAE,IAAI3F,EAAM,IAAI,CAAC,EAClD,GAAI,CACF,MAAO0F,EAAOH,EAAUvF,EAAM,IAAI,EAAIsF,EAAYtF,EAAM,IAAI,GAC5D,MAAMmC,EAAA,CACR,OAASyD,EAAK,CACZb,EAAM,CACJ,MAAOW,EAAO,eAAiB,iBAC/B,YAAaG,EAAqBD,EAAc,OAAO,EACvD,QAAS,OAAA,CACV,CACH,QAAA,CACEjB,EAAYgB,GAAS,CACnB,MAAMG,EAAU,IAAI,IAAIH,CAAI,EAC5B,OAAAG,EAAQ,OAAO9F,EAAM,IAAI,EAClB8F,CACT,CAAC,CACH,CACF,EAEA,OACExH,EAAAA,KAAC,MAAA,CAAI,aAAW,gBAAgB,UAAU,gBACxC,SAAA,CAAAA,EAAAA,KAAC,MAAA,CAAI,UAAW,GAAGyF,EAAiB,gDAClC,SAAA,CAAA/E,EAAAA,IAAC,QAAK,SAAA,eAAA,CAAa,EACnBA,EAAAA,IAACgD,EAAA,CAAQ,QAASiD,EAAY,qBAAuB,mBACnD,SAAAjG,EAAAA,IAAC+G,EAAA,CACC,QAAQ,QACR,KAAK,OACL,UAAU,QACV,SAAUlB,EACV,MAAOI,EAAY,oBAAsB,kBACzC,QAASE,EACT,WAAOa,EAAA,CAAA,CAAa,CAAA,CAAA,CACtB,CACF,CAAA,EACF,EAEAhH,EAAAA,IAAC,MAAG,UAAU,kCACX,WAAO,IAAKiH,GACX3H,EAAAA,KAAC,KAAA,CACC,SAAA,CAAAU,EAAAA,IAAC,MAAA,CACC,UAAU,0HACV,MAAOiH,EAAM,IAEZ,SAAAA,EAAM,GAAA,CAAA,EAETjH,EAAAA,IAAC,MAAG,UAAU,kCACX,WAAM,QAAQ,IAAKgB,GAClB1B,EAAAA,KAAC,KAAA,CAEC,UAAU,+DAEV,SAAA,CAAAU,EAAAA,IAACkH,EAAA,CACC,QAASlG,EAAM,OACf,cAAemE,GAAkBnE,EAAM,MAAM,EAC7C,SAAU0E,EAAQ,IAAI1E,EAAM,IAAI,EAChC,SAAWmG,GAAMV,EAAazF,EAAOmG,EAAE,OAAO,OAAO,EACrD,aAAYnG,EAAM,OAAS,WAAWA,EAAM,IAAI,GAAK,SAASA,EAAM,IAAI,EAAA,CAAA,QAEzER,EAAA,CAAgB,OAAQQ,EAAM,OAAQ,QAAO,GAAC,EAC/ChB,EAAAA,IAACoH,EAAA,CACC,KAAK,QACL,OAAQpG,EAAM,OAASyE,EACvB,QAAS,IAAMT,GAAahE,EAAM,KAAMiE,CAAc,EACtD,UAAU,yCACV,OACE,CAACjE,EAAM,QACLhB,EAAAA,IAACb,EAAA,CAAW,UAAW6B,EAAM,UAAW,UAAWA,EAAM,SAAA,CAAW,EAIxE,SAAAhB,EAAAA,IAAC,OAAA,CACC,UAAU,mFACV,MAAOgB,EAAM,KAEZ,SAAAzB,EAAoByB,EAAM,IAAI,EAAE,IAAA,CAAA,CACnC,CAAA,CACF,CAAA,EA5BKA,EAAM,IAAA,CA8Bd,CAAA,CACH,CAAA,GAzCOiG,EAAM,GA0Cf,CACD,CAAA,CACH,CAAA,EACF,CAEJ"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{c as m,r as o,j as e,P as u,X as i,q as c,Y as h,Q as j,Z as r,_ as p,g as d,a0 as g,a1 as v,R as N}from"./main-s2b5XHSS.js";const f=async()=>{const s=await fetch(m("/api/stats"));if(!s.ok)throw new Error(`Failed to fetch stats: ${s.status}`);return s.json()},y=["open",...h],T={...j,open:"Open"},b=s=>`${Math.round(s*100)}%`,l=({title:s,children:a})=>e.jsx("div",{className:"flex-[1_1_260px] max-w-[360px] max-[480px]:flex-[1_1_100%] max-[480px]:max-w-none",children:e.jsx(N,{size:"small",texture:"kraft",className:"h-full",children:e.jsxs("div",{className:"flex flex-col gap-3",children:[e.jsx("span",{className:"font-handwritten text-xs font-semibold opacity-[0.55]",children:s}),a]})})}),n=({label:s,value:a})=>e.jsxs("div",{className:"flex items-center justify-between gap-2",children:[e.jsx("span",{className:"opacity-70",children:s}),e.jsx("span",{className:"font-semibold",children:a})]}),S=({comments:s})=>e.jsxs(l,{title:"Comment ratio",children:[e.jsx(i,{value:s.ratio*100,color:c.accentSlate}),e.jsx(n,{label:"Ratio",value:b(s.ratio)}),e.jsx(n,{label:"Comment lines",value:s.commentLines}),e.jsx(n,{label:"Source lines",value:s.sourceLines})]}),C=({testCoveragePct:s})=>e.jsx(l,{title:"Test coverage",children:s===null?e.jsx("p",{className:"opacity-50 m-0",children:"Run `pnpm test` to generate a coverage report."}):e.jsxs(e.Fragment,{children:[e.jsx(i,{value:s,color:c.accentGreen}),e.jsx(n,{label:"Line coverage",value:`${Math.round(s)}%`})]})}),L=({sourceLines:s,testLines:a})=>e.jsxs(l,{title:"Codebase size",children:[e.jsx(n,{label:"Source lines",value:s}),e.jsx(n,{label:"Test lines",value:a})]}),w=({entitiesByStatus:s})=>e.jsxs(l,{title:"Entities by status",children:[y.filter(a=>s[a]).map(a=>e.jsx(n,{label:T[a],value:s[a]},a)),Object.keys(s).length===0&&e.jsx("p",{className:"opacity-50 m-0",children:"No entities yet."})]}),A=({openQuestions:s,decisions:a})=>e.jsxs(l,{title:"Notes",children:[e.jsx(n,{label:"Open questions",value:s}),e.jsx(n,{label:"Decisions",value:a})]}),M=({tasksPerWeek:s})=>{const a=Math.max(1,...s.map(t=>t.count));return e.jsxs(l,{title:"Tasks per week",children:[s.length===0&&e.jsx("p",{className:"opacity-50 m-0",children:"No tasks run yet."}),s.map(t=>e.jsxs("div",{className:"flex flex-col gap-1",children:[e.jsx(n,{label:t.week,value:t.count}),e.jsx(i,{value:t.count,max:a,color:c.accentAmber,height:4})]},t.week))]})},E=({usagePerWeek:s})=>{const a=Math.max(1,...s.map(t=>t.agentMinutes));return e.jsxs(l,{title:"Usage per week",children:[s.length===0&&e.jsx("p",{className:"opacity-50 m-0",children:"No usage recorded yet."}),s.map(t=>e.jsxs("div",{className:"flex flex-col gap-1",children:[e.jsx(n,{label:t.week,value:`${t.agentMinutes}m`}),e.jsx(i,{value:t.agentMinutes,max:a,color:c.accentAmber,height:4}),e.jsxs("span",{className:"text-2xs opacity-50",children:[r(t.inputTokens)," in · ",r(t.outputTokens)," out"]})]},t.week))]})},k=({medianPhaseDurationMs:s})=>e.jsx(l,{title:"Median phase duration",children:s===null?e.jsx("p",{className:"opacity-50 m-0",children:"No phase runs recorded yet."}):e.jsx(n,{label:"Median",value:p(s)})}),_=({mostExpensiveIdeas:s})=>e.jsxs(l,{title:"Most expensive ideas",children:[s.length===0&&e.jsx("p",{className:"opacity-50 m-0",children:"No usage recorded yet."}),s.map(a=>e.jsxs("div",{className:"flex flex-col gap-0.5",children:[e.jsx(n,{label:a.planId,value:`${r(a.inputTokens)} in · ${r(a.outputTokens)} out`}),e.jsx("span",{className:"text-2xs opacity-50 truncate",children:a.planTitle})]},a.planId))]}),D={allowed:"success",warning:"warning",rejected:"error"},P=({capacity:s})=>{const[a,t]=o.useState(()=>Date.now());return o.useEffect(()=>{const x=setInterval(()=>t(Date.now()),3e4);return()=>clearInterval(x)},[]),e.jsx(l,{title:"Claude capacity",children:s===null?e.jsx("p",{className:"opacity-50 m-0",children:"No agent run has reported capacity yet."}):e.jsxs(e.Fragment,{children:[e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx(d,{size:"small",variant:D[g(s.snapshot.status)],children:s.snapshot.status}),s.snapshot.overage&&e.jsx(d,{size:"small",variant:"warning",children:"overage"})]}),s.snapshot.rateLimitType&&e.jsx(n,{label:"Window",value:s.snapshot.rateLimitType}),s.snapshot.resetsAt!==void 0&&e.jsx(n,{label:"Resets",value:new Date(v(s.snapshot.resetsAt)).toLocaleTimeString()}),e.jsxs("span",{className:"text-2xs opacity-50",children:["as of last agent run,"," ",p(Math.max(0,a-Date.parse(s.capturedAt)))," ago"]})]})})},I=()=>{const[s,a]=o.useState(null),[t,x]=o.useState(!1);return o.useEffect(()=>{f().then(a).catch(()=>x(!0))},[]),e.jsxs("div",{children:[e.jsx(u,{children:"Stats"}),e.jsx("p",{className:"opacity-50 mb-6",children:"A read-only look at project health — every number here is informational, none of them gate anything."}),t&&e.jsx("p",{className:"opacity-50",children:"Couldn't load stats."}),!t&&!s&&e.jsx("p",{className:"opacity-50",children:"Loading…"}),s&&e.jsxs(e.Fragment,{children:[e.jsxs("div",{className:"flex flex-wrap items-start gap-2.5",children:[e.jsx(S,{comments:s.comments}),e.jsx(C,{testCoveragePct:s.testCoveragePct}),e.jsx(L,{sourceLines:s.comments.sourceLines,testLines:s.testLines}),e.jsx(w,{entitiesByStatus:s.entitiesByStatus}),e.jsx(A,{openQuestions:s.openQuestions,decisions:s.decisions}),e.jsx(M,{tasksPerWeek:s.tasksPerWeek}),e.jsx(E,{usagePerWeek:s.usagePerWeek}),e.jsx(k,{medianPhaseDurationMs:s.medianPhaseDurationMs}),e.jsx(_,{mostExpensiveIdeas:s.mostExpensiveIdeas}),e.jsx(P,{capacity:s.capacity})]}),e.jsxs("p",{className:"opacity-[0.4] text-2xs mt-6",children:["Generated ",new Date(s.generatedAt).toLocaleString()]})]})]})};export{I as StatsPage};
|
|
2
|
+
//# sourceMappingURL=index-COQOoPiS.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index-B6251tL6.js","sources":["../../../src/app/services/content/stats-api.ts","../../../src/app/features/stats/stats-page.tsx"],"sourcesContent":["import type { ProjectStats } from '@/types/index';\nimport { apiUrl } from '../api-base';\n\nexport const fetchStats = async () => {\n const res = await fetch(apiUrl('/api/stats'));\n if (!res.ok) throw new Error(`Failed to fetch stats: ${res.status}`);\n return res.json() as Promise<ProjectStats>;\n};\n","import { PageTitle } from '@/app/components/page-title';\nimport { STATUS_LABEL } from '@/app/features/plans/constants';\nimport { fetchStats } from '@/app/services/content';\nimport { color } from '@/app/styles/tokens';\nimport { formatDuration, formatTokens } from '@/core/phase-run';\nimport { capacityLevel, resetsAtMs } from '@/core/rate-limit';\nimport { type EntityStatus, PLAN_STATUSES, type ProjectStats } from '@/types/index';\nimport { Card, Progress, Stamp } from '@dendelion/paper-ui';\nimport { useEffect, useState } from 'react';\n\nconst ENTITY_STATUS_ORDER: EntityStatus[] = ['open', ...PLAN_STATUSES];\n\nconst ENTITY_STATUS_LABEL: Record<EntityStatus, string> = { ...STATUS_LABEL, open: 'Open' };\n\nconst pct = (value: number) => `${Math.round(value * 100)}%`;\n\nconst StatCard = ({ title, children }: { title: string; children: React.ReactNode }) => (\n <div className=\"flex-[1_1_260px] max-w-[360px] max-[480px]:flex-[1_1_100%] max-[480px]:max-w-none\">\n <Card size=\"small\" texture=\"kraft\" className=\"h-full\">\n <div className=\"flex flex-col gap-3\">\n <span className=\"font-handwritten text-xs font-semibold opacity-[0.55]\">{title}</span>\n {children}\n </div>\n </Card>\n </div>\n);\n\nconst StatRow = ({ label, value }: { label: string; value: React.ReactNode }) => (\n <div className=\"flex items-center justify-between gap-2\">\n <span className=\"opacity-70\">{label}</span>\n <span className=\"font-semibold\">{value}</span>\n </div>\n);\n\nconst CommentRatioCard = ({ comments }: { comments: ProjectStats['comments'] }) => (\n <StatCard title=\"Comment ratio\">\n <Progress value={comments.ratio * 100} color={color.accentSlate} />\n <StatRow label=\"Ratio\" value={pct(comments.ratio)} />\n <StatRow label=\"Comment lines\" value={comments.commentLines} />\n <StatRow label=\"Source lines\" value={comments.sourceLines} />\n </StatCard>\n);\n\nconst TestCoverageCard = ({ testCoveragePct }: { testCoveragePct: number | null }) => (\n <StatCard title=\"Test coverage\">\n {testCoveragePct === null ? (\n <p className=\"opacity-50 m-0\">Run `pnpm test` to generate a coverage report.</p>\n ) : (\n <>\n <Progress value={testCoveragePct} color={color.accentGreen} />\n <StatRow label=\"Line coverage\" value={`${Math.round(testCoveragePct)}%`} />\n </>\n )}\n </StatCard>\n);\n\nconst CodebaseSizeCard = ({\n sourceLines,\n testLines,\n}: { sourceLines: number; testLines: number }) => (\n <StatCard title=\"Codebase size\">\n <StatRow label=\"Source lines\" value={sourceLines} />\n <StatRow label=\"Test lines\" value={testLines} />\n </StatCard>\n);\n\nconst EntitiesByStatusCard = ({\n entitiesByStatus,\n}: { entitiesByStatus: ProjectStats['entitiesByStatus'] }) => (\n <StatCard title=\"Entities by status\">\n {ENTITY_STATUS_ORDER.filter((status) => entitiesByStatus[status]).map((status) => (\n <StatRow key={status} label={ENTITY_STATUS_LABEL[status]} value={entitiesByStatus[status]} />\n ))}\n {Object.keys(entitiesByStatus).length === 0 && (\n <p className=\"opacity-50 m-0\">No entities yet.</p>\n )}\n </StatCard>\n);\n\nconst NotesCard = ({ openQuestions, decisions }: { openQuestions: number; decisions: number }) => (\n <StatCard title=\"Notes\">\n <StatRow label=\"Open questions\" value={openQuestions} />\n <StatRow label=\"Decisions\" value={decisions} />\n </StatCard>\n);\n\nconst TasksPerWeekCard = ({ tasksPerWeek }: { tasksPerWeek: ProjectStats['tasksPerWeek'] }) => {\n const max = Math.max(1, ...tasksPerWeek.map((w) => w.count));\n return (\n <StatCard title=\"Tasks per week\">\n {tasksPerWeek.length === 0 && <p className=\"opacity-50 m-0\">No tasks run yet.</p>}\n {tasksPerWeek.map((week) => (\n <div key={week.week} className=\"flex flex-col gap-1\">\n <StatRow label={week.week} value={week.count} />\n <Progress value={week.count} max={max} color={color.accentAmber} height={4} />\n </div>\n ))}\n </StatCard>\n );\n};\n\nconst UsagePerWeekCard = ({ usagePerWeek }: { usagePerWeek: ProjectStats['usagePerWeek'] }) => {\n const max = Math.max(1, ...usagePerWeek.map((w) => w.agentMinutes));\n return (\n <StatCard title=\"Usage per week\">\n {usagePerWeek.length === 0 && <p className=\"opacity-50 m-0\">No usage recorded yet.</p>}\n {usagePerWeek.map((week) => (\n <div key={week.week} className=\"flex flex-col gap-1\">\n <StatRow label={week.week} value={`${week.agentMinutes}m`} />\n <Progress value={week.agentMinutes} max={max} color={color.accentAmber} height={4} />\n <span className=\"text-2xs opacity-50\">\n {formatTokens(week.inputTokens)} in · {formatTokens(week.outputTokens)} out\n </span>\n </div>\n ))}\n </StatCard>\n );\n};\n\nconst MedianPhaseDurationCard = ({\n medianPhaseDurationMs,\n}: { medianPhaseDurationMs: number | null }) => (\n <StatCard title=\"Median phase duration\">\n {medianPhaseDurationMs === null ? (\n <p className=\"opacity-50 m-0\">No phase runs recorded yet.</p>\n ) : (\n <StatRow label=\"Median\" value={formatDuration(medianPhaseDurationMs)} />\n )}\n </StatCard>\n);\n\nconst MostExpensiveIdeasCard = ({\n mostExpensiveIdeas,\n}: { mostExpensiveIdeas: ProjectStats['mostExpensiveIdeas'] }) => (\n <StatCard title=\"Most expensive ideas\">\n {mostExpensiveIdeas.length === 0 && <p className=\"opacity-50 m-0\">No usage recorded yet.</p>}\n {mostExpensiveIdeas.map((idea) => (\n <div key={idea.planId} className=\"flex flex-col gap-0.5\">\n <StatRow\n label={idea.planId}\n value={`${formatTokens(idea.inputTokens)} in · ${formatTokens(idea.outputTokens)} out`}\n />\n <span className=\"text-2xs opacity-50 truncate\">{idea.planTitle}</span>\n </div>\n ))}\n </StatCard>\n);\n\nconst CAPACITY_STAMP = {\n allowed: 'success',\n warning: 'warning',\n rejected: 'error',\n} as const;\n\nconst ClaudeCapacityCard = ({ capacity }: { capacity: ProjectStats['capacity'] }) => {\n const [now, setNow] = useState(() => Date.now());\n useEffect(() => {\n const id = setInterval(() => setNow(Date.now()), 30_000);\n return () => clearInterval(id);\n }, []);\n return (\n <StatCard title=\"Claude capacity\">\n {capacity === null ? (\n <p className=\"opacity-50 m-0\">No agent run has reported capacity yet.</p>\n ) : (\n <>\n <div className=\"flex items-center gap-2\">\n <Stamp size=\"small\" variant={CAPACITY_STAMP[capacityLevel(capacity.snapshot.status)]}>\n {capacity.snapshot.status}\n </Stamp>\n {capacity.snapshot.overage && (\n <Stamp size=\"small\" variant=\"warning\">\n overage\n </Stamp>\n )}\n </div>\n {capacity.snapshot.rateLimitType && (\n <StatRow label=\"Window\" value={capacity.snapshot.rateLimitType} />\n )}\n {capacity.snapshot.resetsAt !== undefined && (\n <StatRow\n label=\"Resets\"\n value={new Date(resetsAtMs(capacity.snapshot.resetsAt)).toLocaleTimeString()}\n />\n )}\n <span className=\"text-2xs opacity-50\">\n as of last agent run,{' '}\n {formatDuration(Math.max(0, now - Date.parse(capacity.capturedAt)))} ago\n </span>\n </>\n )}\n </StatCard>\n );\n};\n\nexport const StatsPage = () => {\n const [stats, setStats] = useState<ProjectStats | null>(null);\n const [loadFailed, setLoadFailed] = useState(false);\n\n useEffect(() => {\n fetchStats()\n .then(setStats)\n .catch(() => setLoadFailed(true));\n }, []);\n\n return (\n <div>\n <PageTitle>Stats</PageTitle>\n <p className=\"opacity-50 mb-6\">\n A read-only look at project health — every number here is informational, none of them gate\n anything.\n </p>\n {loadFailed && <p className=\"opacity-50\">Couldn't load stats.</p>}\n {!loadFailed && !stats && <p className=\"opacity-50\">Loading…</p>}\n {stats && (\n <>\n <div className=\"flex flex-wrap items-start gap-2.5\">\n <CommentRatioCard comments={stats.comments} />\n <TestCoverageCard testCoveragePct={stats.testCoveragePct} />\n <CodebaseSizeCard\n sourceLines={stats.comments.sourceLines}\n testLines={stats.testLines}\n />\n <EntitiesByStatusCard entitiesByStatus={stats.entitiesByStatus} />\n <NotesCard openQuestions={stats.openQuestions} decisions={stats.decisions} />\n <TasksPerWeekCard tasksPerWeek={stats.tasksPerWeek} />\n <UsagePerWeekCard usagePerWeek={stats.usagePerWeek} />\n <MedianPhaseDurationCard medianPhaseDurationMs={stats.medianPhaseDurationMs} />\n <MostExpensiveIdeasCard mostExpensiveIdeas={stats.mostExpensiveIdeas} />\n <ClaudeCapacityCard capacity={stats.capacity} />\n </div>\n <p className=\"opacity-[0.4] text-2xs mt-6\">\n Generated {new Date(stats.generatedAt).toLocaleString()}\n </p>\n </>\n )}\n </div>\n );\n};\n"],"names":["fetchStats","res","apiUrl","ENTITY_STATUS_ORDER","PLAN_STATUSES","ENTITY_STATUS_LABEL","STATUS_LABEL","pct","value","StatCard","title","children","jsx","Card","jsxs","StatRow","label","CommentRatioCard","comments","Progress","color","TestCoverageCard","testCoveragePct","Fragment","CodebaseSizeCard","sourceLines","testLines","EntitiesByStatusCard","entitiesByStatus","status","NotesCard","openQuestions","decisions","TasksPerWeekCard","tasksPerWeek","max","w","week","UsagePerWeekCard","usagePerWeek","formatTokens","MedianPhaseDurationCard","medianPhaseDurationMs","formatDuration","MostExpensiveIdeasCard","mostExpensiveIdeas","idea","CAPACITY_STAMP","ClaudeCapacityCard","capacity","now","setNow","useState","useEffect","id","Stamp","capacityLevel","resetsAtMs","StatsPage","stats","setStats","loadFailed","setLoadFailed","PageTitle"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index-COQOoPiS.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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"}
|