coxpit 5.0.0 → 5.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "coxpit",
3
- "version": "5.0.0",
3
+ "version": "5.0.2",
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
@@ -58,7 +58,10 @@ export const BOARD_HTML = /* html */ `<!doctype html>
58
58
  .mdot.on{background:var(--s-done)}
59
59
 
60
60
  /* ── layout ─────────────────────────────── */
61
- .layout{display:grid;grid-template-columns:318px 1fr;min-height:calc(100vh - 55px)}
61
+ /* fixed viewport height so the rail stays put and each column scrolls on its own
62
+ (the board grew tall with many cards and dragged the rail's + New below the fold) */
63
+ .layout{display:grid;grid-template-columns:318px 1fr;height:calc(100vh - 55px);overflow:hidden}
64
+ .layout > aside,.layout > main{min-height:0}
62
65
  aside{border-right:1px solid var(--line);padding:20px 18px;background:var(--surface);
63
66
  display:flex;flex-direction:column;gap:26px}
64
67
  main{padding:20px;overflow:auto}
@@ -76,9 +79,29 @@ export const BOARD_HTML = /* html */ `<!doctype html>
76
79
  input:focus,select:focus,textarea:focus{border-color:rgba(78,201,176,.55);outline:none}
77
80
  input::placeholder,textarea::placeholder{color:var(--faint)}
78
81
  textarea{resize:vertical;min-height:56px;line-height:1.5}
82
+ /* no native browser chrome — any bare <select> strips the OS arrow and draws a token chevron
83
+ (dressed selects hide entirely; this covers the undressed fallback like #archRepo) */
84
+ select{appearance:none;-webkit-appearance:none;-moz-appearance:none;padding-right:26px;
85
+ background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%238792a2' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
86
+ background-repeat:no-repeat;background-position:right 8px center;background-size:13px}
87
+ select option{background:#12151c;color:var(--ink)}
88
+ /* kill the native number spinner — a token stepper (below) replaces it */
89
+ input[type=number]{appearance:none;-webkit-appearance:none;-moz-appearance:textfield}
90
+ input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{-webkit-appearance:none;margin:0}
79
91
  .row{display:flex;gap:8px}
80
92
  .row>*{flex:1}
81
93
  .row .narrow{flex:0 0 64px}
94
+ /* count stepper — token − N + (btn-ghost feel, dark, no native spinner) */
95
+ .stepper{display:flex;align-items:center;gap:0;flex:0 0 auto;border:1px solid var(--line);
96
+ border-radius:var(--r-ctl);background:#0e1118;overflow:hidden}
97
+ .stepper .stp{display:inline-flex;align-items:center;justify-content:center;width:26px;height:32px;
98
+ background:transparent;border:none;color:var(--muted);cursor:pointer;font-size:15px;line-height:1;
99
+ transition:background .15s,color .15s}
100
+ .stepper .stp:hover{background:var(--surface2);color:var(--ink)}
101
+ .stepper .stp .ic{width:14px;height:14px}
102
+ .stepper input.narrow{flex:0 0 30px;width:30px;text-align:center;border:none;border-radius:0;
103
+ background:transparent;padding:8px 0;font-variant-numeric:tabular-nums}
104
+ .stepper input.narrow:focus{border:none}
82
105
  .check{display:flex;align-items:center;gap:8px;font-size:12px;color:var(--muted);cursor:pointer;user-select:none}
83
106
  .check input{width:auto;accent-color:var(--brand)}
84
107
 
@@ -115,9 +138,10 @@ export const BOARD_HTML = /* html */ `<!doctype html>
115
138
  #repoList .repo .rowmenu{display:none;align-items:center;gap:2px;flex:none}
116
139
  #repoList .repo:hover .rowmenu{display:flex}
117
140
  #repoList .repo:hover .cnt{display:none}
118
- #repoList .repo .rmbtn{background:none;border:none;color:var(--faint);cursor:pointer;padding:2px;
119
- display:inline-flex;border-radius:5px}
120
- #repoList .repo .rmbtn:hover{color:var(--ink);background:var(--line)}
141
+ /* rail repo-row actions — icon-ghost, never white: transparent bg · muted icon · hover ink + surface */
142
+ #repoList .repo .rmbtn{background:transparent;border:1px solid transparent;color:var(--muted);cursor:pointer;
143
+ padding:3px;display:inline-flex;border-radius:6px;transition:color .15s,background .15s,border-color .15s}
144
+ #repoList .repo .rmbtn:hover{color:var(--ink);background:var(--surface2)}
121
145
  #repoList .repo .rmbtn .ic{width:13px;height:13px;color:inherit}
122
146
  .navi{display:flex;align-items:center;gap:9px;padding:7px 10px;border-radius:8px;color:var(--muted);
123
147
  cursor:pointer;font-size:13px;border:none;background:transparent;width:100%;text-align:left;font-family:var(--sans)}
@@ -200,7 +224,8 @@ export const BOARD_HTML = /* html */ `<!doctype html>
200
224
  .dd-btn:hover{border-color:var(--line-hi)}
201
225
  .dd.open .dd-btn{border-color:rgba(78,201,176,.55)}
202
226
  .dd-lbl{flex:1;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}
203
- .dd-car{color:var(--faint);font-size:10px;transition:transform .15s}
227
+ .dd-car{color:var(--faint);display:inline-flex;align-items:center;transition:transform .15s}
228
+ .dd-car .ic{width:13px;height:13px}
204
229
  .dd.open .dd-car{transform:rotate(180deg)}
205
230
  .dd-panel{position:absolute;top:calc(100% + 5px);left:0;right:0;z-index:40;background:var(--surface2);
206
231
  border:1px solid var(--line-hi);border-radius:9px;box-shadow:var(--shadow);max-height:230px;
@@ -211,14 +236,34 @@ export const BOARD_HTML = /* html */ `<!doctype html>
211
236
  .dd-opt:hover{background:var(--surface);color:var(--ink)}
212
237
  .dd-opt.on{color:var(--brand)}
213
238
  .dd-opt.on::before{content:'✓ ';font-size:10px}
239
+ /* model combo — free-text input + a .dd-style recent-models menu (no native datalist/white popup) */
240
+ .mcombo{position:relative;display:flex;align-items:stretch;gap:0}
241
+ .mcombo input{border-top-right-radius:0;border-bottom-right-radius:0;border-right:none}
242
+ .mcombo .mc-tog{flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;width:30px;
243
+ background:#0e1118;color:var(--faint);border:1px solid var(--line);border-left:none;
244
+ border-top-right-radius:var(--r-ctl);border-bottom-right-radius:var(--r-ctl);cursor:pointer;
245
+ transition:color .15s,border-color .15s}
246
+ .mcombo .mc-tog:hover{color:var(--ink)}
247
+ .mcombo .mc-tog .ic{width:13px;height:13px;transition:transform .15s}
248
+ .mcombo.open input,.mcombo.open .mc-tog{border-color:rgba(78,201,176,.55)}
249
+ .mcombo.open .mc-tog .ic{transform:rotate(180deg)}
250
+ .mcombo .dd-panel{top:calc(100% + 5px)}
251
+ .mcombo.open .dd-panel{display:block}
252
+ .mc-empty{padding:7px 10px;font-family:var(--mono);font-size:11px;color:var(--faint)}
214
253
  .seg{display:flex;gap:3px;padding:3px;border:1px solid var(--line);border-radius:var(--r-ctl);background:#0e1118}
215
- .seg-opt{flex:1;display:flex;flex-direction:column;align-items:center;gap:1px;padding:6px 8px;border:none;
254
+ /* slim single-row option — matches the type/provider seg height; the risky hint rides inline, subtle */
255
+ .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;
216
257
  background:transparent;color:var(--muted);font-size:12px;font-weight:600;cursor:pointer;
217
258
  border-radius:calc(var(--r-ctl) - 3px);transition:background .15s,color .15s}
218
259
  .seg-opt:hover{color:var(--ink)}
219
260
  .seg-opt.on{background:var(--surface2);color:var(--ink)}
220
261
  .seg-opt[data-real="1"].on{background:var(--brand);color:var(--brand-ink)}
221
- .seg-hint{font-family:var(--mono);font-size:9.5px;font-weight:500;text-transform:uppercase;letter-spacing:.06em;opacity:.7}
262
+ .seg-hint{font-family:var(--mono);font-size:9px;font-weight:500;text-transform:uppercase;letter-spacing:.05em;
263
+ opacity:.6;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
264
+ /* the credit hint only reads once the risky option is chosen — keeps the resting seg clean & slim */
265
+ .seg-opt[data-real="1"]:not(.on) .seg-hint{display:none}
266
+ .seg-opt[data-real="1"].on .seg-hint{opacity:.75}
222
267
  /* ── deliverable 계약 칩(Task compose) ── */
223
268
  .ochips{display:flex;flex-wrap:wrap;gap:5px}
224
269
  .ochip{background:transparent;color:var(--muted);border:1px solid var(--line);cursor:pointer;
@@ -669,7 +714,7 @@ export const BOARD_HTML = /* html */ `<!doctype html>
669
714
  .menu-btn{display:none;font-size:15px;padding:5px 10px}
670
715
  .scrim{position:fixed;inset:0;top:54px;background:rgba(5,7,10,.5);z-index:29;display:none}
671
716
  @media (max-width:860px){
672
- .layout{grid-template-columns:1fr;min-height:calc(100dvh - 55px)}
717
+ .layout{grid-template-columns:1fr;height:calc(100dvh - 55px)}
673
718
  /* 사이드바 = 오프캔버스 드로어 — 플릿이 첫 화면, 런처는 ☰ 뒤에 */
674
719
  .menu-btn{display:inline-flex}
675
720
  aside{position:fixed;top:54px;bottom:0;left:0;width:min(86vw,340px);max-width:100vw;z-index:30;
@@ -808,8 +853,11 @@ ${ICON_SPRITE}
808
853
  </button>
809
854
  <div class="opt-b" id="taskOptBody" hidden>
810
855
  <p class="flabel">model · optional</p>
811
- <input id="taskModel" placeholder="CLI default" list="modelHist" autocomplete="off" />
812
- <datalist id="modelHist"></datalist>
856
+ <div class="mcombo" id="modelCombo">
857
+ <input id="taskModel" placeholder="CLI default" autocomplete="off" spellcheck="false" />
858
+ <button type="button" class="mc-tog" id="modelTog" aria-label="recent models" title="recent models"><svg class="ic"><use href="#i-chevron-down"/></svg></button>
859
+ <div class="dd-panel" id="modelMenu"></div>
860
+ </div>
813
861
  <p class="flabel">design capture · optional</p>
814
862
  <select id="taskCapture"><option value="">no design capture</option></select>
815
863
  <p class="flabel">deliverables · optional (contract)</p>
@@ -841,7 +889,11 @@ ${ICON_SPRITE}
841
889
  <button type="button" class="seg-opt" data-real="1">Real agent<span class="seg-hint">spends credits</span></button>
842
890
  </div>
843
891
  <input type="checkbox" id="taskReal" hidden />
844
- <input id="taskCount" class="narrow" type="number" min="1" max="8" value="1" title="agents — 1 for a job, N to explore variants" />
892
+ <div class="stepper" id="taskCountStep" title="agents — 1 for a job, N to explore variants">
893
+ <button type="button" class="stp" id="cntDown" aria-label="fewer agents"><svg class="ic"><use href="#i-minus"/></svg></button>
894
+ <input id="taskCount" class="narrow" type="number" min="1" max="8" value="1" inputmode="numeric" aria-label="agent count" />
895
+ <button type="button" class="stp" id="cntUp" aria-label="more agents"><svg class="ic"><use href="#i-plus"/></svg></button>
896
+ </div>
845
897
  <button class="btn" type="submit" id="runFleetBtn"><svg class="ic"><use href="#i-play"/></svg> Run fleet</button>
846
898
  </div>
847
899
  </form>
@@ -1151,7 +1203,7 @@ function dressSelect(id){
1151
1203
  const sel = $(id); if(!sel || sel.dataset.dd) return;
1152
1204
  sel.dataset.dd = '1';
1153
1205
  const dd = document.createElement('div'); dd.className = 'dd';
1154
- dd.innerHTML = '<button type="button" class="dd-btn"><span class="dd-lbl"></span><span class="dd-car">▾</span></button><div class="dd-panel"></div>';
1206
+ dd.innerHTML = '<button type="button" class="dd-btn"><span class="dd-lbl"></span><span class="dd-car"><svg class="ic"><use href="#i-chevron-down"/></svg></span></button><div class="dd-panel"></div>';
1155
1207
  sel.parentNode.insertBefore(dd, sel);
1156
1208
  sel.style.display = 'none';
1157
1209
  dd.querySelector('.dd-btn').addEventListener('click', (e)=>{
@@ -2983,7 +3035,7 @@ function setV(tab){
2983
3035
  $('panelBench').style.display = tab==='bench' ? 'flex' : 'none';
2984
3036
  // footer adapts: Task=Dry/Real·count·Run fleet · Goal=Dry/Real·Plan & run · Workbench=Open workbench only
2985
3037
  $('modeSeg').style.display = tab==='bench' ? 'none' : 'flex'; // workbench 는 에이전트 없음
2986
- $('taskCount').style.display = tab==='task' ? '' : 'none'; // count 는 Task 만(플래너·워크벤치는 1)
3038
+ $('taskCountStep').style.display = tab==='task' ? 'flex' : 'none'; // count 는 Task 만(플래너·워크벤치는 1)
2987
3039
  $('runFleetBtn').innerHTML = ic(L_ICON[tab]) + ' ' + L_LABEL[tab];
2988
3040
  }
2989
3041
  document.querySelectorAll('#launchTabs .seg-opt').forEach(b=>{
@@ -3091,12 +3143,40 @@ function rememberModel(m){
3091
3143
  }catch{}
3092
3144
  paintModelHist();
3093
3145
  }
3146
+ function modelHistList(){
3147
+ try{ return JSON.parse(localStorage.getItem('coxpit.models')||'[]'); }catch{ return []; }
3148
+ }
3094
3149
  function paintModelHist(){
3095
- let h = [];
3096
- try{ h = JSON.parse(localStorage.getItem('coxpit.models')||'[]'); }catch{}
3097
- $('modelHist').innerHTML = h.map(m=>'<option value="'+escA(m)+'"></option>').join('');
3150
+ const h = modelHistList();
3151
+ const cur = $('taskModel').value.trim();
3152
+ $('modelMenu').innerHTML = h.length
3153
+ ? h.map(m=>'<div class="dd-opt'+(m===cur?' on':'')+'" data-m="'+escA(m)+'">'+esc(m)+'</div>').join('')
3154
+ : '<div class="mc-empty">no recent models</div>';
3098
3155
  }
3099
3156
  paintModelHist();
3157
+ // model combo — free text stays; the caret opens a .dd-style recent-models menu (no native datalist)
3158
+ function closeModelMenu(){ $('modelCombo').classList.remove('open'); }
3159
+ $('modelTog').addEventListener('click', (e)=>{
3160
+ e.stopPropagation();
3161
+ const open = $('modelCombo').classList.contains('open');
3162
+ closeDropdowns(); closeModelMenu();
3163
+ if (!open){ paintModelHist(); $('modelCombo').classList.add('open'); }
3164
+ });
3165
+ $('modelMenu').addEventListener('click', (e)=>{
3166
+ const o = e.target.closest('.dd-opt'); if(!o) return;
3167
+ $('taskModel').value = o.dataset.m; closeModelMenu(); $('taskModel').focus();
3168
+ });
3169
+ document.addEventListener('click', closeModelMenu);
3170
+
3171
+ /* ── count stepper — token −/+ mirror the number input, clamped 1..8 ── */
3172
+ function stepCount(delta){
3173
+ const el = $('taskCount');
3174
+ const n = Math.max(1, Math.min(8, (Number(el.value)||1) + delta));
3175
+ el.value = String(n);
3176
+ }
3177
+ $('cntDown').addEventListener('click', ()=>stepCount(-1));
3178
+ $('cntUp').addEventListener('click', ()=>stepCount(1));
3179
+ $('taskCount').addEventListener('change', ()=>stepCount(0)); // clamp manual typing
3100
3180
 
3101
3181
  /* ── agent mode segmented control (mirrors hidden #taskReal) ── */
3102
3182
  const segOpts = Array.from(document.querySelectorAll('#modeSeg .seg-opt'));
package/src/icons.ts CHANGED
@@ -19,7 +19,9 @@ export const ICON_SPRITE = `<svg style="position:absolute;width:0;height:0" aria
19
19
  <symbol id="i-image" viewBox="0 0 24 24"><rect width="18" height="18" x="3" y="3" rx="2" ry="2"/><circle cx="9" cy="9" r="2"/><path d="m21 15-3.09-3.09a2 2 0 0 0-2.82 0L6 21"/></symbol>
20
20
  <symbol id="i-message" viewBox="0 0 24 24"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></symbol>
21
21
  <symbol id="i-chevron" viewBox="0 0 24 24"><path d="m9 18 6-6-6-6"/></symbol>
22
+ <symbol id="i-chevron-down" viewBox="0 0 24 24"><path d="m6 9 6 6 6-6"/></symbol>
22
23
  <symbol id="i-plus" viewBox="0 0 24 24"><path d="M5 12h14"/><path d="M12 5v14"/></symbol>
24
+ <symbol id="i-minus" viewBox="0 0 24 24"><path d="M5 12h14"/></symbol>
23
25
  <symbol id="i-pencil" viewBox="0 0 24 24"><path d="M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z"/></symbol>
24
26
  <symbol id="i-trash" viewBox="0 0 24 24"><path d="M3 6h18"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"/><path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></symbol>
25
27
  <symbol id="i-recycle" viewBox="0 0 24 24"><path d="M7 19H4.815a1.83 1.83 0 0 1-1.57-.881 1.785 1.785 0 0 1-.004-1.784L7.196 9.5"/><path d="M11 19h8.203a1.83 1.83 0 0 0 1.556-.89 1.784 1.784 0 0 0 0-1.775l-1.226-2.12"/><path d="m14 16-3 3 3 3"/><path d="M8.293 13.596 4.875 9.5 1.5 13.5"/><path d="m9 12 3-3-3-3"/><path d="M13.5 3.5 17 9.5l-3.5 2"/></symbol>