coxpit 5.0.2 → 5.0.3

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 (2) hide show
  1. package/package.json +1 -1
  2. package/src/board.ts +40 -21
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "coxpit",
3
- "version": "5.0.2",
3
+ "version": "5.0.3",
4
4
  "description": "Self-hosted cockpit for running a fleet of AI coding agents across your own machines — parallel worktree runs, live board, compare & merge, web terminal, design capture.",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/src/board.ts CHANGED
@@ -185,9 +185,24 @@ export const BOARD_HTML = /* html */ `<!doctype html>
185
185
  .sheet-h .sp{flex:1}.sheet-h .x{color:var(--muted);cursor:pointer;background:none;border:none;padding:2px}
186
186
  .sheet #taskForm{display:flex;flex-direction:column;gap:13px;min-height:0}
187
187
  .sheet-body{display:flex;flex-direction:column;gap:8px;overflow-y:auto;min-height:0;max-height:60vh;padding:1px}
188
- .sheet-f{display:flex;align-items:center;gap:8px;padding-top:12px;border-top:1px solid var(--line)}
189
- .sheet-f #modeSeg{flex:1}
190
- .sheet-f #runFleetBtn{flex:0 0 auto}
188
+ .sheet-f{display:flex;flex-wrap:wrap;align-items:center;gap:10px;padding-top:12px;border-top:1px solid var(--line)}
189
+ .sheet-f #modeSeg{flex:0 0 auto}
190
+ .sheet-f #taskCountStep{flex:0 0 auto;margin-left:auto}
191
+ .sheet-f #runFleetBtn{flex:1 1 100%;justify-content:center}
192
+ /* agent-mode switch — compact toggle (dry ⇄ real), never a native checkbox/white box */
193
+ .realtog{display:inline-flex;align-items:center;gap:8px;background:#0e1118;border:1px solid var(--line);
194
+ border-radius:999px;padding:5px 13px 5px 6px;cursor:pointer;font-family:var(--sans);font-size:12px;
195
+ font-weight:600;color:var(--muted);white-space:nowrap;transition:color .15s,border-color .15s}
196
+ .realtog:hover{border-color:var(--brand)}
197
+ .realtog .realtog-knob{width:30px;height:17px;border-radius:999px;background:var(--surface2);
198
+ position:relative;flex:none;transition:background .18s}
199
+ .realtog .realtog-knob::after{content:"";position:absolute;top:2px;left:2px;width:13px;height:13px;
200
+ border-radius:50%;background:var(--faint);transition:transform .18s,background .18s}
201
+ .realtog .realtog-hint{font-family:var(--mono);font-size:10px;font-weight:400;color:var(--faint)}
202
+ .realtog[aria-checked="true"]{color:var(--brand);border-color:rgba(78,201,176,.4)}
203
+ .realtog[aria-checked="true"] .realtog-knob{background:var(--brand-dim)}
204
+ .realtog[aria-checked="true"] .realtog-knob::after{transform:translateX(13px);background:var(--brand)}
205
+ .realtog:focus-visible{outline:2px solid var(--brand);outline-offset:2px}
191
206
  /* progressive Options reveal (rarely-used Task fields) */
192
207
  .opt{border:1px solid var(--line);border-radius:var(--r-ctl);background:#0e1118;overflow:hidden}
193
208
  .opt-h{width:100%;display:flex;align-items:center;gap:8px;background:transparent;border:none;cursor:pointer;
@@ -253,7 +268,7 @@ export const BOARD_HTML = /* html */ `<!doctype html>
253
268
  .seg{display:flex;gap:3px;padding:3px;border:1px solid var(--line);border-radius:var(--r-ctl);background:#0e1118}
254
269
  /* slim single-row option — matches the type/provider seg height; the risky hint rides inline, subtle */
255
270
  .seg-opt{flex:1;display:inline-flex;flex-direction:row;align-items:center;justify-content:center;gap:5px;
256
- padding:6px 8px;border:none;min-width:0;
271
+ padding:6px 8px;border:none;min-width:0;white-space:nowrap;line-height:1.2;
257
272
  background:transparent;color:var(--muted);font-size:12px;font-weight:600;cursor:pointer;
258
273
  border-radius:calc(var(--r-ctl) - 3px);transition:background .15s,color .15s}
259
274
  .seg-opt:hover{color:var(--ink)}
@@ -884,10 +899,11 @@ ${ICON_SPRITE}
884
899
  </div>
885
900
  </div>
886
901
  <div class="sheet-f" id="sheetFooter">
887
- <div class="seg" id="modeSeg" role="group" aria-label="agent mode">
888
- <button type="button" class="seg-opt" data-real="0">Dry run</button>
889
- <button type="button" class="seg-opt" data-real="1">Real agent<span class="seg-hint">spends credits</span></button>
890
- </div>
902
+ <button type="button" id="modeSeg" class="realtog" role="switch" aria-checked="false" title="Dry run rehearses the full pipeline with zero credits. Turn on to run the real agent CLI.">
903
+ <span class="realtog-knob"></span>
904
+ <span class="realtog-lbl" id="modeRealLbl">Dry run</span>
905
+ <span class="realtog-hint" id="modeRealHint">rehearsal</span>
906
+ </button>
891
907
  <input type="checkbox" id="taskReal" hidden />
892
908
  <div class="stepper" id="taskCountStep" title="agents — 1 for a job, N to explore variants">
893
909
  <button type="button" class="stp" id="cntDown" aria-label="fewer agents"><svg class="ic"><use href="#i-minus"/></svg></button>
@@ -1764,19 +1780,19 @@ function renderRail(){
1764
1780
  const cnt = railCounts();
1765
1781
  let html = '';
1766
1782
  if (repos.length){
1767
- html += '<button type="button" class="repo'+(selectedRepo==null?' on':'')+'" data-repo="all">'
1768
- + ic('layers')+'<span class="nm">All repositories</span></button>';
1783
+ html += '<div class="repo'+(selectedRepo==null?' on':'')+'" role="button" tabindex="0" data-repo="all">'
1784
+ + ic('layers')+'<span class="nm">All repositories</span></div>';
1769
1785
  for (const r of repos){
1770
1786
  const c = cnt.get(r.id) || { active:0, attn:false };
1771
1787
  const on = selectedRepo===r.id;
1772
- html += '<button type="button" class="repo'+(on?' on':'')+'" data-repo="'+r.id+'" title="'+escA(r.path||r.name)+'">'
1788
+ html += '<div class="repo'+(on?' on':'')+'" role="button" tabindex="0" data-repo="'+r.id+'" title="'+escA(r.path||r.name)+'">'
1773
1789
  + ic('folder')+'<span class="nm">'+esc(r.name)+'</span>'
1774
1790
  + (c.attn ? '<span class="attn" title="a run needs a hand"></span>' : '')
1775
1791
  + (c.active>0 ? '<span class="cnt">'+c.active+'</span>' : '')
1776
1792
  + '<span class="rowmenu">'
1777
1793
  + '<button type="button" class="rmbtn" data-rbranch="'+r.id+'" title="base branch">'+ic('branch')+'</button>'
1778
1794
  + '<button type="button" class="rmbtn" data-rremove="'+r.id+'" title="remove">'+ic('x')+'</button>'
1779
- + '</span></button>';
1795
+ + '</span></div>';
1780
1796
  }
1781
1797
  } else {
1782
1798
  // empty state — 첫 repo 추가 CTA
@@ -1804,6 +1820,12 @@ $('repoList').addEventListener('click', (e)=>{
1804
1820
  const row = e.target.closest('.repo[data-repo]'); if(!row) return;
1805
1821
  setScope(row.dataset.repo==='all' ? null : Number(row.dataset.repo));
1806
1822
  });
1823
+ $('repoList').addEventListener('keydown', (e)=>{
1824
+ if (e.key!=='Enter' && e.key!==' ') return;
1825
+ const row = e.target.closest('.repo[data-repo]'); if(!row) return;
1826
+ e.preventDefault();
1827
+ setScope(row.dataset.repo==='all' ? null : Number(row.dataset.repo));
1828
+ });
1807
1829
  $('captures').addEventListener('click', async (e)=>{
1808
1830
  const b = e.target.closest('button[data-delcap]'); if(!b) return;
1809
1831
  await fetch('/api/design/'+b.dataset.delcap,{method:'DELETE'});
@@ -3034,7 +3056,7 @@ function setV(tab){
3034
3056
  $('panelBench').hidden = tab!=='bench';
3035
3057
  $('panelBench').style.display = tab==='bench' ? 'flex' : 'none';
3036
3058
  // footer adapts: Task=Dry/Real·count·Run fleet · Goal=Dry/Real·Plan & run · Workbench=Open workbench only
3037
- $('modeSeg').style.display = tab==='bench' ? 'none' : 'flex'; // workbench 는 에이전트 없음
3059
+ $('modeSeg').style.display = tab==='bench' ? 'none' : 'inline-flex'; // workbench 는 에이전트 없음
3038
3060
  $('taskCountStep').style.display = tab==='task' ? 'flex' : 'none'; // count 는 Task 만(플래너·워크벤치는 1)
3039
3061
  $('runFleetBtn').innerHTML = ic(L_ICON[tab]) + ' ' + L_LABEL[tab];
3040
3062
  }
@@ -3178,18 +3200,15 @@ $('cntDown').addEventListener('click', ()=>stepCount(-1));
3178
3200
  $('cntUp').addEventListener('click', ()=>stepCount(1));
3179
3201
  $('taskCount').addEventListener('change', ()=>stepCount(0)); // clamp manual typing
3180
3202
 
3181
- /* ── agent mode segmented control (mirrors hidden #taskReal) ── */
3182
- const segOpts = Array.from(document.querySelectorAll('#modeSeg .seg-opt'));
3203
+ /* ── agent mode switch (compact toggle mirroring hidden #taskReal) ── */
3183
3204
  function setMode(real, persist){
3184
3205
  $('taskReal').checked = real;
3185
- for (const b of segOpts){
3186
- const on = (b.dataset.real === '1') === real;
3187
- b.classList.toggle('on', on);
3188
- b.setAttribute('aria-pressed', on ? 'true' : 'false');
3189
- }
3206
+ $('modeSeg').setAttribute('aria-checked', real ? 'true' : 'false');
3207
+ $('modeRealLbl').textContent = real ? 'Real agent' : 'Dry run';
3208
+ $('modeRealHint').textContent = real ? 'spends credits' : 'rehearsal';
3190
3209
  if (persist) try { localStorage.setItem('coxpit.real', real ? '1' : '0'); } catch {}
3191
3210
  }
3192
- for (const b of segOpts) b.addEventListener('click', ()=>setMode(b.dataset.real === '1', true));
3211
+ $('modeSeg').addEventListener('click', ()=>setMode(!$('taskReal').checked, true));
3193
3212
  let savedMode = null;
3194
3213
  try { savedMode = localStorage.getItem('coxpit.real'); } catch {}
3195
3214
  setMode(savedMode === '1', false);