diff --git a/frontend/assign-and-start.js b/frontend/assign-and-start.js index 6c02cfc..b220e95 100644 --- a/frontend/assign-and-start.js +++ b/frontend/assign-and-start.js @@ -1,21 +1,24 @@ function createAssignAndStart({ available, claim, start, recover, announce }) { let request = null; - function run(item) { + function run(item, { alreadyOwned = false } = {}) { if (request) return request; if (!available()) { announce('Today is full—remove an item before assigning this issue.'); return Promise.resolve('full'); } request = Promise.resolve() - .then(() => claim(item)) + .then(() => alreadyOwned ? item : claim(item)) .then(confirmed => { const outcome = start(confirmed); if (outcome === 'started') { - announce('Assigned, added to Today, and ready to work.'); + announce(alreadyOwned ? 'Added to Today and ready to work.' : + 'Assigned, added to Today, and ready to work.'); return outcome; } - announce('Assigned to you, but Today could not start. The issue is open so you can recover.'); + announce(alreadyOwned ? + 'Today could not start. The issue is open so you can recover.' : + 'Assigned to you, but Today could not start. The issue is open so you can recover.'); recover(confirmed); return 'recovery'; }) diff --git a/frontend/dashboard.css b/frontend/dashboard.css index 89878ca..12013ea 100644 --- a/frontend/dashboard.css +++ b/frontend/dashboard.css @@ -274,6 +274,7 @@ textarea { resize: vertical; min-height: 120px; } .search-preview-header button, .search-preview-actions button, .search-preview-actions a { min-height:44px; } .search-preview-body { margin:0; white-space:pre-wrap; overflow-wrap:anywhere; } .search-preview-actions { position:sticky; bottom:0; display:grid; gap:8px; padding:10px 0; padding-bottom:calc(10px + env(safe-area-inset-bottom)); background:#0b1526; } +.search-preview-primary-actions { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:8px; } .search-preview-actions a { display:flex; align-items:center; justify-content:center; border:1px solid #60a5fa; border-radius:10px; font-weight:700; } .markdown-content { min-width:0; max-width:100%; overflow-wrap:anywhere; white-space:normal; } .markdown-content > :first-child { margin-top:0; } diff --git a/frontend/dashboard.js b/frontend/dashboard.js index 83dfe83..9d27128 100644 --- a/frontend/dashboard.js +++ b/frontend/dashboard.js @@ -2644,6 +2644,7 @@ const sheet = qs('#search-preview'); const status = qs('#search-preview-status'); const claimButton = qs('#claim-search-result'); + const startButton = qs('#start-search-result'); if (state.status === 'closed') { sheet.classList.remove('open'); return; @@ -2651,6 +2652,8 @@ sheet.classList.add('open'); claimButton.hidden = true; claimButton.disabled = false; + startButton.hidden = true; + startButton.disabled = false; if (state.status === 'loading') { searchPreviewDetail = null; qs('#search-preview-key').textContent = state.item.repository + ' #' + state.item.number; @@ -2680,6 +2683,10 @@ claimButton.hidden = !(detail.claimable || (detail.assigned_to_me && detail.kind === 'issue')); claimButton.textContent = detail.assigned_to_me ? 'Open in My Work' : 'Assign to me'; claimButton.disabled = state.status === 'claiming'; + startButton.hidden = !(detail.kind === 'issue' && detail.state === 'open' && + (detail.claimable || detail.assigned_to_me)); + startButton.textContent = detail.assigned_to_me ? 'Start in Today' : 'Assign & start'; + startButton.disabled = state.status === 'claiming'; status.textContent = state.status === 'claiming' ? 'Assigning this issue to you…' : (state.status === 'claimed' ? 'Assignment confirmed. Opening My Work…' : (detail.claimable ? 'This issue is open and unassigned.' : @@ -2694,6 +2701,26 @@ ), onState: renderSearchPreview, }); + const searchAssignAndStart = createAssignAndStart({ + available: createAndStart.available, + claim: detail => searchPreview.claim(detail), + start: confirmed => { + const claimed = acceptClaimedIssue(confirmed); + taskOverlayHistory.leave(); + refreshMyWorkView(); + return createAndStart.complete(claimed); + }, + recover: confirmed => { + const claimed = acceptClaimedIssue(confirmed); + taskOverlayHistory.leave(); + refreshMyWorkView(); + openRoutedWork(claimed, qs('#open-palette')); + }, + announce: message => { + qs('#search-preview-status').textContent = message; + qs('#my-work-action-status').textContent = message; + }, + }); const mobileSearchViewport = createMobileSearchViewport({ palette: qs('#cmd-palette'), results: qs('#cmd-results'), @@ -2902,6 +2929,16 @@ qs('#search-preview-status').textContent = error.message + ' Retry assignment.'; } }); + qs('#start-search-result').addEventListener('click', async () => { + const detail = searchPreviewDetail; + if (!detail || detail.kind !== 'issue' || detail.state !== 'open' || + (!detail.claimable && !detail.assigned_to_me)) return; + try { + await searchAssignAndStart.run(detail, { alreadyOwned: detail.assigned_to_me }); + } catch (error) { + qs('#search-preview-status').textContent = error.message + ' Retry assignment and start.'; + } + }); qs('#close-whiteboard').addEventListener('click', () => closeModal('whiteboard-modal')); qs('#find-work').addEventListener('click', openFindWorkSheet); qs('#close-find-work').addEventListener('click', closeFindWorkSheet); diff --git a/frontend/index.html b/frontend/index.html index ada1710..0f6412c 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -223,7 +223,10 @@