Continue Today after ownership exits #450
|
|
@ -917,6 +917,14 @@
|
|||
announce: message => { qs('#my-work-action-status').textContent = message; },
|
||||
advance: () => runTodayTransition('complete'),
|
||||
});
|
||||
async function completeOwnershipExitToday(item) {
|
||||
if (!item || !todayWork.remove(item)) return null;
|
||||
todaySync.enqueue('remove', todayWork.identity(item));
|
||||
todaySync.flush();
|
||||
refreshMyWorkView({ reconcileSession:false });
|
||||
warmTodayOffline();
|
||||
return await runTodayTransition('complete');
|
||||
}
|
||||
function setCommentNextVisibility(kind) {
|
||||
const item = kind === 'issue' ? selectedIssue : selectedPull;
|
||||
qs('#send-' + kind + '-comment-next').hidden = !item || !workSession.checkpointed(item);
|
||||
|
|
@ -1947,6 +1955,7 @@
|
|||
qs('#issue-handoff-recipient').innerHTML = '<option value="">Select a teammate</option>';
|
||||
qs('#issue-handoff-recipient').disabled = true;
|
||||
qs('#confirm-issue-handoff').disabled = true;
|
||||
qs('#confirm-issue-handoff').textContent = workSession.checkpointed(item) ? 'Hand off & next' : 'Confirm handoff';
|
||||
qs('#load-issue-handoff').disabled = false;
|
||||
qs('#issue-handoff-status').textContent = 'Load teammates to transfer ownership.';
|
||||
qs('#issue-planning').open = false;
|
||||
|
|
@ -1974,6 +1983,7 @@
|
|||
qs('#close-issue').textContent = offlineDetail ?
|
||||
(workSession.active() ? 'Queue close & next' : 'Queue issue closure') :
|
||||
(workSession.active() ? 'Close & next' : 'Close issue');
|
||||
qs('#release-issue').textContent = workSession.checkpointed(item) ? 'Release & next' : 'Release assignment';
|
||||
qs('#close-issue-sheet').focus();
|
||||
try {
|
||||
const detail = offlineDetail || await issueController.load(item);
|
||||
|
|
@ -3679,6 +3689,7 @@
|
|||
qs('#release-issue').addEventListener('click', async () => {
|
||||
if (!selectedIssue || !window.confirm('Release ' + selectedIssue.key + ' from your My Work?')) return;
|
||||
const releasing = selectedIssue;
|
||||
const continuingSession = workSession.checkpointed(releasing);
|
||||
const button = qs('#release-issue');
|
||||
button.disabled = true;
|
||||
qs('#issue-sheet-status').textContent = 'Releasing assignment…';
|
||||
|
|
@ -3688,9 +3699,20 @@
|
|||
lastContextSnapshot, releasing.repository, releasing.number
|
||||
);
|
||||
closeIssueSheet();
|
||||
paintMyWork(lastContextSnapshot);
|
||||
qs('#my-work-action-status').textContent = releasing.key + ' released.' +
|
||||
(confirmed.available ? ' It is available in Find Work.' : ' Other assignees remain.');
|
||||
if (continuingSession) {
|
||||
const transitionResult = await completeOwnershipExitToday(releasing);
|
||||
if (transitionResult === 'opened') {
|
||||
qs('#my-work-action-status').textContent = releasing.key + ' released. Next work item opened.';
|
||||
} else if (transitionResult === 'gated') {
|
||||
qs('#my-work-action-status').textContent = releasing.key + ' released. Choose the next ready Today item.';
|
||||
} else if (transitionResult === null) {
|
||||
qs('#my-work-action-status').textContent = releasing.key + ' released, but Today still needs completion.';
|
||||
}
|
||||
} else {
|
||||
paintMyWork(lastContextSnapshot);
|
||||
qs('#my-work-action-status').textContent = releasing.key + ' released.' +
|
||||
(confirmed.available ? ' It is available in Find Work.' : ' Other assignees remain.');
|
||||
}
|
||||
} catch (error) {
|
||||
qs('#issue-sheet-status').textContent = error.message + ' The issue remains in My Work; retry.';
|
||||
button.disabled = false;
|
||||
|
|
@ -3734,6 +3756,7 @@
|
|||
const recipient = qs('#issue-handoff-recipient').value;
|
||||
if (!selectedIssue || !recipient || !window.confirm('Hand off ' + selectedIssue.key + ' to @' + recipient + '?')) return;
|
||||
const handingOff = selectedIssue;
|
||||
const continuingSession = workSession.checkpointed(handingOff);
|
||||
const button = qs('#confirm-issue-handoff');
|
||||
button.disabled = true;
|
||||
qs('#issue-handoff-status').textContent = 'Confirming handoff…';
|
||||
|
|
@ -3743,8 +3766,19 @@
|
|||
lastContextSnapshot, handingOff.repository, handingOff.number
|
||||
);
|
||||
closeIssueSheet();
|
||||
paintMyWork(lastContextSnapshot);
|
||||
qs('#my-work-action-status').textContent = handingOff.key + ' handed off to @' + recipient + '.';
|
||||
if (continuingSession) {
|
||||
const transitionResult = await completeOwnershipExitToday(handingOff);
|
||||
if (transitionResult === 'opened') {
|
||||
qs('#my-work-action-status').textContent = handingOff.key + ' handed off to @' + recipient + '. Next work item opened.';
|
||||
} else if (transitionResult === 'gated') {
|
||||
qs('#my-work-action-status').textContent = handingOff.key + ' handed off to @' + recipient + '. Choose the next ready Today item.';
|
||||
} else if (transitionResult === null) {
|
||||
qs('#my-work-action-status').textContent = handingOff.key + ' handed off to @' + recipient + ', but Today still needs completion.';
|
||||
}
|
||||
} else {
|
||||
paintMyWork(lastContextSnapshot);
|
||||
qs('#my-work-action-status').textContent = handingOff.key + ' handed off to @' + recipient + '.';
|
||||
}
|
||||
} catch (error) {
|
||||
qs('#issue-handoff-status').textContent = error.message + ' The issue remains in My Work; retry.';
|
||||
button.disabled = false;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
const BASE = new URL('./', self.location.href).pathname;
|
||||
importScripts(BASE + 'static/background-issue-sync.js');
|
||||
const CACHE = 'stackchain-dashboard-shell-v76';
|
||||
const CACHE = 'stackchain-dashboard-shell-v77';
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -347,5 +347,5 @@ async def test_dashboard_syncs_every_later_change_and_exposes_account_status():
|
|||
def test_later_sync_ships_atomically_in_the_offline_shell():
|
||||
source = (Path(__file__).parents[1] / "frontend" / "service-worker.js").read_text()
|
||||
|
||||
assert "stackchain-dashboard-shell-v76" in source
|
||||
assert "stackchain-dashboard-shell-v77" in source
|
||||
assert "BASE + 'static/later-sync.js'" in source
|
||||
|
|
|
|||
|
|
@ -137,4 +137,4 @@ def test_markdown_work_bodies_are_mobile_safe_block_containers():
|
|||
assert ".markdown-content { min-width:0; max-width:100%; overflow-wrap:anywhere;" in css
|
||||
assert ".markdown-content pre { max-width:100%; overflow-x:auto;" in css
|
||||
assert ".markdown-content a { min-height:44px;" in css
|
||||
assert "stackchain-dashboard-shell-v76" in worker
|
||||
assert "stackchain-dashboard-shell-v77" in worker
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ def test_offline_shell_contains_every_local_dashboard_runtime_asset():
|
|||
shell_assets = set(re.findall(r"BASE \+ '([^']+)'", worker.split("async function sessionCsrf", 1)[0]))
|
||||
|
||||
assert local_assets <= shell_assets, f"Offline shell is missing: {sorted(local_assets - shell_assets)}"
|
||||
assert "stackchain-dashboard-shell-v76" in worker
|
||||
assert "stackchain-dashboard-shell-v77" in worker
|
||||
|
||||
|
||||
def test_all_conversation_composers_offer_accessible_mobile_mentions():
|
||||
|
|
|
|||
|
|
@ -1688,6 +1688,44 @@ async def test_closing_issue_advances_active_session_once_and_exposes_close_and_
|
|||
)
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_releasing_checkpointed_issue_completes_today_through_readiness_transition():
|
||||
html = await dashboard()
|
||||
|
||||
assert "workSession.checkpointed(item) ? 'Release & next' : 'Release assignment'" in html
|
||||
release_handler = html.split("qs('#release-issue').addEventListener('click'", 1)[1].split(
|
||||
"qs('#load-issue-handoff').addEventListener", 1
|
||||
)[0]
|
||||
assert "const continuingSession = workSession.checkpointed(releasing);" in release_handler
|
||||
assert "await completeOwnershipExitToday(releasing)" in release_handler
|
||||
assert "paintMyWork(lastContextSnapshot);" in release_handler
|
||||
assert release_handler.index("await issueController.release") < release_handler.index(
|
||||
"await completeOwnershipExitToday(releasing)"
|
||||
)
|
||||
assert release_handler.index("buildMyWork.removeIssue") < release_handler.index(
|
||||
"await completeOwnershipExitToday(releasing)"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_handing_off_checkpointed_issue_completes_today_through_readiness_transition():
|
||||
html = await dashboard()
|
||||
|
||||
assert "workSession.checkpointed(item) ? 'Hand off & next' : 'Confirm handoff'" in html
|
||||
handoff_handler = html.split("qs('#confirm-issue-handoff').addEventListener('click'", 1)[1].split(
|
||||
"qs('#close-issue').addEventListener", 1
|
||||
)[0]
|
||||
assert "const continuingSession = workSession.checkpointed(handingOff);" in handoff_handler
|
||||
assert "await completeOwnershipExitToday(handingOff)" in handoff_handler
|
||||
assert "paintMyWork(lastContextSnapshot);" in handoff_handler
|
||||
assert handoff_handler.index("await issueController.handoff") < handoff_handler.index(
|
||||
"await completeOwnershipExitToday(handingOff)"
|
||||
)
|
||||
assert handoff_handler.index("buildMyWork.removeIssue") < handoff_handler.index(
|
||||
"await completeOwnershipExitToday(handingOff)"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_merging_pull_advances_active_session_once_and_exposes_merge_and_next():
|
||||
html = await dashboard()
|
||||
|
|
|
|||
|
|
@ -232,6 +232,6 @@ async def test_plan_today_wires_cancel_back_and_success_through_overlay_history(
|
|||
def test_plan_today_controller_is_available_in_the_offline_shell():
|
||||
source = SERVICE_WORKER.read_text()
|
||||
|
||||
assert "stackchain-dashboard-shell-v76" in source
|
||||
assert "stackchain-dashboard-shell-v77" in source
|
||||
assert "BASE + 'static/plan-today.js'" in source
|
||||
assert "BASE + 'static/plan-today-preview.js'" in source
|
||||
|
|
|
|||
|
|
@ -122,16 +122,23 @@ async function dispatchNotificationClick(route) {{
|
|||
def test_resumable_today_session_ships_in_a_new_offline_shell():
|
||||
source = WORKER.read_text()
|
||||
|
||||
assert "stackchain-dashboard-shell-v76" in source
|
||||
assert "stackchain-dashboard-shell-v77" in source
|
||||
assert "BASE + 'static/my-work.js'" in source
|
||||
assert "BASE + 'static/dashboard.js'" in source
|
||||
assert "BASE + 'static/dashboard.css'" in source
|
||||
|
||||
|
||||
def test_ownership_exit_runtime_rolls_the_offline_shell_cache():
|
||||
source = WORKER.read_text()
|
||||
|
||||
assert "stackchain-dashboard-shell-v77" in source
|
||||
assert "BASE + 'static/dashboard.js'" in source
|
||||
|
||||
|
||||
def test_offline_review_next_ships_today_completion_atomically():
|
||||
source = WORKER.read_text()
|
||||
|
||||
assert "stackchain-dashboard-shell-v76" in source
|
||||
assert "stackchain-dashboard-shell-v77" in source
|
||||
assert "BASE + 'static/today-completion.js'" in source
|
||||
assert "BASE + 'static/dashboard.js'" in source
|
||||
|
||||
|
|
@ -139,7 +146,7 @@ def test_offline_review_next_ships_today_completion_atomically():
|
|||
def test_duplicate_aware_capture_ships_in_a_new_offline_shell():
|
||||
source = WORKER.read_text()
|
||||
|
||||
assert "stackchain-dashboard-shell-v76" in source
|
||||
assert "stackchain-dashboard-shell-v77" in source
|
||||
assert "BASE + 'static/create-issue-sheet.js'" in source
|
||||
assert "BASE + 'static/dashboard.js'" in source
|
||||
|
||||
|
|
@ -147,14 +154,14 @@ def test_duplicate_aware_capture_ships_in_a_new_offline_shell():
|
|||
def test_exact_later_picker_ships_atomically_in_a_new_offline_shell():
|
||||
source = WORKER.read_text()
|
||||
|
||||
assert "stackchain-dashboard-shell-v76" in source
|
||||
assert "stackchain-dashboard-shell-v77" in source
|
||||
assert "BASE + 'static/later-picker.js'" in source
|
||||
|
||||
|
||||
def test_navigation_deadline_ships_in_a_new_shell_cache():
|
||||
source = WORKER.read_text()
|
||||
|
||||
assert "stackchain-dashboard-shell-v76" in source
|
||||
assert "stackchain-dashboard-shell-v77" in source
|
||||
assert "BASE + 'static/dashboard.css'" in source
|
||||
assert "BASE + 'static/dashboard.js'" in source
|
||||
assert "BASE + 'static/install-app.js'" in source
|
||||
|
|
@ -163,21 +170,21 @@ def test_navigation_deadline_ships_in_a_new_shell_cache():
|
|||
def test_today_convergence_ships_in_a_new_shell_cache():
|
||||
source = WORKER.read_text()
|
||||
|
||||
assert "stackchain-dashboard-shell-v76" in source
|
||||
assert "stackchain-dashboard-shell-v77" in source
|
||||
assert "BASE + 'static/today-sync.js'" in source
|
||||
|
||||
|
||||
def test_mobile_search_viewport_ships_in_a_new_offline_shell():
|
||||
source = WORKER.read_text()
|
||||
|
||||
assert "stackchain-dashboard-shell-v76" in source
|
||||
assert "stackchain-dashboard-shell-v77" in source
|
||||
assert "BASE + 'static/mobile-search-viewport.js'" in source
|
||||
|
||||
|
||||
def test_update_ownership_flow_ships_atomically_in_a_new_offline_shell():
|
||||
source = WORKER.read_text()
|
||||
|
||||
assert "stackchain-dashboard-shell-v76" in source
|
||||
assert "stackchain-dashboard-shell-v77" in source
|
||||
assert "BASE + 'static/update-ownership.js'" in source
|
||||
|
||||
|
||||
|
|
@ -358,7 +365,7 @@ def test_one_session_bound_csrf_proof_is_reused_for_a_background_drain():
|
|||
def test_queue_today_ships_atomically_in_a_new_offline_shell():
|
||||
source = WORKER.read_text()
|
||||
|
||||
assert "stackchain-dashboard-shell-v76" in source
|
||||
assert "stackchain-dashboard-shell-v77" in source
|
||||
assert "BASE + 'static/queue-today.js'" in source
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -221,7 +221,7 @@ async def test_today_blocker_opens_existing_preview_and_preserves_readiness_gate
|
|||
def test_readiness_runtime_is_available_in_offline_shell():
|
||||
service_worker = SERVICE_WORKER.read_text()
|
||||
|
||||
assert "const CACHE = 'stackchain-dashboard-shell-v76';" in service_worker
|
||||
assert "const CACHE = 'stackchain-dashboard-shell-v77';" in service_worker
|
||||
assert "BASE + 'static/today-readiness.js'" in service_worker
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@ sync.enqueue('add', 'issue:r:1:');
|
|||
def test_inflight_today_drain_ships_in_a_new_offline_shell():
|
||||
source = (Path(__file__).parents[1] / "frontend" / "service-worker.js").read_text()
|
||||
|
||||
assert "stackchain-dashboard-shell-v76" in source
|
||||
assert "stackchain-dashboard-shell-v77" in source
|
||||
assert "BASE + 'static/today-sync.js'" in source
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user