diff --git a/client.js b/client.js index 132b1eb..10b9246 100644 --- a/client.js +++ b/client.js @@ -60,14 +60,14 @@ window.__ModuleLoader__.load({ const move = (id, target) => { setAssign((v) => ({...v, [id]: target})); setTab(target); }; const startEdit = (row) => setEditing({ id: row.id, value: drafts[row.id] ?? rowText(row) }); const cancelEdit = () => { if (editing) setDrafts((v) => ({...v, [editing.id]: editing.value})); setEditing(null); }; - const saveEdit = async () => { if (!editing || !editing.value.trim()) return; setBusy(editing.id); try { await updateQueue(editing.id, {kind:'edit', content:[{type:'text', text:editing.value}]}); setDrafts((v) => { const n={...v}; delete n[editing.id]; return n; }); setEditing(null); } catch { notify('error', 'Queue edit failed'); } finally { setBusy(null); } }; - const action = async (id, value) => { setBusy(id); try { await updateQueue(id, value); setDrafts((v) => { const n={...v}; delete n[id]; return n; }); } catch { notify('error', 'Queue action failed'); } finally { setBusy(null); } }; + const saveEdit = async () => { if (!editing || !editing.value.trim()) return false; const id = editing.id; const value = editing.value; setBusy(id); try { await Promise.resolve(updateQueue(id, {kind:'edit', content:[{type:'text', text:value}]})); setDrafts((v) => { const n={...v}; delete n[id]; return n; }); setEditing(null); return true; } catch { notify('error', 'Queue edit failed'); return false; } finally { setBusy(null); } }; + const action = async (id, value) => { setBusy(id); try { await Promise.resolve(updateQueue(id, value)); setDrafts((v) => { const n={...v}; delete n[id]; return n; }); } catch { notify('error', 'Queue action failed'); } finally { setBusy(null); } }; const drop = (targetId) => { if (!drag || drag.id === targetId) return; const ids = sorted.map((r) => r.id).filter((id) => id !== drag.id); const at = ids.indexOf(targetId); ids.splice(Math.max(0, at), 0, drag.id); setOrder(ids); if (drag.list !== tab) move(drag.id, tab); setDrag(null); }; if (!rows.length) return null; const Item = ({ row }) => { const isEdit = editing?.id === row.id; const draft = drafts[row.id]; return h('li', { className: `dqs-row${isEdit?' editing':''}${drag?.over===row.id?' over':''}`, draggable: !isEdit, onDragStart: () => setDrag({id:row.id,list:tab}), onDragOver: (e) => { e.preventDefault(); setDrag((v) => v && ({...v, over:row.id})); }, onDrop: () => drop(row.id), onDragEnd: () => setDrag(null) }, h('span',{className:'dqs-grip','aria-hidden':true},'⋮⋮'), isEdit ? h('textarea',{className:'dqs-editor',autoFocus:true,value:editing.value,onChange:(e)=>setEditing({...editing,value:e.target.value}),onKeyDown:(e)=>{if(e.key==='Escape')cancelEdit();if(e.key==='Enter'&&!e.shiftKey){e.preventDefault();saveEdit();}}}) : h('span',{className:'dqs-preview'}, draft || rowText(row)), draft && !isEdit && h('small',{className:'dqs-draft'},'draft'), h('span',{className:'dqs-actions'}, isEdit ? [h('button',{key:'save',className:'dqs-action',disabled:!!busy,onClick:saveEdit},icon('IconCheckOutlineRegular','✓')),h('button',{key:'cancel',className:'dqs-action',disabled:!!busy,onClick:cancelEdit},icon('IconCloseOutlineRegular','×'))] : [h('button',{key:'edit',className:'dqs-action',disabled:!!busy,onClick:()=>startEdit(row)},icon('IconEditOutlineRegular','✎')),h('button',{key:'move',className:'dqs-action',disabled:!!busy,onClick:()=>move(row.id,tab==='batch'?'sequential':'batch')},tab==='batch'?'⇢':'⇠'),h('button',{key:'steer',className:'dqs-action',disabled:!!busy||!running,onClick:()=>action(row.id,{kind:'steer'})},icon('IconSendOutlineRegular','➤')),h('button',{key:'remove',className:'dqs-action',disabled:!!busy,onClick:()=>action(row.id,{kind:'remove'})},icon('IconTrashOutlineRegular','×'))])); }; return h('div',{className:'dqs-dock','data-queue-dock':'dsh-queue-steer'},h('div',{className:'dqs-panel'},h('button',{className:'dqs-header',onClick:()=>setOpen(!open)},icon('IconQueueOutlineRegular','☷'),h('span',{className:'dqs-count'},`Queue & Steering (${rows.length})`),h('span',null,open?'⌃':'⌄')),open && h(React.Fragment,null,h('div',{className:'dqs-tabs'},h('button',{className:`dqs-tab ${tab==='batch'?'active':''}`,onClick:()=>{setTab('batch');write(`${sessionId}:tab`,'batch')}},`List 1 · all at once (${sorted.filter((r)=>listOf(r)==='batch').length})`),h('button',{className:`dqs-tab ${tab==='sequential'?'active':''}`,onClick:()=>{setTab('sequential');write(`${sessionId}:tab`,'sequential')}},`List 2 · one at a time (${sorted.filter((r)=>listOf(r)==='sequential').length})`)),visible.length?h('ul',{className:'dqs-list'},visible.map((r)=>h(Item,{key:r.id,row:r}))):h('div',{className:'dqs-empty'},'No entries in this list')))); } - return { inject: ['slots', 'conversation'], apply(ctx) { installCss(); ctx.slots.inject('settings.section', () => ctx.slots.register({name:'settings.section', id:'dsh-queue-steer', order:170, label:'Queue & Steering'}, SettingsPanel)); ctx.slots.inject('conversation.input.dock', () => ctx.slots.register({name:'conversation.input.dock', id:'queue', priority:-1, order:20, inject:(sessionId)=>({updateQueue:(id,a)=>ctx.conversation.updateQueue(id,a), notify:(level,msg)=>ctx.conversation.input.for(ctx.sessions.scope(sessionId)).notify(level,msg)})}, QueueDock)); } }; + return { inject: ['slots', 'conversation', 'sessions'], apply(ctx) { installCss(); ctx.slots.inject('settings.section', () => ctx.slots.register({name:'settings.section', id:'dsh-queue-steer', order:170, label:'Queue & Steering'}, SettingsPanel)); ctx.slots.inject('conversation.input.dock', () => ctx.slots.register({name:'conversation.input.dock', id:'queue', priority:-1, order:20, inject:(sessionId)=>{ const actx = ctx.sessions.scope(sessionId); const conversation = actx.get('conversation'); return { updateQueue:(id,a)=>conversation.updateQueue(id,a), notify:(level,msg)=>conversation.input.for(actx).notify(level,msg) }; }}, QueueDock)); } }; } }); diff --git a/package.json b/package.json index 8112f75..46b10ab 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@local/dsh-queue-steer", - "version": "0.2.0", + "version": "0.2.1", "description": "Custom queue and steering logic, settings page, /queue and /steer commands, and two-list queue widget for DeepSeek Harness.", "type": "module", "main": "index.js",