feat: work Today blockers in-app (#443)
CI / lint (pull_request) Successful in 52s
CI / build-release (pull_request) Successful in 5s
CI / release-candidate (pull_request) Has been skipped

This commit is contained in:
timmy committed 2026-08-10 02:05:11 +00:00
1 parent 60bc7cd686
commit a0aa3c0a22
11 files changed
+118 -23

No files matched your search

+1
View File
@@ -226,6 +226,7 @@ textarea { resize: vertical; min-height: 120px; }
.issue-blocker-list { display:grid; gap:8px; }
.issue-blocker { min-width:0; overflow-wrap:anywhere; display:grid; gap:4px; padding:10px; border:1px solid #92400e; border-radius:10px; color:#fef3c7; text-decoration:none; }
.issue-blocker:hover, .issue-blocker:focus-visible { border-color:#f59e0b; }
.today-readiness-blocker { width:100%; min-height:44px; text-align:left; background:#291b0c; }
.issue-planning { max-width:100%; margin-top:16px; border:1px solid #2a496e; border-radius:12px; padding:0 12px 12px; overflow-x:hidden; }
.issue-planning > summary { min-height:44px; display:flex; align-items:center; cursor:pointer; font-weight:700; }
.issue-planning-retry { min-height:44px; width:100%; }
+39 -6
View File
@@ -729,6 +729,8 @@
}
let todayReadinessTrigger = null;
let todayReadinessBlockerFocus = null;
let searchPreviewReturnKind = null;
function inspectTodayDependencies(item) {
if (offlineWorkMode) {
const login = confirmedOwnerLogin || String(offlineWorkStore.load()?.user?.login || '').trim();
@@ -746,12 +748,17 @@
}));
}
function closeTodayReadiness(navigate = true) {
function suspendTodayReadiness() {
qs('#today-readiness-sheet').hidden = true;
document.body.classList.remove('task-overlay-open');
}
function closeTodayReadiness(navigate = true) {
suspendTodayReadiness();
if (navigate) taskOverlayHistory.close();
else {
todayReadiness.cancel();
todayReadinessBlockerFocus = null;
requestAnimationFrame(() => todayReadinessTrigger?.focus());
}
}
@@ -765,18 +772,32 @@
: state.dependencies.length + (state.dependencies.length === 1 ? ' open dependency must finish first.' : ' open dependencies must finish first.');
qs('#today-readiness-item').innerHTML = '<strong>' + escapeHtml(state.target.title || 'Untitled issue') +
'</strong><div class="small">' + escapeHtml(state.target.repository + '#' + state.target.number) + '</div>';
qs('#today-readiness-blockers').innerHTML = state.dependencies.map(blocker =>
'<a class="issue-blocker" href="' + escAttr(blocker.url || '#') + '" target="_blank" rel="noopener noreferrer"><strong>' +
const blockers = qs('#today-readiness-blockers');
blockers.innerHTML = state.dependencies.map((blocker, index) =>
'<button class="issue-blocker today-readiness-blocker" type="button" data-today-blocker-index="' + index + '"><strong>' +
escapeHtml(blocker.repository + '#' + blocker.number) + ' · ' + escapeHtml(blocker.title || 'Untitled blocker') +
'</strong><span class="small">State: ' + escapeHtml(blocker.state || 'open') + '</span></a>'
'</strong><span class="small">State: ' + escapeHtml(blocker.state || 'open') + ' · Preview and work blocker</span></button>'
).join('');
blockers.querySelectorAll('[data-today-blocker-index]').forEach(button => {
button.addEventListener('click', () => {
const blocker = state.dependencies[Number(button.dataset.todayBlockerIndex)];
todayReadinessBlockerFocus = blocker?.repository + '#' + blocker?.number;
todayReadiness.previewBlocker(blocker);
});
});
qs('#today-readiness-next').hidden = !state.nextReady;
if (state.nextReady) qs('#today-readiness-next').textContent = 'Start next ready · ' +
(state.nextReady.title || state.nextReady.key || 'work item');
qs('#today-readiness-retry').hidden = !unknown;
qs('#today-readiness-sheet').hidden = false;
document.body.classList.add('task-overlay-open');
requestAnimationFrame(() => (state.nextReady ? qs('#today-readiness-next') : qs('#today-readiness-anyway')).focus());
requestAnimationFrame(() => {
const blocker = state.dependencies.findIndex(candidate =>
candidate?.repository + '#' + candidate?.number === todayReadinessBlockerFocus
);
if (blocker >= 0) blockers.querySelector('[data-today-blocker-index="' + blocker + '"]')?.focus();
else (state.nextReady ? qs('#today-readiness-next') : qs('#today-readiness-anyway')).focus();
});
}
function performTodayTransition(action, item = null) {
@@ -804,6 +825,11 @@
renderTodayReadiness(state);
taskOverlayHistory.open('today-readiness');
},
onPreview:blocker => {
searchPreviewReturnKind = 'today-readiness';
searchPreview.open({ ...blocker, kind:'issue' }).catch(() => {});
taskOverlayHistory.open('search-preview');
},
});
function runTodayTransition(action) {
@@ -2892,6 +2918,8 @@
const status = qs('#search-preview-status');
const claimButton = qs('#claim-search-result');
const startButton = qs('#start-search-result');
qs('#close-search-preview').textContent = searchPreviewReturnKind === 'today-readiness'
? 'Back to blockers' : 'Back to search';
if (state.status === 'closed') {
sheet.classList.remove('open');
return;
@@ -3003,6 +3031,7 @@
qs('#cmd-input').value = '';
} else {
mobileSearchViewport.rememberScroll();
searchPreviewReturnKind = 'search';
searchPreview.open(item.result).catch(() => {});
taskOverlayHistory.open('search-preview');
}
@@ -3062,6 +3091,7 @@
searchPreview.close();
mobileSearchViewport.close();
}
searchPreviewReturnKind = null;
}
if (previous === 'search' && kind !== 'search' && kind !== 'search-preview') {
qs('#cmd-palette').classList.remove('open');
@@ -3077,7 +3107,10 @@
addPlanPreviewOverride = false;
}
if (previous === 'plan-today' && kind !== 'plan-today' && kind !== 'plan-today-preview') closePlanToday(false);
if (previous === 'today-readiness' && kind !== 'today-readiness') closeTodayReadiness(false);
if (previous === 'today-readiness' && kind !== 'today-readiness') {
if (kind === 'search-preview') suspendTodayReadiness();
else closeTodayReadiness(false);
}
if (kind === 'new' && previous !== 'new') openCreateIssueSheet(false);
if (kind === 'find' && previous !== 'find') openFindWorkSheet(false);
if (kind === 'search' && previous !== 'search-preview') openCommandPalette(false);
+1 -1
View File
@@ -1,6 +1,6 @@
const BASE = new URL('./', self.location.href).pathname;
importScripts(BASE + 'static/background-issue-sync.js');
const CACHE = 'stackchain-dashboard-shell-v74';
const CACHE = 'stackchain-dashboard-shell-v75';
const OFFLINE_LEASE_URL = new URL(BASE + '__offline-session-lease', self.location.origin).href;
const OUTAGE_STATUSES = new Set([500, 502, 503, 504]);
const NAVIGATION_TIMEOUT_MS = self.__STACKCHAIN_NAVIGATION_TIMEOUT_MS || 4000;
+12 -1
View File
@@ -5,7 +5,7 @@
})(typeof globalThis !== 'undefined' ? globalThis : this, function () {
'use strict';
return function createTodayReadiness({ inspect, onOpen = () => {}, onGate = () => {} }) {
return function createTodayReadiness({ inspect, onOpen = () => {}, onGate = () => {}, onPreview = () => {} }) {
let pending = null;
let generation = 0;
@@ -67,9 +67,20 @@
return true;
}
function previewBlocker(blocker) {
if (!pending || !blocker) return false;
const current = pending.dependencies.find(dependency =>
dependency?.repository === blocker.repository && Number(dependency?.number) === Number(blocker.number)
);
if (!current) return false;
onPreview(current);
return true;
}
return {
run,
retry: () => pending ? run(pending.action, pending.items, pending.target) : Promise.resolve('closed'),
previewBlocker,
startNextReady: () => open(pending?.nextReady),
workAnyway: () => open(pending?.target),
cancel() {