@dendelion/paper-camp 0.26.0 → 0.27.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/dist/app/assets/{index-DAhfAB9y.js → index-BEeox2IM.js} +2 -2
  2. package/dist/app/assets/{index-DAhfAB9y.js.map → index-BEeox2IM.js.map} +1 -1
  3. package/dist/app/assets/{index-X5ikcKAy.js → index-BW-jiFHN.js} +2 -2
  4. package/dist/app/assets/{index-X5ikcKAy.js.map → index-BW-jiFHN.js.map} +1 -1
  5. package/dist/app/assets/{index-DuhmLhzz.js → index-BhD5WS8p.js} +2 -2
  6. package/dist/app/assets/{index-DuhmLhzz.js.map → index-BhD5WS8p.js.map} +1 -1
  7. package/dist/app/assets/{index-HixYRYWW.js → index-C1zPjxRZ.js} +2 -2
  8. package/dist/app/assets/{index-HixYRYWW.js.map → index-C1zPjxRZ.js.map} +1 -1
  9. package/dist/app/assets/{index-sqaNs4DX.js → index-C2q1JS1i.js} +2 -2
  10. package/dist/app/assets/{index-sqaNs4DX.js.map → index-C2q1JS1i.js.map} +1 -1
  11. package/dist/app/assets/{index-CqDZ80wA.js → index-CHcj_gHq.js} +2 -2
  12. package/dist/app/assets/{index-CqDZ80wA.js.map → index-CHcj_gHq.js.map} +1 -1
  13. package/dist/app/assets/{index-DYGnWiFG.js → index-DN8Ti3Ov.js} +2 -2
  14. package/dist/app/assets/{index-DYGnWiFG.js.map → index-DN8Ti3Ov.js.map} +1 -1
  15. package/dist/app/assets/{index-VoRTCwB6.js → index-DX3JeGXT.js} +2 -2
  16. package/dist/app/assets/{index-VoRTCwB6.js.map → index-DX3JeGXT.js.map} +1 -1
  17. package/dist/app/assets/{main-Ceiva0Al.js → main-DCzV1FeH.js} +97 -97
  18. package/dist/app/assets/main-DCzV1FeH.js.map +1 -0
  19. package/dist/app/assets/{sidebar-section-CW3WaioH.js → sidebar-section-CFrPSdM0.js} +2 -2
  20. package/dist/app/assets/{sidebar-section-CW3WaioH.js.map → sidebar-section-CFrPSdM0.js.map} +1 -1
  21. package/dist/app/index.html +1 -1
  22. package/dist/chunks/{machine-registry.DF3kTKHr.js → phase-progress.DoB5EhtE.js} +276 -272
  23. package/dist/chunks/phase-progress.DoB5EhtE.js.map +1 -0
  24. package/dist/cli/index.js +3096 -3071
  25. package/dist/cli/index.js.map +1 -1
  26. package/dist/core/index.js +1 -1
  27. package/dist/core/runtime-reachability.d.ts +6 -0
  28. package/dist/core/runtime-reachability.d.ts.map +1 -0
  29. package/dist/core/runtime-reachability.test.d.ts +2 -0
  30. package/dist/core/runtime-reachability.test.d.ts.map +1 -0
  31. package/dist/core/scaffold/scaffold.d.ts.map +1 -1
  32. package/package.json +1 -1
  33. package/dist/app/assets/main-Ceiva0Al.js.map +0 -1
  34. package/dist/chunks/machine-registry.DF3kTKHr.js.map +0 -1
@@ -1,6 +1,6 @@
1
- import{u as h,a9 as y,r as x,aa as D,j as s,J as k,$ as C,g as v,ab as L,C as $,ac as N,ad as S,Z as A,ae as E,af as T,B as R,ag as I,ah as _,p as G,G as M,ai as U,w as O,P as z,aj as B,ak as H}from"./main-Ceiva0Al.js";const W=({state:e,filesEmpty:a})=>{const t=h(f=>f.status),{checks:i}=y(),{qualityStatus:l,testStatus:o,consistencyStatus:c}=x.useMemo(()=>D(t,i),[t,i]),n=[l==="fail"&&"Quality",o==="fail"&&"Tests",c==="fail"&&"Consistency"].filter(f=>f!==!1),m=s.jsx(k,{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 n.length===0?m:s.jsxs("div",{className:"flex items-center gap-2",children:[s.jsx(C,{content:`${n.join(", ")} failing`,children:s.jsx(v,{size:"small",variant:"warning",children:"!"})}),m]})},K="__git__",q=()=>!0,J=e=>L(e,{formKey:K,matchesSuggestionTask:q});function b(e){const a=e.lastIndexOf("/");return a===-1?{dir:"",base:e}:{dir:e.slice(0,a+1),base:e.slice(a+1)}}function Q(e){const a=new Map;for(const t of e){const{dir:i}=b(t.path),l=i||"./",o=a.get(l);o?o.push(t):a.set(l,[t])}return[...a].map(([t,i])=>({dir:t,entries:i}))}const V=()=>{const e=h(d=>d.diffFiles),a=h(d=>d.activeDiffPath),t=h(d=>d.loadDiffFiles),i=h(d=>d.expandDiffPath),[l,o]=x.useState(new Set),[c,n]=x.useState(!1),{toast:m}=$(),f=x.useMemo(()=>Q(e??[]),[e]),r=(e??[]).length>0&&(e??[]).every(d=>d.staged);return{files:e,activePath:a,expandDiffPath:i,groups:f,pending:l,bulkPending:c,allStaged:r,toggleAll:async()=>{const d=(e??[]).filter(j=>j.staged===r);n(!0);try{const p=(await Promise.allSettled(d.map(u=>r?S(u.path):N(u.path)))).filter(u=>u.status==="rejected").length;p>0&&m({title:r?"Unstage failed":"Stage failed",description:`${p} of ${d.length} file(s) could not be updated.`,variant:"error"})}finally{await t(),n(!1)}},toggleStaged:async(d,j)=>{o(p=>new Set(p).add(d.path));try{await(j?N(d.path):S(d.path)),await t()}catch(p){m({title:j?"Stage failed":"Unstage failed",description:A(p.message),variant:"error"})}finally{o(p=>{const u=new Set(p);return u.delete(d.path),u})}}}},X=()=>{const e=h(n=>n.diffFiles),a=h(n=>n.diffLoadFailed),t=h(n=>n.loadDiffFiles),i=h(n=>n.setActiveDiffPath),l=x.useRef(null),o=x.useMemo(()=>(e==null?void 0:e.map(n=>({path:n.path,staged:n.staged})))??[],[e]),c=J(o);return x.useEffect(()=>{t()},[t]),x.useEffect(()=>{let n;const m=E(r=>{r.message==="changed"&&(n&&clearTimeout(n),n=setTimeout(t,250))}),f=()=>t();return window.addEventListener("focus",f),()=>{n&&clearTimeout(n),window.removeEventListener("focus",f),m()}},[t]),x.useEffect(()=>{const n=l.current;if(!n)return;const m=Array.from(n.querySelectorAll("[data-diff-path]"));if(m.length===0)return;const f=new Set,r=new IntersectionObserver(g=>{for(const d of g)d.isIntersecting?f.add(d.target):f.delete(d.target);const w=m.find(d=>f.has(d));w&&i(w.dataset.diffPath??null)},{rootMargin:"0px 0px -70% 0px"});for(const g of m)r.observe(g);return()=>r.disconnect()},[e,i]),{files:e,loadFailed:a,loadDiffFiles:t,sectionsRef:l,commitForm:c}},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]})]}),Y=({path:e,prefix:a,className:t=""})=>{const{dir:i,base:l}=b(e),o=`${a??""}${i}`;return s.jsxs("span",{className:`flex min-w-0 items-center font-mono ${t}`,children:[o&&s.jsx("span",{className:"min-w-0 overflow-hidden text-ellipsis whitespace-nowrap opacity-50",children:o}),s.jsx("span",{className:"shrink-0 whitespace-nowrap",children:l})]})},Z={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"},ee={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"}},se=e=>{if(e==="??")return{letter:"??",label:"Untracked",variant:"neutral"};const[a,t]=e,i=a!==" "&&a!=="?"?a:t;return ee[i]??{letter:i??"?",label:i??"Unknown",variant:"neutral"}},P=({status:e,compact:a=!1})=>{const t=se(e);return a?s.jsx("span",{title:t.label,className:`w-4 shrink-0 text-center font-mono text-3xs font-semibold leading-none ${Z[t.variant]??""}`,children:t.letter}):s.jsx("span",{title:t.label,children:s.jsx(v,{size:"small",variant:t.variant,children:t.letter})})},te=/^@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@/;function ae(e){if(!e)return[];const a=(e.endsWith(`
1
+ import{u as h,a9 as y,r as x,aa as D,j as s,J as k,$ as C,g as v,ab as L,C as $,ac as N,ad as S,Z as A,ae as E,af as T,B as R,ag as I,ah as _,p as G,G as M,ai as U,w as O,P as z,aj as B,ak as H}from"./main-DCzV1FeH.js";const W=({state:e,filesEmpty:a})=>{const t=h(f=>f.status),{checks:i}=y(),{qualityStatus:l,testStatus:o,consistencyStatus:c}=x.useMemo(()=>D(t,i),[t,i]),n=[l==="fail"&&"Quality",o==="fail"&&"Tests",c==="fail"&&"Consistency"].filter(f=>f!==!1),m=s.jsx(k,{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 n.length===0?m:s.jsxs("div",{className:"flex items-center gap-2",children:[s.jsx(C,{content:`${n.join(", ")} failing`,children:s.jsx(v,{size:"small",variant:"warning",children:"!"})}),m]})},K="__git__",q=()=>!0,J=e=>L(e,{formKey:K,matchesSuggestionTask:q});function b(e){const a=e.lastIndexOf("/");return a===-1?{dir:"",base:e}:{dir:e.slice(0,a+1),base:e.slice(a+1)}}function Q(e){const a=new Map;for(const t of e){const{dir:i}=b(t.path),l=i||"./",o=a.get(l);o?o.push(t):a.set(l,[t])}return[...a].map(([t,i])=>({dir:t,entries:i}))}const V=()=>{const e=h(d=>d.diffFiles),a=h(d=>d.activeDiffPath),t=h(d=>d.loadDiffFiles),i=h(d=>d.expandDiffPath),[l,o]=x.useState(new Set),[c,n]=x.useState(!1),{toast:m}=$(),f=x.useMemo(()=>Q(e??[]),[e]),r=(e??[]).length>0&&(e??[]).every(d=>d.staged);return{files:e,activePath:a,expandDiffPath:i,groups:f,pending:l,bulkPending:c,allStaged:r,toggleAll:async()=>{const d=(e??[]).filter(j=>j.staged===r);n(!0);try{const p=(await Promise.allSettled(d.map(u=>r?S(u.path):N(u.path)))).filter(u=>u.status==="rejected").length;p>0&&m({title:r?"Unstage failed":"Stage failed",description:`${p} of ${d.length} file(s) could not be updated.`,variant:"error"})}finally{await t(),n(!1)}},toggleStaged:async(d,j)=>{o(p=>new Set(p).add(d.path));try{await(j?N(d.path):S(d.path)),await t()}catch(p){m({title:j?"Stage failed":"Unstage failed",description:A(p.message),variant:"error"})}finally{o(p=>{const u=new Set(p);return u.delete(d.path),u})}}}},X=()=>{const e=h(n=>n.diffFiles),a=h(n=>n.diffLoadFailed),t=h(n=>n.loadDiffFiles),i=h(n=>n.setActiveDiffPath),l=x.useRef(null),o=x.useMemo(()=>(e==null?void 0:e.map(n=>({path:n.path,staged:n.staged})))??[],[e]),c=J(o);return x.useEffect(()=>{t()},[t]),x.useEffect(()=>{let n;const m=E(r=>{r.message==="changed"&&(n&&clearTimeout(n),n=setTimeout(t,250))}),f=()=>t();return window.addEventListener("focus",f),()=>{n&&clearTimeout(n),window.removeEventListener("focus",f),m()}},[t]),x.useEffect(()=>{const n=l.current;if(!n)return;const m=Array.from(n.querySelectorAll("[data-diff-path]"));if(m.length===0)return;const f=new Set,r=new IntersectionObserver(g=>{for(const d of g)d.isIntersecting?f.add(d.target):f.delete(d.target);const w=m.find(d=>f.has(d));w&&i(w.dataset.diffPath??null)},{rootMargin:"0px 0px -70% 0px"});for(const g of m)r.observe(g);return()=>r.disconnect()},[e,i]),{files:e,loadFailed:a,loadDiffFiles:t,sectionsRef:l,commitForm:c}},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]})]}),Y=({path:e,prefix:a,className:t=""})=>{const{dir:i,base:l}=b(e),o=`${a??""}${i}`;return s.jsxs("span",{className:`flex min-w-0 items-center font-mono ${t}`,children:[o&&s.jsx("span",{className:"min-w-0 overflow-hidden text-ellipsis whitespace-nowrap opacity-50",children:o}),s.jsx("span",{className:"shrink-0 whitespace-nowrap",children:l})]})},Z={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"},ee={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"}},se=e=>{if(e==="??")return{letter:"??",label:"Untracked",variant:"neutral"};const[a,t]=e,i=a!==" "&&a!=="?"?a:t;return ee[i]??{letter:i??"?",label:i??"Unknown",variant:"neutral"}},P=({status:e,compact:a=!1})=>{const t=se(e);return a?s.jsx("span",{title:t.label,className:`w-4 shrink-0 text-center font-mono text-3xs font-semibold leading-none ${Z[t.variant]??""}`,children:t.letter}):s.jsx("span",{title:t.label,children:s.jsx(v,{size:"small",variant:t.variant,children:t.letter})})},te=/^@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@/;function ae(e){if(!e)return[];const a=(e.endsWith(`
2
2
  `)?e.slice(0,-1):e).split(`
3
3
  `),t=[];let i=null,l=0,o=0;for(const c of a){const n=c.match(te);if(n){i={header:c,lines:[]},t.push(i),l=Number(n[1]),o=Number(n[2]);continue}i&&(c.startsWith("\\")||(c.startsWith("+")?(i.lines.push({type:"add",text:c.slice(1),oldLine:null,newLine:o}),o++):c.startsWith("-")?(i.lines.push({type:"remove",text:c.slice(1),oldLine:l,newLine:null}),l++):(i.lines.push({type:"context",text:c.startsWith(" ")?c.slice(1):c,oldLine:l,newLine:o}),l++,o++)))}return t}function ie(e){return e?[{header:"",lines:(e.endsWith(`
4
4
  `)?e.slice(0,-1):e).split(`
5
5
  `).map((t,i)=>({type:"add",text:t,oldLine:null,newLine:i+1}))}]:[]}const ne=60,le={add:"bg-watercolor-green/[18%] text-watercolor-green-dark",remove:"bg-watercolor-rose/[18%] text-watercolor-rose-dark",context:""},oe={add:"+",remove:"-",context:" "},ce=({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(Y,{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})]})]}),re=({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"?ie(e.patch):ae(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,o)=>s.jsxs("span",{className:`flex ${le[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:[oe[l.type],l.text]})]},`${l.type}-${o}`))})]},`${t.header}-${i}`))})},de=({entry:e})=>{const a=h(c=>c.manuallyExpandedDiffPaths.has(e.path)),t=h(c=>c.manuallyCollapsedDiffPaths.has(e.path)),i=h(c=>c.setDiffCollapsed),l=t||!a&&e.additions+e.deletions>ne,o=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:o}),s.jsx(T,{title:s.jsx(ce,{entry:e}),expanded:!l,onToggle:()=>i(e.path,!l),children:s.jsx(re,{entry:e})})]})},fe="font-handwritten text-xs font-semibold leading-none opacity-[0.45]",me=(e,a)=>{a(e),requestAnimationFrame(()=>{var t;(t=document.querySelector(`[data-diff-path="${CSS.escape(e)}"]`))==null||t.scrollIntoView({behavior:"smooth",block:"start"})})},he=e=>{const[a,t]=e;return a!==" "&&a!=="?"&&t!==" "&&t!=="?"},pe=()=>{const{files:e,activePath:a,expandDiffPath:t,groups:i,pending:l,bulkPending:o,allStaged:c,toggleAll:n,toggleStaged:m}=V();return!e||e.length===0?null:s.jsxs("nav",{"aria-label":"Changed files",className:"flex flex-col",children:[s.jsxs("div",{className:`${fe} flex h-[32px] items-end justify-between pb-1`,children:[s.jsx("span",{children:"Changed files"}),s.jsx(C,{content:c?"Unstage every file":"Stage every file",children:s.jsx(R,{variant:"ghost",size:"tiny",className:"-mb-1",disabled:o,label:c?"Unstage all files":"Stage all files",onClick:n,icon:s.jsx(I,{})})})]}),s.jsx("ul",{className:"m-0 flex list-none flex-col p-0",children:i.map(f=>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:f.dir,children:f.dir}),s.jsx("ul",{className:"m-0 flex list-none flex-col p-0",children:f.entries.map(r=>s.jsxs("li",{className:"pc-git-file-row flex h-[32px] min-w-0 items-end gap-1.5 pb-1",children:[s.jsx(_,{checked:r.staged,indeterminate:he(r.status),disabled:l.has(r.path),onChange:g=>m(r,g.target.checked),"aria-label":r.staged?`Unstage ${r.path}`:`Stage ${r.path}`}),s.jsx(P,{status:r.status,compact:!0}),s.jsx(G,{size:"small",active:r.path===a,onClick:()=>me(r.path,t),className:"min-w-0 flex-1 items-end py-0 text-3xs",action:!r.binary&&s.jsx(F,{additions:r.additions,deletions:r.deletions}),children:s.jsx("span",{className:"block min-w-0 overflow-hidden text-ellipsis whitespace-nowrap font-mono text-3xs",title:r.path,children:b(r.path).base})})]},r.path))})]},f.dir))})]})},ue=()=>{const{files:e,loadFailed:a,loadDiffFiles:t,sectionsRef:i,commitForm:l}=X(),o="min-h-page",c=s.jsxs("div",{className:"mb-4 flex flex-wrap items-center justify-between gap-x-4 gap-y-2",children:[s.jsx(z,{className:"!mb-0",children:"Git"}),s.jsxs("div",{className:"flex items-center gap-2",children:[s.jsx(B,{}),s.jsx(H,{})]})]});return a?s.jsxs("div",{children:[c,s.jsxs("div",{className:`${o} 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(k,{variant:"secondary",size:"small",onClick:t,children:"Retry"})]})]}):e?s.jsxs("div",{children:[c,e.length===0?s.jsx("div",{className:o,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(U,{state:l,filesEmpty:!1})}),s.jsx(W,{state:l,filesEmpty:!1})]}),s.jsx("div",{ref:i,className:`flex min-w-0 flex-col gap-6 ${o}`,children:e.map((n,m)=>s.jsxs(x.Fragment,{children:[m>0&&s.jsx(O,{}),s.jsx(de,{entry:n})]},n.path))})]})]}):s.jsxs("div",{children:[c,s.jsx("div",{className:o,children:s.jsx(M,{label:"Loading the working-tree diff…"})})]})};export{pe as GitFileList,ue as GitPage};
6
- //# sourceMappingURL=index-CqDZ80wA.js.map
6
+ //# sourceMappingURL=index-CHcj_gHq.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index-CqDZ80wA.js","sources":["../../../src/app/features/git/actions/git-commit-button.tsx","../../../src/app/features/git/hooks/use-git-commit-form.ts","../../../src/app/utils/path-display.ts","../../../src/app/features/git/hooks/use-git-file-list.ts","../../../src/app/features/git/hooks/use-git-page.ts","../../../src/app/features/git/components/count-badge.tsx","../../../src/app/features/git/components/file-path.tsx","../../../src/app/features/git/components/git-status-marker.tsx","../../../src/app/utils/parse-diff.ts","../../../src/app/features/git/views/file-diff-section.tsx","../../../src/app/features/git/views/git-file-list.tsx","../../../src/app/features/git/git-page.tsx"],"sourcesContent":["import type { GitCommitFormState } from '@/app/features/git/hooks';\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\ninterface GitCommitButtonProps {\n state: GitCommitFormState;\n filesEmpty: boolean;\n}\n\n// Never Fix (plan-scoped, and the git page never has one) — a failing check is a warning instead.\nexport const GitCommitButton = ({ state, filesEmpty }: GitCommitButtonProps) => {\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 { type CommitFormFile, useCommitForm } from '@/app/hooks/use-commit-form';\n\n// Stable across renders — 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","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 { 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 { useToast } from '@dendelion/paper-ui';\nimport { useMemo, useState } from 'react';\n\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 useGitFileList = () => {\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 // 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 files,\n activePath,\n expandDiffPath,\n groups,\n pending,\n bulkPending,\n allStaged,\n toggleAll,\n toggleStaged,\n };\n};\n","import { useGitCommitForm } from '@/app/features/git/hooks/use-git-commit-form';\nimport { subscribeToActivityStream } from '@/app/services/activity-stream';\nimport { useAppStore } from '@/app/stores/app-store';\nimport { useEffect, useMemo, useRef } from 'react';\n\nexport const useGitPage = () => {\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 return {\n files,\n loadFailed,\n loadDiffFiles,\n sectionsRef,\n commitForm,\n };\n};\n\nexport type GitPageState = ReturnType<typeof useGitPage>;\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","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","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","import { CountBadge, FilePath, GitStatusMarker } from '@/app/features/git/components';\nimport { 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';\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 { CheckAllIcon } from '@/app/components/icons';\nimport { CountBadge, GitStatusMarker } from '@/app/features/git/components';\nimport { useGitFileList } from '@/app/features/git/hooks';\nimport { splitPathForDisplay } from '@/app/utils/path-display';\nimport { Checkbox, IconButton, ListItem, Tooltip } from '@dendelion/paper-ui';\n\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\nexport const GitFileList = () => {\n const {\n files,\n activePath,\n expandDiffPath,\n groups,\n pending,\n bulkPending,\n allStaged,\n toggleAll,\n toggleStaged,\n } = useGitFileList();\n\n if (!files || files.length === 0) return null;\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","import { CommitMessageFields, GitStashSurface, GitSyncActions } from '@/app/components';\nimport { PageTitle } from '@/app/components/page-title';\nimport { GitCommitButton } from '@/app/features/git/actions';\nimport { useGitPage } from '@/app/features/git/hooks';\nimport { FileDiffSection } from '@/app/features/git/views';\nimport { Button, Divider, Spinner } from '@dendelion/paper-ui';\nimport { Fragment } from 'react';\n\nexport const GitPage = () => {\n const { files, loadFailed, loadDiffFiles, sectionsRef, commitForm } = useGitPage();\n\n const contentClass = 'min-h-page';\n\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"],"names":["GitCommitButton","state","filesEmpty","status","useAppStore","s","deskChecks","useDeskChecks","qualityStatus","testStatus","consistencyStatus","useMemo","deriveCheckStatuses","failingChecks","label","commitButton","jsx","Button","jsxs","Tooltip","Stamp","GIT_PAGE_FORM_KEY","matchAnySuggestionTask","useGitCommitForm","files","useCommitForm","splitPathForDisplay","path","slash","groupByFolder","groups","entry","dir","key","bucket","entries","useGitFileList","activePath","loadDiffFiles","expandDiffPath","pending","setPending","useState","bulkPending","setBulkPending","toast","useToast","allStaged","f","targets","failed","unstagePath","stagePath","r","next","prev","err","oneLineErrorSummary","updated","useGitPage","loadFailed","setActiveDiffPath","sectionsRef","useRef","commitFiles","commitForm","useEffect","timer","unsubscribe","subscribeToActivityStream","payload","onFocus","root","sections","visible","observer","topmost","el","CountBadge","additions","deletions","FilePath","prefix","className","base","dimmed","COMPACT_COLOR","STATUS_INFO","gitStatusInfo","x","y","code","GitStatusMarker","compact","info","HUNK_HEADER","parsePatch","patch","lines","hunks","current","oldLine","newLine","line","header","rawContentHunks","content","text","COLLAPSE_LINE_THRESHOLD","LINE_CLASS","LINE_PREFIX","FileHeader","DiffBody","hunk","j","FileDiffSection","manuallyExpanded","manuallyCollapsed","setDiffCollapsed","collapsed","headingText","Accordion","sectionLabelClass","scrollToFile","_a","isPartiallyStaged","GitFileList","toggleAll","toggleStaged","IconButton","CheckAllIcon","group","Checkbox","e","ListItem","GitPage","contentClass","PageTitle","GitSyncActions","GitStashSurface","Fragment","CommitMessageFields","idx","Divider","Spinner"],"mappings":"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"}
1
+ {"version":3,"file":"index-CHcj_gHq.js","sources":["../../../src/app/features/git/actions/git-commit-button.tsx","../../../src/app/features/git/hooks/use-git-commit-form.ts","../../../src/app/utils/path-display.ts","../../../src/app/features/git/hooks/use-git-file-list.ts","../../../src/app/features/git/hooks/use-git-page.ts","../../../src/app/features/git/components/count-badge.tsx","../../../src/app/features/git/components/file-path.tsx","../../../src/app/features/git/components/git-status-marker.tsx","../../../src/app/utils/parse-diff.ts","../../../src/app/features/git/views/file-diff-section.tsx","../../../src/app/features/git/views/git-file-list.tsx","../../../src/app/features/git/git-page.tsx"],"sourcesContent":["import type { GitCommitFormState } from '@/app/features/git/hooks';\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\ninterface GitCommitButtonProps {\n state: GitCommitFormState;\n filesEmpty: boolean;\n}\n\n// Never Fix (plan-scoped, and the git page never has one) — a failing check is a warning instead.\nexport const GitCommitButton = ({ state, filesEmpty }: GitCommitButtonProps) => {\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 { type CommitFormFile, useCommitForm } from '@/app/hooks/use-commit-form';\n\n// Stable across renders — 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","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 { 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 { useToast } from '@dendelion/paper-ui';\nimport { useMemo, useState } from 'react';\n\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 useGitFileList = () => {\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 // 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 files,\n activePath,\n expandDiffPath,\n groups,\n pending,\n bulkPending,\n allStaged,\n toggleAll,\n toggleStaged,\n };\n};\n","import { useGitCommitForm } from '@/app/features/git/hooks/use-git-commit-form';\nimport { subscribeToActivityStream } from '@/app/services/activity-stream';\nimport { useAppStore } from '@/app/stores/app-store';\nimport { useEffect, useMemo, useRef } from 'react';\n\nexport const useGitPage = () => {\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 return {\n files,\n loadFailed,\n loadDiffFiles,\n sectionsRef,\n commitForm,\n };\n};\n\nexport type GitPageState = ReturnType<typeof useGitPage>;\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","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","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","import { CountBadge, FilePath, GitStatusMarker } from '@/app/features/git/components';\nimport { 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';\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 { CheckAllIcon } from '@/app/components/icons';\nimport { CountBadge, GitStatusMarker } from '@/app/features/git/components';\nimport { useGitFileList } from '@/app/features/git/hooks';\nimport { splitPathForDisplay } from '@/app/utils/path-display';\nimport { Checkbox, IconButton, ListItem, Tooltip } from '@dendelion/paper-ui';\n\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\nexport const GitFileList = () => {\n const {\n files,\n activePath,\n expandDiffPath,\n groups,\n pending,\n bulkPending,\n allStaged,\n toggleAll,\n toggleStaged,\n } = useGitFileList();\n\n if (!files || files.length === 0) return null;\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","import { CommitMessageFields, GitStashSurface, GitSyncActions } from '@/app/components';\nimport { PageTitle } from '@/app/components/page-title';\nimport { GitCommitButton } from '@/app/features/git/actions';\nimport { useGitPage } from '@/app/features/git/hooks';\nimport { FileDiffSection } from '@/app/features/git/views';\nimport { Button, Divider, Spinner } from '@dendelion/paper-ui';\nimport { Fragment } from 'react';\n\nexport const GitPage = () => {\n const { files, loadFailed, loadDiffFiles, sectionsRef, commitForm } = useGitPage();\n\n const contentClass = 'min-h-page';\n\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"],"names":["GitCommitButton","state","filesEmpty","status","useAppStore","s","deskChecks","useDeskChecks","qualityStatus","testStatus","consistencyStatus","useMemo","deriveCheckStatuses","failingChecks","label","commitButton","jsx","Button","jsxs","Tooltip","Stamp","GIT_PAGE_FORM_KEY","matchAnySuggestionTask","useGitCommitForm","files","useCommitForm","splitPathForDisplay","path","slash","groupByFolder","groups","entry","dir","key","bucket","entries","useGitFileList","activePath","loadDiffFiles","expandDiffPath","pending","setPending","useState","bulkPending","setBulkPending","toast","useToast","allStaged","f","targets","failed","unstagePath","stagePath","r","next","prev","err","oneLineErrorSummary","updated","useGitPage","loadFailed","setActiveDiffPath","sectionsRef","useRef","commitFiles","commitForm","useEffect","timer","unsubscribe","subscribeToActivityStream","payload","onFocus","root","sections","visible","observer","topmost","el","CountBadge","additions","deletions","FilePath","prefix","className","base","dimmed","COMPACT_COLOR","STATUS_INFO","gitStatusInfo","x","y","code","GitStatusMarker","compact","info","HUNK_HEADER","parsePatch","patch","lines","hunks","current","oldLine","newLine","line","header","rawContentHunks","content","text","COLLAPSE_LINE_THRESHOLD","LINE_CLASS","LINE_PREFIX","FileHeader","DiffBody","hunk","j","FileDiffSection","manuallyExpanded","manuallyCollapsed","setDiffCollapsed","collapsed","headingText","Accordion","sectionLabelClass","scrollToFile","_a","isPartiallyStaged","GitFileList","toggleAll","toggleStaged","IconButton","CheckAllIcon","group","Checkbox","e","ListItem","GitPage","contentClass","PageTitle","GitSyncActions","GitStashSurface","Fragment","CommitMessageFields","idx","Divider","Spinner"],"mappings":"2NAaO,MAAMA,EAAkB,CAAC,CAAE,MAAAC,EAAO,WAAAC,KAAuC,CAC9E,MAAMC,EAASC,EAAaC,GAAMA,EAAE,MAAM,EACpC,CAAE,OAAQC,CAAA,EAAeC,EAAA,EACzB,CAAE,cAAAC,EAAe,WAAAC,EAAY,kBAAAC,CAAA,EAAsBC,EAAAA,QACvD,IAAMC,EAAoBT,EAAQG,CAAU,EAC5C,CAACH,EAAQG,CAAU,CAAA,EAEfO,EAAgB,CACpBL,IAAkB,QAAU,UAC5BC,IAAe,QAAU,QACzBC,IAAsB,QAAU,aAAA,EAChC,OAAQI,GAAiDA,IAAU,EAAK,EAEpEC,EACJC,EAAAA,IAACC,EAAA,CACC,KAAK,QACL,SAAUf,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,OAAIY,EAAc,SAAW,EAAUE,EAGrCG,EAAAA,KAAC,MAAA,CAAI,UAAU,0BACb,SAAA,CAAAF,MAACG,EAAA,CAAQ,QAAS,GAAGN,EAAc,KAAK,IAAI,CAAC,WAC3C,SAAAG,EAAAA,IAACI,GAAM,KAAK,QAAQ,QAAQ,UAAU,aAEtC,EACF,EACCL,CAAA,EACH,CAEJ,ECjDMM,EAAoB,UAEpBC,EAAyB,IAAM,GAGxBC,EAAoBC,GAC/BC,EAAcD,EAAO,CACnB,QAASH,EACT,sBAAuBC,CACzB,CAAC,ECPI,SAASI,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,CCFA,SAASC,EAAcL,EAAqE,CAC1F,MAAMM,MAAa,IACnB,UAAWC,KAASP,EAAO,CACzB,KAAM,CAAE,IAAAQ,CAAA,EAAQN,EAAoBK,EAAM,IAAI,EACxCE,EAAMD,GAAO,KACbE,EAASJ,EAAO,IAAIG,CAAG,EACzBC,EAAQA,EAAO,KAAKH,CAAK,EACxBD,EAAO,IAAIG,EAAK,CAACF,CAAK,CAAC,CAC9B,CACA,MAAO,CAAC,GAAGD,CAAM,EAAE,IAAI,CAAC,CAACE,EAAKG,CAAO,KAAO,CAAE,IAAAH,EAAK,QAAAG,GAAU,CAC/D,CAEO,MAAMC,EAAiB,IAAM,CAClC,MAAMZ,EAAQpB,EAAaC,GAAMA,EAAE,SAAS,EACtCgC,EAAajC,EAAaC,GAAMA,EAAE,cAAc,EAChDiC,EAAgBlC,EAAaC,GAAMA,EAAE,aAAa,EAClDkC,EAAiBnC,EAAaC,GAAMA,EAAE,cAAc,EACpD,CAACmC,EAASC,CAAU,EAAIC,EAAAA,SAAsB,IAAI,GAAK,EACvD,CAACC,EAAaC,CAAc,EAAIF,EAAAA,SAAS,EAAK,EAC9C,CAAE,MAAAG,CAAA,EAAUC,EAAA,EACZhB,EAASnB,EAAAA,QAAQ,IAAMkB,EAAcL,GAAS,EAAE,EAAG,CAACA,CAAK,CAAC,EAC1DuB,GAAavB,GAAS,CAAA,GAAI,OAAS,IAAMA,GAAS,CAAA,GAAI,MAAOwB,GAAMA,EAAE,MAAM,EA6CjF,MAAO,CACL,MAAAxB,EACA,WAAAa,EACA,eAAAE,EACA,OAAAT,EACA,QAAAU,EACA,YAAAG,EACA,UAAAI,EACA,UAjDgB,SAAY,CAC5B,MAAME,GAAWzB,GAAS,IAAI,OAAQwB,GAAMA,EAAE,SAAWD,CAAS,EAClEH,EAAe,EAAI,EACnB,GAAI,CAIF,MAAMM,GAHU,MAAM,QAAQ,WAC5BD,EAAQ,IAAKD,GAAOD,EAAYI,EAAYH,EAAE,IAAI,EAAII,EAAUJ,EAAE,IAAI,CAAE,CAAA,GAEnD,OAAQK,GAAMA,EAAE,SAAW,UAAU,EAAE,OAC1DH,EAAS,GACXL,EAAM,CACJ,MAAOE,EAAY,iBAAmB,eACtC,YAAa,GAAGG,CAAM,OAAOD,EAAQ,MAAM,iCAC3C,QAAS,OAAA,CACV,CAEL,QAAA,CACE,MAAMX,EAAA,EACNM,EAAe,EAAK,CACtB,CACF,EA+BE,aA7BmB,MAAOb,EAAsBuB,IAAkB,CAClEb,EAAYc,GAAS,IAAI,IAAIA,CAAI,EAAE,IAAIxB,EAAM,IAAI,CAAC,EAClD,GAAI,CACF,MAAOuB,EAAOF,EAAUrB,EAAM,IAAI,EAAIoB,EAAYpB,EAAM,IAAI,GAC5D,MAAMO,EAAA,CACR,OAASkB,EAAK,CACZX,EAAM,CACJ,MAAOS,EAAO,eAAiB,iBAC/B,YAAaG,EAAqBD,EAAc,OAAO,EACvD,QAAS,OAAA,CACV,CACH,QAAA,CACEf,EAAYc,GAAS,CACnB,MAAMG,EAAU,IAAI,IAAIH,CAAI,EAC5B,OAAAG,EAAQ,OAAO3B,EAAM,IAAI,EAClB2B,CACT,CAAC,CACH,CACF,CAWE,CAEJ,EChFaC,EAAa,IAAM,CAC9B,MAAMnC,EAAQpB,EAAaC,GAAMA,EAAE,SAAS,EACtCuD,EAAaxD,EAAaC,GAAMA,EAAE,cAAc,EAChDiC,EAAgBlC,EAAaC,GAAMA,EAAE,aAAa,EAClDwD,EAAoBzD,EAAaC,GAAMA,EAAE,iBAAiB,EAC1DyD,EAAcC,EAAAA,OAAuB,IAAI,EACzCC,EAAcrD,EAAAA,QAClB,KAAMa,GAAA,YAAAA,EAAO,IAAKO,IAAW,CAAE,KAAMA,EAAM,KAAM,OAAQA,EAAM,MAAA,MAAc,CAAA,EAC7E,CAACP,CAAK,CAAA,EAEFyC,EAAa1C,EAAiByC,CAAW,EAE/CE,OAAAA,EAAAA,UAAU,IAAM,CACd5B,EAAA,CACF,EAAG,CAACA,CAAa,CAAC,EAElB4B,EAAAA,UAAU,IAAM,CACd,IAAIC,EACJ,MAAMC,EAAcC,EAA2BC,GAAY,CACrDA,EAAQ,UAAY,YACpBH,gBAAoBA,CAAK,EAC7BA,EAAQ,WAAW7B,EAAe,GAAG,EACvC,CAAC,EACKiC,EAAU,IAAMjC,EAAA,EACtB,cAAO,iBAAiB,QAASiC,CAAO,EACjC,IAAM,CACPJ,gBAAoBA,CAAK,EAC7B,OAAO,oBAAoB,QAASI,CAAO,EAC3CH,EAAA,CACF,CACF,EAAG,CAAC9B,CAAa,CAAC,EAGlB4B,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,qBAClBxC,GAAY,CACX,UAAWJ,KAASI,EACdJ,EAAM,eAAgB2C,EAAQ,IAAI3C,EAAM,MAAqB,EAC5D2C,EAAQ,OAAO3C,EAAM,MAAqB,EAEjD,MAAM6C,EAAUH,EAAS,KAAMI,GAAOH,EAAQ,IAAIG,CAAE,CAAC,EACjDD,GAASf,EAAkBe,EAAQ,QAAQ,UAAY,IAAI,CACjE,EACA,CAAE,WAAY,kBAAA,CAAmB,EAEnC,UAAWC,KAAMJ,EAAUE,EAAS,QAAQE,CAAE,EAC9C,MAAO,IAAMF,EAAS,WAAA,CACxB,EAAG,CAACnD,EAAOqC,CAAiB,CAAC,EAEtB,CACL,MAAArC,EACA,WAAAoC,EACA,cAAAtB,EACA,YAAAwB,EACA,WAAAG,CAAA,CAEJ,EC7Daa,EAAa,CAAC,CAAE,UAAAC,EAAW,UAAAC,KACtC9D,OAAC,OAAA,CAAK,UAAU,kDACd,SAAA,CAAAA,EAAAA,KAAC,OAAA,CAAK,UAAU,6BAA6B,SAAA,CAAA,IAAE6D,CAAA,EAAU,EACzD7D,EAAAA,KAAC,OAAA,CAAK,UAAU,4BAA4B,SAAA,CAAA,IAAE8D,CAAA,CAAA,CAAU,CAAA,EAC1D,ECDWC,EAAW,CAAC,CAAE,KAAAtD,EAAM,OAAAuD,EAAQ,UAAAC,EAAY,MAAwB,CAC3E,KAAM,CAAE,IAAAnD,EAAK,KAAAoD,GAAS1D,EAAoBC,CAAI,EACxC0D,EAAS,GAAGH,GAAU,EAAE,GAAGlD,CAAG,GACpC,OACEd,EAAAA,KAAC,OAAA,CAAK,UAAW,uCAAuCiE,CAAS,GAC9D,SAAA,CAAAE,GACCrE,EAAAA,IAAC,OAAA,CAAK,UAAU,qEACb,SAAAqE,EACH,EAEFrE,EAAAA,IAAC,OAAA,CAAK,UAAU,6BAA8B,SAAAoE,CAAA,CAAK,CAAA,EACrD,CAEJ,ECXME,EAA8C,CAClD,QAAS,6BACT,KAAM,4BACN,MAAO,4BACP,QAAS,6BACT,QAAS,4BACX,EAEMC,GAA0C,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,GAAiBrF,GAA+B,CAC3D,GAAIA,IAAW,KAAM,MAAO,CAAE,OAAQ,KAAM,MAAO,YAAa,QAAS,SAAA,EACzE,KAAM,CAACsF,EAAGC,CAAC,EAAIvF,EACTwF,EAAOF,IAAM,KAAOA,IAAM,IAAMA,EAAIC,EAC1C,OAAOH,GAAYI,CAAI,GAAK,CAAE,OAAQA,GAAQ,IAAK,MAAOA,GAAQ,UAAW,QAAS,SAAA,CACxF,EAOaC,EAAkB,CAAC,CAAE,OAAAzF,EAAQ,QAAA0F,EAAU,MAAkC,CACpF,MAAMC,EAAON,GAAcrF,CAAM,EACjC,OAAI0F,EAEA7E,EAAAA,IAAC,OAAA,CACC,MAAO8E,EAAK,MACZ,UAAW,0EAA0ER,EAAcQ,EAAK,OAAO,GAAK,EAAE,GAErH,SAAAA,EAAK,MAAA,CAAA,EAKV9E,EAAAA,IAAC,OAAA,CAAK,MAAO8E,EAAK,MAChB,SAAA9E,EAAAA,IAACI,EAAA,CAAM,KAAK,QAAQ,QAAS0E,EAAK,QAC/B,SAAAA,EAAK,OACR,EACF,CAEJ,EC9CMC,GAAc,0CAIb,SAASC,GAAWC,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,EAAW,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,GAAgBC,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,CC9DA,MAAMC,GAA0B,GAE1BC,GAA2C,CAC/C,IAAK,uDACL,OAAQ,qDACR,QAAS,EACX,EAEMC,GAA4C,CAAE,IAAK,IAAK,OAAQ,IAAK,QAAS,GAAA,EAM9EC,GAAa,CAAC,CAAE,MAAAhF,KACpBb,EAAAA,KAAC,MAAA,CAAI,UAAU,yCACb,SAAA,CAAAF,EAAAA,IAAC4E,EAAA,CAAgB,OAAQ7D,EAAM,MAAA,CAAQ,EACvCf,EAAAA,IAACiE,EAAA,CACC,KAAMlD,EAAM,KACZ,OAAQA,EAAM,aAAe,GAAGA,EAAM,YAAY,MAAQ,OAC1D,UAAU,wBAAA,CAAA,EAEZb,EAAAA,KAAC,OAAA,CAAK,UAAU,mCACb,SAAA,CAAAa,EAAM,QAAUf,EAAAA,IAACI,EAAA,CAAM,KAAK,QAAQ,SAAA,SAAM,EAC1C,CAACW,EAAM,QAAUf,EAAAA,IAAC8D,EAAA,CAAW,UAAW/C,EAAM,UAAW,UAAWA,EAAM,SAAA,CAAW,CAAA,CAAA,CACxF,CAAA,EACF,EAOIiF,GAAW,CAAC,CAAE,MAAAjF,KAA2B,CAC7C,GAAIA,EAAM,OACR,OAAOf,EAAAA,IAAC,IAAA,CAAE,UAAU,iBAAiB,SAAA,yBAAsB,EAE7D,GAAIe,EAAM,cAAgB,YACxB,OAAOf,EAAAA,IAAC,IAAA,CAAE,UAAU,iBAAiB,SAAA,6BAA0B,EAEjE,MAAMmF,EACJpE,EAAM,cAAgB,MAAQ0E,GAAgB1E,EAAM,KAAK,EAAIiE,GAAWjE,EAAM,KAAK,EACrF,OAAIoE,EAAM,SAAW,EACfpE,EAAM,aAENf,EAAAA,IAAC,IAAA,CAAE,UAAU,iBACV,SAAAe,EAAM,YAAc,GAAKA,EAAM,YAAc,EAC1C,+BACA,gDAAA,CACN,EAGGf,EAAAA,IAAC,IAAA,CAAE,UAAU,iBAAiB,SAAA,yBAAsB,EAG3DA,EAAAA,IAAC,MAAA,CAAI,UAAU,yCACZ,SAAAmF,EAAM,IAAI,CAACc,EAAM,IAChB/F,OAAC,MAAA,CAAgC,UAAU,qBACxC,SAAA,CAAA+F,EAAK,QAAUjG,EAAAA,IAAC,MAAA,CAAI,UAAU,qCAAsC,WAAK,OAAO,QAEhF,MAAA,CAAI,UAAU,2EACZ,SAAAiG,EAAK,MAAM,IAAI,CAACV,EAAMW,IACrBhG,EAAAA,KAAC,QAA+B,UAAW,QAAQ2F,GAAWN,EAAK,IAAI,CAAC,GACtE,SAAA,CAAArF,EAAAA,KAAC,OAAA,CACC,UAAU,yDACV,cAAY,OAEZ,SAAA,CAAAF,MAAC,OAAA,CAAK,UAAU,iBAAkB,SAAAuF,EAAK,SAAW,GAAG,QACpD,OAAA,CAAK,UAAU,iBAAkB,SAAAA,EAAK,SAAW,EAAA,CAAG,CAAA,CAAA,CAAA,SAEtD,OAAA,CACE,SAAA,CAAAO,GAAYP,EAAK,IAAI,EACrBA,EAAK,IAAA,CAAA,CACR,CAAA,CAAA,EAXS,GAAGA,EAAK,IAAI,IAAIW,CAAC,EAY5B,CACD,CAAA,CACH,CAAA,GAnBQ,GAAGD,EAAK,MAAM,IAAI,CAAC,EAoB7B,CACD,EACH,CAEJ,EAMaE,GAAkB,CAAC,CAAE,MAAApF,KAAkC,CAClE,MAAMqF,EAAmBhH,EAAaC,GAAMA,EAAE,0BAA0B,IAAI0B,EAAM,IAAI,CAAC,EACjFsF,EAAoBjH,EAAaC,GAAMA,EAAE,2BAA2B,IAAI0B,EAAM,IAAI,CAAC,EACnFuF,EAAmBlH,EAAaC,GAAMA,EAAE,gBAAgB,EACxDkH,EACJF,GACC,CAACD,GAAoBrF,EAAM,UAAYA,EAAM,UAAY6E,GAEtDY,EAAczF,EAAM,aAAe,GAAGA,EAAM,YAAY,MAAMA,EAAM,IAAI,GAAKA,EAAM,KAEzF,cACG,MAAA,CAAI,iBAAgBA,EAAM,KAAM,UAAU,iCACzC,SAAA,CAAAf,EAAAA,IAAC,KAAA,CAAG,UAAU,UAAW,SAAAwG,EAAY,EACrCxG,EAAAA,IAACyG,EAAA,CACC,MAAOzG,EAAAA,IAAC+F,GAAA,CAAW,MAAAhF,CAAA,CAAc,EACjC,SAAU,CAACwF,EACX,SAAU,IAAMD,EAAiBvF,EAAM,KAAM,CAACwF,CAAS,EAEvD,SAAAvG,EAAAA,IAACgG,IAAS,MAAAjF,CAAA,CAAc,CAAA,CAAA,CAC1B,EACF,CAEJ,EC7GM2F,GAAoB,qEAEpBC,GAAe,CAAChG,EAAcY,IAA2C,CAC7EA,EAAeZ,CAAI,EACnB,sBAAsB,IAAM,QAC1BiG,EAAA,SACG,cAAc,oBAAoB,IAAI,OAAOjG,CAAI,CAAC,IAAI,IADzD,MAAAiG,EAEI,eAAe,CAAE,SAAU,SAAU,MAAO,SAClD,CAAC,CACH,EAIMC,GAAqB1H,GAAmB,CAC5C,KAAM,CAACsF,EAAGC,CAAC,EAAIvF,EACf,OAAOsF,IAAM,KAAOA,IAAM,KAAOC,IAAM,KAAOA,IAAM,GACtD,EAEaoC,GAAc,IAAM,CAC/B,KAAM,CACJ,MAAAtG,EACA,WAAAa,EACA,eAAAE,EACA,OAAAT,EACA,QAAAU,EACA,YAAAG,EACA,UAAAI,EACA,UAAAgF,EACA,aAAAC,CAAA,EACE5F,EAAA,EAEJ,MAAI,CAACZ,GAASA,EAAM,SAAW,EAAU,KAGvCN,EAAAA,KAAC,MAAA,CAAI,aAAW,gBAAgB,UAAU,gBACxC,SAAA,CAAAA,EAAAA,KAAC,MAAA,CAAI,UAAW,GAAGwG,EAAiB,gDAClC,SAAA,CAAA1G,EAAAA,IAAC,QAAK,SAAA,eAAA,CAAa,EACnBA,EAAAA,IAACG,EAAA,CAAQ,QAAS4B,EAAY,qBAAuB,mBACnD,SAAA/B,EAAAA,IAACiH,EAAA,CACC,QAAQ,QACR,KAAK,OACL,UAAU,QACV,SAAUtF,EACV,MAAOI,EAAY,oBAAsB,kBACzC,QAASgF,EACT,WAAOG,EAAA,CAAA,CAAa,CAAA,CAAA,CACtB,CACF,CAAA,EACF,EAEAlH,EAAAA,IAAC,MAAG,UAAU,kCACX,WAAO,IAAKmH,GACXjH,EAAAA,KAAC,KAAA,CACC,SAAA,CAAAF,EAAAA,IAAC,MAAA,CACC,UAAU,0HACV,MAAOmH,EAAM,IAEZ,SAAAA,EAAM,GAAA,CAAA,EAETnH,EAAAA,IAAC,MAAG,UAAU,kCACX,WAAM,QAAQ,IAAKe,GAClBb,EAAAA,KAAC,KAAA,CAEC,UAAU,+DAEV,SAAA,CAAAF,EAAAA,IAACoH,EAAA,CACC,QAASrG,EAAM,OACf,cAAe8F,GAAkB9F,EAAM,MAAM,EAC7C,SAAUS,EAAQ,IAAIT,EAAM,IAAI,EAChC,SAAWsG,GAAML,EAAajG,EAAOsG,EAAE,OAAO,OAAO,EACrD,aAAYtG,EAAM,OAAS,WAAWA,EAAM,IAAI,GAAK,SAASA,EAAM,IAAI,EAAA,CAAA,QAEzE6D,EAAA,CAAgB,OAAQ7D,EAAM,OAAQ,QAAO,GAAC,EAC/Cf,EAAAA,IAACsH,EAAA,CACC,KAAK,QACL,OAAQvG,EAAM,OAASM,EACvB,QAAS,IAAMsF,GAAa5F,EAAM,KAAMQ,CAAc,EACtD,UAAU,yCACV,OACE,CAACR,EAAM,QACLf,EAAAA,IAAC8D,EAAA,CAAW,UAAW/C,EAAM,UAAW,UAAWA,EAAM,SAAA,CAAW,EAIxE,SAAAf,EAAAA,IAAC,OAAA,CACC,UAAU,mFACV,MAAOe,EAAM,KAEZ,SAAAL,EAAoBK,EAAM,IAAI,EAAE,IAAA,CAAA,CACnC,CAAA,CACF,CAAA,EA5BKA,EAAM,IAAA,CA8Bd,CAAA,CACH,CAAA,GAzCOoG,EAAM,GA0Cf,CACD,CAAA,CACH,CAAA,EACF,CAEJ,ECjGaI,GAAU,IAAM,CAC3B,KAAM,CAAE,MAAA/G,EAAO,WAAAoC,EAAY,cAAAtB,EAAe,YAAAwB,EAAa,WAAAG,CAAA,EAAeN,EAAA,EAEhE6E,EAAe,aAEfhC,EACJtF,EAAAA,KAAC,MAAA,CAAI,UAAU,mEACb,SAAA,CAAAF,EAAAA,IAACyH,EAAA,CAAU,UAAU,QAAQ,SAAA,MAAG,EAChCvH,EAAAA,KAAC,MAAA,CAAI,UAAU,0BACb,SAAA,CAAAF,EAAAA,IAAC0H,EAAA,EAAe,QACfC,EAAA,CAAA,CAAgB,CAAA,CAAA,CACnB,CAAA,EACF,EAGF,OAAI/E,SAEC,MAAA,CACE,SAAA,CAAA4C,EACDtF,EAAAA,KAAC,MAAA,CAAI,UAAW,GAAGsH,CAAY,mCAC7B,SAAA,CAAAxH,EAAAA,IAAC,IAAA,CAAE,UAAU,iBAAiB,SAAA,uCAAoC,EAClEA,EAAAA,IAACC,GAAO,QAAQ,YAAY,KAAK,QAAQ,QAASqB,EAAe,SAAA,OAAA,CAEjE,CAAA,CAAA,CACF,CAAA,EACF,EAICd,SAYF,MAAA,CACE,SAAA,CAAAgF,EACAhF,EAAM,SAAW,EAChBR,MAAC,OAAI,UAAWwH,EACd,SAAAxH,MAAC,IAAA,CAAE,UAAU,aAAa,SAAA,mBAAA,CAAiB,CAAA,CAC7C,EAEAE,EAAAA,KAAA0H,WAAA,CACE,SAAA,CAAA1H,EAAAA,KAAC,MAAA,CAAI,UAAU,sDACb,SAAA,CAAAF,EAAAA,IAAC,MAAA,CAAI,UAAU,SACb,SAAAA,EAAAA,IAAC6H,GAAoB,MAAO5E,EAAY,WAAY,EAAA,CAAO,CAAA,CAC7D,EACAjD,EAAAA,IAAChB,EAAA,CAAgB,MAAOiE,EAAY,WAAY,EAAA,CAAO,CAAA,EACzD,EACAjD,EAAAA,IAAC,MAAA,CAAI,IAAK8C,EAAa,UAAW,+BAA+B0E,CAAY,GAC1E,SAAAhH,EAAM,IAAI,CAACO,EAAO+G,IACjB5H,OAAC0H,EAAAA,SAAA,CACE,SAAA,CAAAE,EAAM,SAAMC,EAAA,CAAA,CAAQ,EACrB/H,MAACmG,IAAgB,MAAApF,CAAA,CAAc,CAAA,GAFlBA,EAAM,IAGrB,CACD,CAAA,CACH,CAAA,CAAA,CACF,CAAA,EAEJ,SAlCG,MAAA,CACE,SAAA,CAAAyE,EACDxF,EAAAA,IAAC,OAAI,UAAWwH,EACd,eAACQ,EAAA,CAAQ,MAAM,iCAAiC,CAAA,CAClD,CAAA,EACF,CA+BN"}
@@ -1,2 +1,2 @@
1
- import{c as g,r,a3 as v,a0 as f,j as e,R as N,g as p,a4 as y,a5 as S,a6 as m,a7 as x,K as d,a8 as c,P as b}from"./main-Ceiva0Al.js";const T=async()=>{const s=await fetch(g("/api/stats"));if(!s.ok)throw new Error(`Failed to fetch stats: ${s.status}`);return s.json()},C=()=>{const[s,a]=r.useState(null),[t,o]=r.useState(!1);return r.useEffect(()=>{T().then(a).catch(()=>o(!0))},[]),{stats:s,loadFailed:t}},w=["open",...v],A={...f,open:"Open"},L={allowed:"success",warning:"warning",rejected:"error"},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})]}),M=[{key:"five_hour",label:"5-hour limit"},{key:"seven_day",label:"Weekly"}],E=({capacity:s})=>{const[a,t]=r.useState(()=>Date.now());return r.useEffect(()=>{const o=setInterval(()=>t(Date.now()),3e4);return()=>clearInterval(o)},[]),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(p,{size:"small",variant:L[y(s.snapshot.status)],children:s.snapshot.status}),s.snapshot.overage&&e.jsx(p,{size:"small",variant:"warning",children:"overage"})]}),M.map(({key:o,label:h})=>{var u;const i=(u=s.snapshot.unifiedWindows)==null?void 0:u[o];if(!i)return null;const j=i.resetsAt===void 0?"":` · resets ${new Date(S(i.resetsAt)).toLocaleTimeString()}`;return e.jsx(n,{label:h,value:`${Math.round(i.utilization*100)}% used${j}`},o)}),e.jsxs("span",{className:"text-2xs opacity-50",children:["as of last agent run,"," ",m(Math.max(0,a-Date.parse(s.capturedAt)))," ago"]})]})})},k=({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})]}),_=s=>`${Math.round(s*100)}%`,D=({comments:s})=>e.jsxs(l,{title:"Comment ratio",children:[e.jsx(x,{value:s.ratio*100,color:d.accentSlate}),e.jsx(n,{label:"Ratio",value:_(s.ratio)}),e.jsx(n,{label:"Comment lines",value:s.commentLines}),e.jsx(n,{label:"Source lines",value:s.sourceLines})]}),P=({entitiesByStatus:s})=>e.jsxs(l,{title:"Entities by status",children:[w.filter(a=>s[a]).map(a=>e.jsx(n,{label:A[a],value:s[a]},a)),Object.keys(s).length===0&&e.jsx("p",{className:"opacity-50 m-0",children:"No entities yet."})]}),I=({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:m(s)})}),R=({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:`${c(a.inputTokens)} in · ${c(a.outputTokens)} out`}),e.jsx("span",{className:"text-2xs opacity-50 truncate",children:a.planTitle})]},a.planId))]}),$=({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})]}),F=({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(x,{value:t.count,max:a,color:d.accentAmber,height:4})]},t.week))]})},U=({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(x,{value:s,color:d.accentGreen}),e.jsx(n,{label:"Line coverage",value:`${Math.round(s)}%`})]})}),W=({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(x,{value:t.agentMinutes,max:a,color:d.accentAmber,height:4}),e.jsxs("span",{className:"text-2xs opacity-50",children:[c(t.inputTokens)," in · ",c(t.outputTokens)," out"]})]},t.week))]})},z=({stats:s})=>e.jsxs(e.Fragment,{children:[e.jsxs("div",{className:"flex flex-wrap items-start gap-2.5",children:[e.jsx(D,{comments:s.comments}),e.jsx(U,{testCoveragePct:s.testCoveragePct}),e.jsx(k,{sourceLines:s.comments.sourceLines,testLines:s.testLines}),e.jsx(P,{entitiesByStatus:s.entitiesByStatus}),e.jsx($,{openQuestions:s.openQuestions,decisions:s.decisions}),e.jsx(F,{tasksPerWeek:s.tasksPerWeek}),e.jsx(W,{usagePerWeek:s.usagePerWeek}),e.jsx(I,{medianPhaseDurationMs:s.medianPhaseDurationMs}),e.jsx(R,{mostExpensiveIdeas:s.mostExpensiveIdeas}),e.jsx(E,{capacity:s.capacity})]}),e.jsxs("p",{className:"opacity-[0.4] text-2xs mt-6",children:["Generated ",new Date(s.generatedAt).toLocaleString()]})]}),B=()=>{const{stats:s,loadFailed:a}=C();return e.jsxs("div",{children:[e.jsx(b,{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."}),a&&e.jsx("p",{className:"opacity-50",children:"Couldn't load stats."}),!a&&!s&&e.jsx("p",{className:"opacity-50",children:"Loading…"}),s&&e.jsx(z,{stats:s})]})};export{B as StatsPage};
2
- //# sourceMappingURL=index-DYGnWiFG.js.map
1
+ import{c as g,r,a3 as v,a0 as f,j as e,R as N,g as p,a4 as y,a5 as S,a6 as m,a7 as x,K as d,a8 as c,P as b}from"./main-DCzV1FeH.js";const T=async()=>{const s=await fetch(g("/api/stats"));if(!s.ok)throw new Error(`Failed to fetch stats: ${s.status}`);return s.json()},C=()=>{const[s,a]=r.useState(null),[t,o]=r.useState(!1);return r.useEffect(()=>{T().then(a).catch(()=>o(!0))},[]),{stats:s,loadFailed:t}},w=["open",...v],A={...f,open:"Open"},L={allowed:"success",warning:"warning",rejected:"error"},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})]}),M=[{key:"five_hour",label:"5-hour limit"},{key:"seven_day",label:"Weekly"}],E=({capacity:s})=>{const[a,t]=r.useState(()=>Date.now());return r.useEffect(()=>{const o=setInterval(()=>t(Date.now()),3e4);return()=>clearInterval(o)},[]),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(p,{size:"small",variant:L[y(s.snapshot.status)],children:s.snapshot.status}),s.snapshot.overage&&e.jsx(p,{size:"small",variant:"warning",children:"overage"})]}),M.map(({key:o,label:h})=>{var u;const i=(u=s.snapshot.unifiedWindows)==null?void 0:u[o];if(!i)return null;const j=i.resetsAt===void 0?"":` · resets ${new Date(S(i.resetsAt)).toLocaleTimeString()}`;return e.jsx(n,{label:h,value:`${Math.round(i.utilization*100)}% used${j}`},o)}),e.jsxs("span",{className:"text-2xs opacity-50",children:["as of last agent run,"," ",m(Math.max(0,a-Date.parse(s.capturedAt)))," ago"]})]})})},k=({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})]}),_=s=>`${Math.round(s*100)}%`,D=({comments:s})=>e.jsxs(l,{title:"Comment ratio",children:[e.jsx(x,{value:s.ratio*100,color:d.accentSlate}),e.jsx(n,{label:"Ratio",value:_(s.ratio)}),e.jsx(n,{label:"Comment lines",value:s.commentLines}),e.jsx(n,{label:"Source lines",value:s.sourceLines})]}),P=({entitiesByStatus:s})=>e.jsxs(l,{title:"Entities by status",children:[w.filter(a=>s[a]).map(a=>e.jsx(n,{label:A[a],value:s[a]},a)),Object.keys(s).length===0&&e.jsx("p",{className:"opacity-50 m-0",children:"No entities yet."})]}),I=({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:m(s)})}),R=({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:`${c(a.inputTokens)} in · ${c(a.outputTokens)} out`}),e.jsx("span",{className:"text-2xs opacity-50 truncate",children:a.planTitle})]},a.planId))]}),$=({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})]}),F=({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(x,{value:t.count,max:a,color:d.accentAmber,height:4})]},t.week))]})},U=({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(x,{value:s,color:d.accentGreen}),e.jsx(n,{label:"Line coverage",value:`${Math.round(s)}%`})]})}),W=({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(x,{value:t.agentMinutes,max:a,color:d.accentAmber,height:4}),e.jsxs("span",{className:"text-2xs opacity-50",children:[c(t.inputTokens)," in · ",c(t.outputTokens)," out"]})]},t.week))]})},z=({stats:s})=>e.jsxs(e.Fragment,{children:[e.jsxs("div",{className:"flex flex-wrap items-start gap-2.5",children:[e.jsx(D,{comments:s.comments}),e.jsx(U,{testCoveragePct:s.testCoveragePct}),e.jsx(k,{sourceLines:s.comments.sourceLines,testLines:s.testLines}),e.jsx(P,{entitiesByStatus:s.entitiesByStatus}),e.jsx($,{openQuestions:s.openQuestions,decisions:s.decisions}),e.jsx(F,{tasksPerWeek:s.tasksPerWeek}),e.jsx(W,{usagePerWeek:s.usagePerWeek}),e.jsx(I,{medianPhaseDurationMs:s.medianPhaseDurationMs}),e.jsx(R,{mostExpensiveIdeas:s.mostExpensiveIdeas}),e.jsx(E,{capacity:s.capacity})]}),e.jsxs("p",{className:"opacity-[0.4] text-2xs mt-6",children:["Generated ",new Date(s.generatedAt).toLocaleString()]})]}),B=()=>{const{stats:s,loadFailed:a}=C();return e.jsxs("div",{children:[e.jsx(b,{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."}),a&&e.jsx("p",{className:"opacity-50",children:"Couldn't load stats."}),!a&&!s&&e.jsx("p",{className:"opacity-50",children:"Loading…"}),s&&e.jsx(z,{stats:s})]})};export{B as StatsPage};
2
+ //# sourceMappingURL=index-DN8Ti3Ov.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index-DYGnWiFG.js","sources":["../../../src/app/services/content/stats-api.ts","../../../src/app/features/stats/hooks/use-stats-page.ts","../../../src/app/features/stats/constants.ts","../../../src/app/features/stats/views/stat-card.tsx","../../../src/app/features/stats/views/claude-capacity-card.tsx","../../../src/app/features/stats/views/codebase-size-card.tsx","../../../src/app/features/stats/helpers/format-stats.ts","../../../src/app/features/stats/views/comment-ratio-card.tsx","../../../src/app/features/stats/views/entities-by-status-card.tsx","../../../src/app/features/stats/views/median-phase-duration-card.tsx","../../../src/app/features/stats/views/most-expensive-ideas-card.tsx","../../../src/app/features/stats/views/notes-card.tsx","../../../src/app/features/stats/views/tasks-per-week-card.tsx","../../../src/app/features/stats/views/test-coverage-card.tsx","../../../src/app/features/stats/views/usage-per-week-card.tsx","../../../src/app/features/stats/views/stats-grid.tsx","../../../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 { fetchStats } from '@/app/services/content';\nimport type { ProjectStats } from '@/types/index';\nimport { useEffect, useState } from 'react';\n\nexport const useStatsPage = () => {\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 { stats, loadFailed };\n};\n","import { STATUS_LABEL } from '@/app/features/plans/constants';\nimport { type EntityStatus, PLAN_STATUSES } from '@/types/index';\n\nexport const ENTITY_STATUS_ORDER: EntityStatus[] = ['open', ...PLAN_STATUSES];\n\nexport const ENTITY_STATUS_LABEL: Record<EntityStatus, string> = { ...STATUS_LABEL, open: 'Open' };\n\nexport const CAPACITY_STAMP = {\n allowed: 'success',\n warning: 'warning',\n rejected: 'error',\n} as const;\n","import { Card } from '@dendelion/paper-ui';\nimport type { ReactNode } from 'react';\n\nexport interface StatCardProps {\n title: string;\n children: ReactNode;\n}\n\nexport const StatCard = ({ title, children }: StatCardProps) => (\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\nexport interface StatRowProps {\n label: string;\n value: ReactNode;\n}\n\nexport const StatRow = ({ label, value }: StatRowProps) => (\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","import { formatDuration } from '@/core/phase-run';\nimport { capacityLevel, resetsAtMs } from '@/core/rate-limit';\nimport type { ProjectStats } from '@/types/index';\nimport type { RateLimitWindowKey } from '@/types/index';\nimport { Stamp } from '@dendelion/paper-ui';\nimport { useEffect, useState } from 'react';\nimport { CAPACITY_STAMP } from '../constants';\nimport { StatCard, StatRow } from './stat-card';\n\nconst WINDOW_ROWS: { key: RateLimitWindowKey; label: string }[] = [\n { key: 'five_hour', label: '5-hour limit' },\n { key: 'seven_day', label: 'Weekly' },\n];\n\nexport interface ClaudeCapacityCardProps {\n capacity: ProjectStats['capacity'];\n}\n\nexport const ClaudeCapacityCard = ({ capacity }: ClaudeCapacityCardProps) => {\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 {WINDOW_ROWS.map(({ key, label }) => {\n const window = capacity.snapshot.unifiedWindows?.[key];\n if (!window) return null;\n const resets =\n window.resetsAt === undefined\n ? ''\n : ` · resets ${new Date(resetsAtMs(window.resetsAt)).toLocaleTimeString()}`;\n return (\n <StatRow\n key={key}\n label={label}\n value={`${Math.round(window.utilization * 100)}% used${resets}`}\n />\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","import { StatCard, StatRow } from './stat-card';\n\nexport interface CodebaseSizeCardProps {\n sourceLines: number;\n testLines: number;\n}\n\nexport const CodebaseSizeCard = ({ sourceLines, testLines }: CodebaseSizeCardProps) => (\n <StatCard title=\"Codebase size\">\n <StatRow label=\"Source lines\" value={sourceLines} />\n <StatRow label=\"Test lines\" value={testLines} />\n </StatCard>\n);\n","export const pct = (value: number) => `${Math.round(value * 100)}%`;\n","import { color } from '@/app/styles/tokens';\nimport type { ProjectStats } from '@/types/index';\nimport { Progress } from '@dendelion/paper-ui';\nimport { pct } from '../helpers';\nimport { StatCard, StatRow } from './stat-card';\n\nexport interface CommentRatioCardProps {\n comments: ProjectStats['comments'];\n}\n\nexport const CommentRatioCard = ({ comments }: CommentRatioCardProps) => (\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","import type { ProjectStats } from '@/types/index';\nimport { ENTITY_STATUS_LABEL, ENTITY_STATUS_ORDER } from '../constants';\nimport { StatCard, StatRow } from './stat-card';\n\nexport interface EntitiesByStatusCardProps {\n entitiesByStatus: ProjectStats['entitiesByStatus'];\n}\n\nexport const EntitiesByStatusCard = ({ entitiesByStatus }: EntitiesByStatusCardProps) => (\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","import { formatDuration } from '@/core/phase-run';\nimport { StatCard, StatRow } from './stat-card';\n\nexport interface MedianPhaseDurationCardProps {\n medianPhaseDurationMs: number | null;\n}\n\nexport const MedianPhaseDurationCard = ({\n medianPhaseDurationMs,\n}: MedianPhaseDurationCardProps) => (\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","import { formatTokens } from '@/core/phase-run';\nimport type { ProjectStats } from '@/types/index';\nimport { StatCard, StatRow } from './stat-card';\n\nexport interface MostExpensiveIdeasCardProps {\n mostExpensiveIdeas: ProjectStats['mostExpensiveIdeas'];\n}\n\nexport const MostExpensiveIdeasCard = ({ mostExpensiveIdeas }: MostExpensiveIdeasCardProps) => (\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","import { StatCard, StatRow } from './stat-card';\n\nexport interface NotesCardProps {\n openQuestions: number;\n decisions: number;\n}\n\nexport const NotesCard = ({ openQuestions, decisions }: NotesCardProps) => (\n <StatCard title=\"Notes\">\n <StatRow label=\"Open questions\" value={openQuestions} />\n <StatRow label=\"Decisions\" value={decisions} />\n </StatCard>\n);\n","import { color } from '@/app/styles/tokens';\nimport type { ProjectStats } from '@/types/index';\nimport { Progress } from '@dendelion/paper-ui';\nimport { StatCard, StatRow } from './stat-card';\n\nexport interface TasksPerWeekCardProps {\n tasksPerWeek: ProjectStats['tasksPerWeek'];\n}\n\nexport const TasksPerWeekCard = ({ tasksPerWeek }: TasksPerWeekCardProps) => {\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","import { color } from '@/app/styles/tokens';\nimport { Progress } from '@dendelion/paper-ui';\nimport { StatCard, StatRow } from './stat-card';\n\nexport interface TestCoverageCardProps {\n testCoveragePct: number | null;\n}\n\nexport const TestCoverageCard = ({ testCoveragePct }: TestCoverageCardProps) => (\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","import { color } from '@/app/styles/tokens';\nimport { formatTokens } from '@/core/phase-run';\nimport type { ProjectStats } from '@/types/index';\nimport { Progress } from '@dendelion/paper-ui';\nimport { StatCard, StatRow } from './stat-card';\n\nexport interface UsagePerWeekCardProps {\n usagePerWeek: ProjectStats['usagePerWeek'];\n}\n\nexport const UsagePerWeekCard = ({ usagePerWeek }: UsagePerWeekCardProps) => {\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","import type { ProjectStats } from '@/types/index';\nimport { ClaudeCapacityCard } from './claude-capacity-card';\nimport { CodebaseSizeCard } from './codebase-size-card';\nimport { CommentRatioCard } from './comment-ratio-card';\nimport { EntitiesByStatusCard } from './entities-by-status-card';\nimport { MedianPhaseDurationCard } from './median-phase-duration-card';\nimport { MostExpensiveIdeasCard } from './most-expensive-ideas-card';\nimport { NotesCard } from './notes-card';\nimport { TasksPerWeekCard } from './tasks-per-week-card';\nimport { TestCoverageCard } from './test-coverage-card';\nimport { UsagePerWeekCard } from './usage-per-week-card';\n\nexport interface StatsGridProps {\n stats: ProjectStats;\n}\n\nexport const StatsGrid = ({ stats }: StatsGridProps) => (\n <>\n <div className=\"flex flex-wrap items-start gap-2.5\">\n <CommentRatioCard comments={stats.comments} />\n <TestCoverageCard testCoveragePct={stats.testCoveragePct} />\n <CodebaseSizeCard sourceLines={stats.comments.sourceLines} testLines={stats.testLines} />\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","import { PageTitle } from '@/app/components/page-title';\nimport { useStatsPage } from './hooks';\nimport { StatsGrid } from './views';\n\nexport const StatsPage = () => {\n const { stats, loadFailed } = useStatsPage();\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 && <StatsGrid stats={stats} />}\n </div>\n );\n};\n"],"names":["fetchStats","res","apiUrl","useStatsPage","stats","setStats","useState","loadFailed","setLoadFailed","useEffect","ENTITY_STATUS_ORDER","PLAN_STATUSES","ENTITY_STATUS_LABEL","STATUS_LABEL","CAPACITY_STAMP","StatCard","title","children","jsx","Card","jsxs","StatRow","label","value","WINDOW_ROWS","ClaudeCapacityCard","capacity","now","setNow","id","Fragment","Stamp","capacityLevel","key","window","_a","resets","resetsAtMs","formatDuration","CodebaseSizeCard","sourceLines","testLines","pct","CommentRatioCard","comments","Progress","color","EntitiesByStatusCard","entitiesByStatus","status","MedianPhaseDurationCard","medianPhaseDurationMs","MostExpensiveIdeasCard","mostExpensiveIdeas","idea","formatTokens","NotesCard","openQuestions","decisions","TasksPerWeekCard","tasksPerWeek","max","w","week","TestCoverageCard","testCoveragePct","UsagePerWeekCard","usagePerWeek","StatsGrid","StatsPage","PageTitle"],"mappings":"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"}
1
+ {"version":3,"file":"index-DN8Ti3Ov.js","sources":["../../../src/app/services/content/stats-api.ts","../../../src/app/features/stats/hooks/use-stats-page.ts","../../../src/app/features/stats/constants.ts","../../../src/app/features/stats/views/stat-card.tsx","../../../src/app/features/stats/views/claude-capacity-card.tsx","../../../src/app/features/stats/views/codebase-size-card.tsx","../../../src/app/features/stats/helpers/format-stats.ts","../../../src/app/features/stats/views/comment-ratio-card.tsx","../../../src/app/features/stats/views/entities-by-status-card.tsx","../../../src/app/features/stats/views/median-phase-duration-card.tsx","../../../src/app/features/stats/views/most-expensive-ideas-card.tsx","../../../src/app/features/stats/views/notes-card.tsx","../../../src/app/features/stats/views/tasks-per-week-card.tsx","../../../src/app/features/stats/views/test-coverage-card.tsx","../../../src/app/features/stats/views/usage-per-week-card.tsx","../../../src/app/features/stats/views/stats-grid.tsx","../../../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 { fetchStats } from '@/app/services/content';\nimport type { ProjectStats } from '@/types/index';\nimport { useEffect, useState } from 'react';\n\nexport const useStatsPage = () => {\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 { stats, loadFailed };\n};\n","import { STATUS_LABEL } from '@/app/features/plans/constants';\nimport { type EntityStatus, PLAN_STATUSES } from '@/types/index';\n\nexport const ENTITY_STATUS_ORDER: EntityStatus[] = ['open', ...PLAN_STATUSES];\n\nexport const ENTITY_STATUS_LABEL: Record<EntityStatus, string> = { ...STATUS_LABEL, open: 'Open' };\n\nexport const CAPACITY_STAMP = {\n allowed: 'success',\n warning: 'warning',\n rejected: 'error',\n} as const;\n","import { Card } from '@dendelion/paper-ui';\nimport type { ReactNode } from 'react';\n\nexport interface StatCardProps {\n title: string;\n children: ReactNode;\n}\n\nexport const StatCard = ({ title, children }: StatCardProps) => (\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\nexport interface StatRowProps {\n label: string;\n value: ReactNode;\n}\n\nexport const StatRow = ({ label, value }: StatRowProps) => (\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","import { formatDuration } from '@/core/phase-run';\nimport { capacityLevel, resetsAtMs } from '@/core/rate-limit';\nimport type { ProjectStats } from '@/types/index';\nimport type { RateLimitWindowKey } from '@/types/index';\nimport { Stamp } from '@dendelion/paper-ui';\nimport { useEffect, useState } from 'react';\nimport { CAPACITY_STAMP } from '../constants';\nimport { StatCard, StatRow } from './stat-card';\n\nconst WINDOW_ROWS: { key: RateLimitWindowKey; label: string }[] = [\n { key: 'five_hour', label: '5-hour limit' },\n { key: 'seven_day', label: 'Weekly' },\n];\n\nexport interface ClaudeCapacityCardProps {\n capacity: ProjectStats['capacity'];\n}\n\nexport const ClaudeCapacityCard = ({ capacity }: ClaudeCapacityCardProps) => {\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 {WINDOW_ROWS.map(({ key, label }) => {\n const window = capacity.snapshot.unifiedWindows?.[key];\n if (!window) return null;\n const resets =\n window.resetsAt === undefined\n ? ''\n : ` · resets ${new Date(resetsAtMs(window.resetsAt)).toLocaleTimeString()}`;\n return (\n <StatRow\n key={key}\n label={label}\n value={`${Math.round(window.utilization * 100)}% used${resets}`}\n />\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","import { StatCard, StatRow } from './stat-card';\n\nexport interface CodebaseSizeCardProps {\n sourceLines: number;\n testLines: number;\n}\n\nexport const CodebaseSizeCard = ({ sourceLines, testLines }: CodebaseSizeCardProps) => (\n <StatCard title=\"Codebase size\">\n <StatRow label=\"Source lines\" value={sourceLines} />\n <StatRow label=\"Test lines\" value={testLines} />\n </StatCard>\n);\n","export const pct = (value: number) => `${Math.round(value * 100)}%`;\n","import { color } from '@/app/styles/tokens';\nimport type { ProjectStats } from '@/types/index';\nimport { Progress } from '@dendelion/paper-ui';\nimport { pct } from '../helpers';\nimport { StatCard, StatRow } from './stat-card';\n\nexport interface CommentRatioCardProps {\n comments: ProjectStats['comments'];\n}\n\nexport const CommentRatioCard = ({ comments }: CommentRatioCardProps) => (\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","import type { ProjectStats } from '@/types/index';\nimport { ENTITY_STATUS_LABEL, ENTITY_STATUS_ORDER } from '../constants';\nimport { StatCard, StatRow } from './stat-card';\n\nexport interface EntitiesByStatusCardProps {\n entitiesByStatus: ProjectStats['entitiesByStatus'];\n}\n\nexport const EntitiesByStatusCard = ({ entitiesByStatus }: EntitiesByStatusCardProps) => (\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","import { formatDuration } from '@/core/phase-run';\nimport { StatCard, StatRow } from './stat-card';\n\nexport interface MedianPhaseDurationCardProps {\n medianPhaseDurationMs: number | null;\n}\n\nexport const MedianPhaseDurationCard = ({\n medianPhaseDurationMs,\n}: MedianPhaseDurationCardProps) => (\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","import { formatTokens } from '@/core/phase-run';\nimport type { ProjectStats } from '@/types/index';\nimport { StatCard, StatRow } from './stat-card';\n\nexport interface MostExpensiveIdeasCardProps {\n mostExpensiveIdeas: ProjectStats['mostExpensiveIdeas'];\n}\n\nexport const MostExpensiveIdeasCard = ({ mostExpensiveIdeas }: MostExpensiveIdeasCardProps) => (\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","import { StatCard, StatRow } from './stat-card';\n\nexport interface NotesCardProps {\n openQuestions: number;\n decisions: number;\n}\n\nexport const NotesCard = ({ openQuestions, decisions }: NotesCardProps) => (\n <StatCard title=\"Notes\">\n <StatRow label=\"Open questions\" value={openQuestions} />\n <StatRow label=\"Decisions\" value={decisions} />\n </StatCard>\n);\n","import { color } from '@/app/styles/tokens';\nimport type { ProjectStats } from '@/types/index';\nimport { Progress } from '@dendelion/paper-ui';\nimport { StatCard, StatRow } from './stat-card';\n\nexport interface TasksPerWeekCardProps {\n tasksPerWeek: ProjectStats['tasksPerWeek'];\n}\n\nexport const TasksPerWeekCard = ({ tasksPerWeek }: TasksPerWeekCardProps) => {\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","import { color } from '@/app/styles/tokens';\nimport { Progress } from '@dendelion/paper-ui';\nimport { StatCard, StatRow } from './stat-card';\n\nexport interface TestCoverageCardProps {\n testCoveragePct: number | null;\n}\n\nexport const TestCoverageCard = ({ testCoveragePct }: TestCoverageCardProps) => (\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","import { color } from '@/app/styles/tokens';\nimport { formatTokens } from '@/core/phase-run';\nimport type { ProjectStats } from '@/types/index';\nimport { Progress } from '@dendelion/paper-ui';\nimport { StatCard, StatRow } from './stat-card';\n\nexport interface UsagePerWeekCardProps {\n usagePerWeek: ProjectStats['usagePerWeek'];\n}\n\nexport const UsagePerWeekCard = ({ usagePerWeek }: UsagePerWeekCardProps) => {\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","import type { ProjectStats } from '@/types/index';\nimport { ClaudeCapacityCard } from './claude-capacity-card';\nimport { CodebaseSizeCard } from './codebase-size-card';\nimport { CommentRatioCard } from './comment-ratio-card';\nimport { EntitiesByStatusCard } from './entities-by-status-card';\nimport { MedianPhaseDurationCard } from './median-phase-duration-card';\nimport { MostExpensiveIdeasCard } from './most-expensive-ideas-card';\nimport { NotesCard } from './notes-card';\nimport { TasksPerWeekCard } from './tasks-per-week-card';\nimport { TestCoverageCard } from './test-coverage-card';\nimport { UsagePerWeekCard } from './usage-per-week-card';\n\nexport interface StatsGridProps {\n stats: ProjectStats;\n}\n\nexport const StatsGrid = ({ stats }: StatsGridProps) => (\n <>\n <div className=\"flex flex-wrap items-start gap-2.5\">\n <CommentRatioCard comments={stats.comments} />\n <TestCoverageCard testCoveragePct={stats.testCoveragePct} />\n <CodebaseSizeCard sourceLines={stats.comments.sourceLines} testLines={stats.testLines} />\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","import { PageTitle } from '@/app/components/page-title';\nimport { useStatsPage } from './hooks';\nimport { StatsGrid } from './views';\n\nexport const StatsPage = () => {\n const { stats, loadFailed } = useStatsPage();\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 && <StatsGrid stats={stats} />}\n </div>\n );\n};\n"],"names":["fetchStats","res","apiUrl","useStatsPage","stats","setStats","useState","loadFailed","setLoadFailed","useEffect","ENTITY_STATUS_ORDER","PLAN_STATUSES","ENTITY_STATUS_LABEL","STATUS_LABEL","CAPACITY_STAMP","StatCard","title","children","jsx","Card","jsxs","StatRow","label","value","WINDOW_ROWS","ClaudeCapacityCard","capacity","now","setNow","id","Fragment","Stamp","capacityLevel","key","window","_a","resets","resetsAtMs","formatDuration","CodebaseSizeCard","sourceLines","testLines","pct","CommentRatioCard","comments","Progress","color","EntitiesByStatusCard","entitiesByStatus","status","MedianPhaseDurationCard","medianPhaseDurationMs","MostExpensiveIdeasCard","mostExpensiveIdeas","idea","formatTokens","NotesCard","openQuestions","decisions","TasksPerWeekCard","tasksPerWeek","max","w","week","TestCoverageCard","testCoveragePct","UsagePerWeekCard","usagePerWeek","StatsGrid","StatsPage","PageTitle"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- import{as as E,r as g,u as c,a9 as L,Q as $,at as R,j as r,al as C,ap as S,J as b,G as w,P as K}from"./main-Ceiva0Al.js";const D=(n,e)=>{const t=n.entityId?e.find(s=>s.id===n.entityId):void 0;return t?E(t)?"Promote to fix":`Add to ${t.id}'s fixes`:"Promote to idea"},M=n=>{if(!n)return"";const e=Math.floor((Date.now()-Date.parse(n))/864e5);return Number.isFinite(e)?e<=0?"today":e===1?"1 day ago":`${e} days ago`:""},F="9999-12-31T23:59:59.999Z",z=n=>[...n].sort((e,t)=>(e.occurredAt??F).localeCompare(t.occurredAt??F)),j=(n,e)=>`${n}:${e}`;function O(n){return n.filter(e=>e.outcome==="error").map(e=>{const t=e.planId??e.id;return{id:j("agent-run",t),sourceKind:"agent-run",sourceKey:t,entityId:e.planId,entityTitle:e.planTitle,title:`${e.planTitle} — ${e.taskKind} failed`,reason:e.reason??"The run ended in error with no reason recorded.",occurredAt:e.endedAt,thread:[],status:"open"}})}function q(n){return n.filter(e=>e.status==="fail").map(e=>({id:j("check",e.name),sourceKind:"check",sourceKey:e.name,title:`"${e.name}" check is failing`,reason:`The command was \`${e.cmd}\`.`,output:e.output||void 0,occurredAt:e.lastRun??void 0,thread:[],status:"open"}))}function J(n){return n.filter(e=>{var t;return((t=e.pr)==null?void 0:t.reviewDecision)==="changes-requested"}).map(e=>{const t=e.pr,s=`${t.number}:${t.headSha??"unknown"}`;return{id:j("pr-review",s),sourceKind:"pr-review",sourceKey:s,entityId:e.id,entityTitle:e.title,title:`${e.title} — PR review requested changes`,reason:`PR #${t.number} has unresolved change requests.`,thread:[],status:"open"}})}function B(n,e){return n.map(t=>{var l;const s=e.find(i=>i.issueSource===t.id);if(s!=null&&s.id)return{...t,promotedFixId:s.id};const d=t.entityId?e.find(i=>i.id===t.entityId):void 0;return((l=d==null?void 0:d.fixes)==null?void 0:l.some(i=>i.source==="issue"&&i.text===t.title))&&(d!=null&&d.id)?{...t,promotedFixId:d.id}:t}).filter(t=>{const s=t.promotedFixId&&e.find(d=>d.id===t.promotedFixId);return!(s&&s.status==="done")})}function G(n,e){return e.filter(t=>t.issueId===n.id).sort((t,s)=>t.endedAt.localeCompare(s.endedAt)).map(t=>({kind:"log",date:t.endedAt.slice(0,10),from:"agent",text:t.outcome==="done"?"Ran a fix agent — finished.":t.outcome==="error"?`Ran a fix agent — failed: ${t.reason??"no reason recorded"}`:"Ran a fix agent — superseded by a newer run."}))}const Q=()=>{var k;const[n,e]=g.useState(null),[t,s]=g.useState(null),d=c(o=>o.taskLog),m=c(o=>o.taskLogLoading),l=c(o=>o.loadTaskLog),i=c(o=>o.plans),p=c(o=>o.ideaEntries),f=c(o=>o.agentStatus),a=c(o=>o.launchIssueFix),h=c(o=>o.loadPlans),x=c(o=>o.loadIdeas),{checks:u}=L(),I=$();g.useEffect(()=>{l()},[l]);const P=g.useMemo(()=>((i==null?void 0:i.entries)??[]).filter(o=>o.id!=null),[i]),y=g.useMemo(()=>[...P,...p].filter(o=>o.id!=null),[P,p]),T=g.useMemo(()=>{const o=z([...O(d),...q(u),...J(y)]);return B(o,y).map(v=>({...v,thread:G(v,d)}))},[d,u,y]),A=async o=>{s(o.id);try{await R(o),await Promise.all([h(),x()])}finally{s(null)}},N=(k=f.find(o=>o.taskKind==="issue-fix"&&o.status!=="done"&&o.status!=="error"&&o.status!=="superseded"))==null?void 0:k.issueId;return{issues:T,entities:y,taskLogLoading:m,expandedId:n,setExpandedId:e,openEntity:I,fixingIssueId:N,launchIssueFix:a,promotingId:t,handlePromote:A}},U=({issue:n,expanded:e,onToggle:t,onOpen:s,fixing:d,fixDisabled:m,onFix:l,promoteLabel:i,promoting:p,promoteDisabled:f,onPromote:a,onOpenPromoted:h})=>{const x=async()=>{try{await l()}catch(I){alert(I.message)}},u=async()=>{try{await a()}catch(I){alert(I.message)}};return r.jsxs("div",{className:"border-b border-black/10 last:border-b-0",children:[r.jsxs("div",{className:"flex items-center gap-3 py-2.5",children:[s&&r.jsx("button",{type:"button",onClick:s,"aria-label":`Open ${n.entityTitle}`,className:"bg-transparent border-none p-0 cursor-pointer",children:r.jsx(C,{id:n.entityId})}),r.jsx("button",{type:"button",onClick:t,"aria-expanded":e,className:"flex-1 min-w-0 truncate text-left bg-transparent border-none p-0 cursor-pointer",children:n.title}),r.jsx("span",{className:"opacity-50 text-xs whitespace-nowrap",children:M(n.occurredAt)})]}),e&&r.jsxs("div",{className:"flex flex-col gap-3 pb-4",children:[r.jsx("p",{children:n.reason}),n.output&&r.jsx("pre",{className:"max-h-48 overflow-auto whitespace-pre-wrap text-left font-mono text-2xs opacity-70",children:n.output}),n.thread.length>0&&r.jsx(S,{messages:n.thread,undo:null,undoing:!1,onUndo:()=>{}}),r.jsx("div",{className:"flex items-center gap-3",children:n.promotedFixId?r.jsxs(b,{size:"small",onClick:h,children:["View ",n.promotedFixId]}):r.jsxs(r.Fragment,{children:[r.jsx(b,{size:"small",onClick:x,disabled:m,children:d?"Fixing…":"Fix it here"}),d&&r.jsx(w,{size:"small",label:"Agent fixing…"}),r.jsx(b,{size:"small",onClick:u,disabled:f,children:p?"Promoting…":i}),p&&r.jsx(w,{size:"small",label:"Promoting…"})]})})]})]})},Z=()=>{const{issues:n,entities:e,taskLogLoading:t,expandedId:s,setExpandedId:d,openEntity:m,fixingIssueId:l,launchIssueFix:i,promotingId:p,handlePromote:f}=Q();return r.jsxs("div",{children:[r.jsx(K,{children:"Issues"}),r.jsx("p",{className:"opacity-50 mb-6",children:"Every failing agent run, red check, and PR review, oldest first."}),t&&n.length===0&&r.jsx("p",{className:"opacity-50",children:"Loading…"}),!t&&n.length===0&&r.jsx("p",{className:"opacity-50",children:"Nothing broken right now."}),n.length>0&&r.jsx("div",{className:"flex flex-col",children:n.map(a=>{var x;const h=a.promotedFixId?((x=e.find(u=>u.id===a.promotedFixId))==null?void 0:x.title)??a.promotedFixId:void 0;return r.jsx(U,{issue:a,expanded:s===a.id,onToggle:()=>d(u=>u===a.id?null:a.id),onOpen:a.entityId&&a.entityTitle?()=>m(a.entityId,a.entityTitle):void 0,fixing:l===a.id,fixDisabled:l!==void 0,onFix:()=>i(a.id,a.title,a.reason,a.output),promoteLabel:D(a,e),promoting:p===a.id,promoteDisabled:p!==null,onPromote:()=>f(a),onOpenPromoted:a.promotedFixId?()=>m(a.promotedFixId,h):void 0},a.id)})})]})};export{Z as IssuesPage};
2
- //# sourceMappingURL=index-VoRTCwB6.js.map
1
+ import{as as E,r as g,u as c,a9 as L,Q as $,at as R,j as r,al as C,ap as S,J as b,G as w,P as K}from"./main-DCzV1FeH.js";const D=(n,e)=>{const t=n.entityId?e.find(s=>s.id===n.entityId):void 0;return t?E(t)?"Promote to fix":`Add to ${t.id}'s fixes`:"Promote to idea"},M=n=>{if(!n)return"";const e=Math.floor((Date.now()-Date.parse(n))/864e5);return Number.isFinite(e)?e<=0?"today":e===1?"1 day ago":`${e} days ago`:""},F="9999-12-31T23:59:59.999Z",z=n=>[...n].sort((e,t)=>(e.occurredAt??F).localeCompare(t.occurredAt??F)),j=(n,e)=>`${n}:${e}`;function O(n){return n.filter(e=>e.outcome==="error").map(e=>{const t=e.planId??e.id;return{id:j("agent-run",t),sourceKind:"agent-run",sourceKey:t,entityId:e.planId,entityTitle:e.planTitle,title:`${e.planTitle} — ${e.taskKind} failed`,reason:e.reason??"The run ended in error with no reason recorded.",occurredAt:e.endedAt,thread:[],status:"open"}})}function q(n){return n.filter(e=>e.status==="fail").map(e=>({id:j("check",e.name),sourceKind:"check",sourceKey:e.name,title:`"${e.name}" check is failing`,reason:`The command was \`${e.cmd}\`.`,output:e.output||void 0,occurredAt:e.lastRun??void 0,thread:[],status:"open"}))}function J(n){return n.filter(e=>{var t;return((t=e.pr)==null?void 0:t.reviewDecision)==="changes-requested"}).map(e=>{const t=e.pr,s=`${t.number}:${t.headSha??"unknown"}`;return{id:j("pr-review",s),sourceKind:"pr-review",sourceKey:s,entityId:e.id,entityTitle:e.title,title:`${e.title} — PR review requested changes`,reason:`PR #${t.number} has unresolved change requests.`,thread:[],status:"open"}})}function B(n,e){return n.map(t=>{var l;const s=e.find(i=>i.issueSource===t.id);if(s!=null&&s.id)return{...t,promotedFixId:s.id};const d=t.entityId?e.find(i=>i.id===t.entityId):void 0;return((l=d==null?void 0:d.fixes)==null?void 0:l.some(i=>i.source==="issue"&&i.text===t.title))&&(d!=null&&d.id)?{...t,promotedFixId:d.id}:t}).filter(t=>{const s=t.promotedFixId&&e.find(d=>d.id===t.promotedFixId);return!(s&&s.status==="done")})}function G(n,e){return e.filter(t=>t.issueId===n.id).sort((t,s)=>t.endedAt.localeCompare(s.endedAt)).map(t=>({kind:"log",date:t.endedAt.slice(0,10),from:"agent",text:t.outcome==="done"?"Ran a fix agent — finished.":t.outcome==="error"?`Ran a fix agent — failed: ${t.reason??"no reason recorded"}`:"Ran a fix agent — superseded by a newer run."}))}const Q=()=>{var k;const[n,e]=g.useState(null),[t,s]=g.useState(null),d=c(o=>o.taskLog),m=c(o=>o.taskLogLoading),l=c(o=>o.loadTaskLog),i=c(o=>o.plans),p=c(o=>o.ideaEntries),f=c(o=>o.agentStatus),a=c(o=>o.launchIssueFix),h=c(o=>o.loadPlans),x=c(o=>o.loadIdeas),{checks:u}=L(),I=$();g.useEffect(()=>{l()},[l]);const P=g.useMemo(()=>((i==null?void 0:i.entries)??[]).filter(o=>o.id!=null),[i]),y=g.useMemo(()=>[...P,...p].filter(o=>o.id!=null),[P,p]),T=g.useMemo(()=>{const o=z([...O(d),...q(u),...J(y)]);return B(o,y).map(v=>({...v,thread:G(v,d)}))},[d,u,y]),A=async o=>{s(o.id);try{await R(o),await Promise.all([h(),x()])}finally{s(null)}},N=(k=f.find(o=>o.taskKind==="issue-fix"&&o.status!=="done"&&o.status!=="error"&&o.status!=="superseded"))==null?void 0:k.issueId;return{issues:T,entities:y,taskLogLoading:m,expandedId:n,setExpandedId:e,openEntity:I,fixingIssueId:N,launchIssueFix:a,promotingId:t,handlePromote:A}},U=({issue:n,expanded:e,onToggle:t,onOpen:s,fixing:d,fixDisabled:m,onFix:l,promoteLabel:i,promoting:p,promoteDisabled:f,onPromote:a,onOpenPromoted:h})=>{const x=async()=>{try{await l()}catch(I){alert(I.message)}},u=async()=>{try{await a()}catch(I){alert(I.message)}};return r.jsxs("div",{className:"border-b border-black/10 last:border-b-0",children:[r.jsxs("div",{className:"flex items-center gap-3 py-2.5",children:[s&&r.jsx("button",{type:"button",onClick:s,"aria-label":`Open ${n.entityTitle}`,className:"bg-transparent border-none p-0 cursor-pointer",children:r.jsx(C,{id:n.entityId})}),r.jsx("button",{type:"button",onClick:t,"aria-expanded":e,className:"flex-1 min-w-0 truncate text-left bg-transparent border-none p-0 cursor-pointer",children:n.title}),r.jsx("span",{className:"opacity-50 text-xs whitespace-nowrap",children:M(n.occurredAt)})]}),e&&r.jsxs("div",{className:"flex flex-col gap-3 pb-4",children:[r.jsx("p",{children:n.reason}),n.output&&r.jsx("pre",{className:"max-h-48 overflow-auto whitespace-pre-wrap text-left font-mono text-2xs opacity-70",children:n.output}),n.thread.length>0&&r.jsx(S,{messages:n.thread,undo:null,undoing:!1,onUndo:()=>{}}),r.jsx("div",{className:"flex items-center gap-3",children:n.promotedFixId?r.jsxs(b,{size:"small",onClick:h,children:["View ",n.promotedFixId]}):r.jsxs(r.Fragment,{children:[r.jsx(b,{size:"small",onClick:x,disabled:m,children:d?"Fixing…":"Fix it here"}),d&&r.jsx(w,{size:"small",label:"Agent fixing…"}),r.jsx(b,{size:"small",onClick:u,disabled:f,children:p?"Promoting…":i}),p&&r.jsx(w,{size:"small",label:"Promoting…"})]})})]})]})},Z=()=>{const{issues:n,entities:e,taskLogLoading:t,expandedId:s,setExpandedId:d,openEntity:m,fixingIssueId:l,launchIssueFix:i,promotingId:p,handlePromote:f}=Q();return r.jsxs("div",{children:[r.jsx(K,{children:"Issues"}),r.jsx("p",{className:"opacity-50 mb-6",children:"Every failing agent run, red check, and PR review, oldest first."}),t&&n.length===0&&r.jsx("p",{className:"opacity-50",children:"Loading…"}),!t&&n.length===0&&r.jsx("p",{className:"opacity-50",children:"Nothing broken right now."}),n.length>0&&r.jsx("div",{className:"flex flex-col",children:n.map(a=>{var x;const h=a.promotedFixId?((x=e.find(u=>u.id===a.promotedFixId))==null?void 0:x.title)??a.promotedFixId:void 0;return r.jsx(U,{issue:a,expanded:s===a.id,onToggle:()=>d(u=>u===a.id?null:a.id),onOpen:a.entityId&&a.entityTitle?()=>m(a.entityId,a.entityTitle):void 0,fixing:l===a.id,fixDisabled:l!==void 0,onFix:()=>i(a.id,a.title,a.reason,a.output),promoteLabel:D(a,e),promoting:p===a.id,promoteDisabled:p!==null,onPromote:()=>f(a),onOpenPromoted:a.promotedFixId?()=>m(a.promotedFixId,h):void 0},a.id)})})]})};export{Z as IssuesPage};
2
+ //# sourceMappingURL=index-DX3JeGXT.js.map