Compare commits
No commits in common. "7204a426afa7ffd3b584748fdbde6dc76d2a7987" and "dbadc73a57b5d9293fbb1fed30da84db3b39dffe" have entirely different histories.
7204a426af
...
dbadc73a57
|
|
@ -474,12 +474,6 @@ textarea { resize: vertical; min-height: 120px; }
|
||||||
.completed-filed-actions button { min-height:44px; min-width:0; }
|
.completed-filed-actions button { min-height:44px; min-width:0; }
|
||||||
#issue-sheet:has(.completed-filed-actions:not([hidden])) .issue-sheet-panel { padding-bottom:calc(110px + env(safe-area-inset-bottom)); }
|
#issue-sheet:has(.completed-filed-actions:not([hidden])) .issue-sheet-panel { padding-bottom:calc(110px + env(safe-area-inset-bottom)); }
|
||||||
.issue-sheet-content { overflow-wrap:anywhere; white-space:pre-wrap; }
|
.issue-sheet-content { overflow-wrap:anywhere; white-space:pre-wrap; }
|
||||||
.checklist-completion { position:fixed; right:0; bottom:0; z-index:58; box-sizing:border-box; width:min(560px,100%); display:grid; grid-template-columns:minmax(0,1fr) repeat(2,minmax(0,auto)); align-items:center; gap:8px; padding:10px 12px calc(10px + env(safe-area-inset-bottom)); border-top:1px solid #4ade80; background:rgba(11,21,38,.98); overflow-wrap:anywhere; }
|
|
||||||
.checklist-completion[hidden] { display:none; }
|
|
||||||
.checklist-completion button { min-height:44px; min-width:0; }
|
|
||||||
#issue-sheet:has(.checklist-completion:not([hidden])) .issue-sheet-panel { padding-bottom:calc(118px + env(safe-area-inset-bottom)); }
|
|
||||||
#issue-sheet:has(.checklist-completion:not([hidden])) .work-session-nav { display:none; }
|
|
||||||
@media(max-width:420px) { .checklist-completion { grid-template-columns:1fr 1fr; } .checklist-completion span { grid-column:1 / -1; } }
|
|
||||||
.issue-blockers { max-width:100%; overflow-x:hidden; margin:16px 0; padding:12px; border:1px solid #b45309; border-radius:12px; background:#291b0c; }
|
.issue-blockers { max-width:100%; overflow-x:hidden; margin:16px 0; padding:12px; border:1px solid #b45309; border-radius:12px; background:#291b0c; }
|
||||||
.issue-blockers h2 { margin-top:0; }
|
.issue-blockers h2 { margin-top:0; }
|
||||||
.issue-blocker-list { display:grid; gap:8px; }
|
.issue-blocker-list { display:grid; gap:8px; }
|
||||||
|
|
|
||||||
|
|
@ -216,7 +216,6 @@
|
||||||
let selectedIssue = null;
|
let selectedIssue = null;
|
||||||
let selectedIssueOffline = false;
|
let selectedIssueOffline = false;
|
||||||
let selectedIssueDetail = null;
|
let selectedIssueDetail = null;
|
||||||
let dismissedChecklistBody = null;
|
|
||||||
let issueBlockerCandidates = [];
|
let issueBlockerCandidates = [];
|
||||||
let issueBlockerSearchTimer = null;
|
let issueBlockerSearchTimer = null;
|
||||||
let issueConversation = null;
|
let issueConversation = null;
|
||||||
|
|
@ -3399,22 +3398,9 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderChecklistCompletion(detail) {
|
|
||||||
const interactive = !issueController.readOnly(selectedIssue) && detail?.state === 'open' && detail?.updated_at;
|
|
||||||
const completion = issueController.checklistCompletion(qs('#issue-sheet-body'), {
|
|
||||||
interactive, today:workSession.active(), offline:selectedIssueOffline,
|
|
||||||
dismissed:dismissedChecklistBody === detail?.body,
|
|
||||||
});
|
|
||||||
const bar = qs('#checklist-completion');
|
|
||||||
bar.hidden = !completion.visible;
|
|
||||||
qs('#checklist-completion-status').textContent = completion.status;
|
|
||||||
qs('#complete-checklist-issue').textContent = completion.label;
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderIssueBody(detail) {
|
function renderIssueBody(detail) {
|
||||||
const interactive = !issueController.readOnly(selectedIssue) && detail.state === 'open' && detail.updated_at;
|
issueController.renderTasks(qs('#issue-sheet-body'), detail,
|
||||||
issueController.renderTasks(qs('#issue-sheet-body'), detail, interactive);
|
!issueController.readOnly(selectedIssue) && detail.state === 'open' && detail.updated_at);
|
||||||
renderChecklistCompletion(detail);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyIssueContent(editing, detail, confirmed) {
|
function applyIssueContent(editing, detail, confirmed) {
|
||||||
|
|
@ -3441,7 +3427,6 @@
|
||||||
qs('#issue-planning').inert = false;
|
qs('#issue-planning').inert = false;
|
||||||
qs('#issue-handoff').inert = false;
|
qs('#issue-handoff').inert = false;
|
||||||
selectedIssue = item;
|
selectedIssue = item;
|
||||||
dismissedChecklistBody = null;
|
|
||||||
issueMentions.dismiss();
|
issueMentions.dismiss();
|
||||||
selectedIssueOffline = Boolean(offlineDetail);
|
selectedIssueOffline = Boolean(offlineDetail);
|
||||||
selectedIssueDetail = null;
|
selectedIssueDetail = null;
|
||||||
|
|
@ -3462,7 +3447,6 @@
|
||||||
qs('#completed-filed-progress').textContent = item.is_completed ?
|
qs('#completed-filed-progress').textContent = item.is_completed ?
|
||||||
'Completed Filed issue ' + (completedPosition + 1) + ' of ' + completedItems.length : '';
|
'Completed Filed issue ' + (completedPosition + 1) + ' of ' + completedItems.length : '';
|
||||||
qs('#issue-sheet-body').textContent = '';
|
qs('#issue-sheet-body').textContent = '';
|
||||||
qs('#checklist-completion').hidden = true;
|
|
||||||
qs('#issue-labels').textContent = '';
|
qs('#issue-labels').textContent = '';
|
||||||
qs('#issue-assignees').textContent = '';
|
qs('#issue-assignees').textContent = '';
|
||||||
qs('#issue-comments').textContent = '';
|
qs('#issue-comments').textContent = '';
|
||||||
|
|
@ -5625,14 +5609,6 @@
|
||||||
confirmed:applyIssueContent,
|
confirmed:applyIssueContent,
|
||||||
restore:renderIssueBody,
|
restore:renderIssueBody,
|
||||||
});
|
});
|
||||||
qs('#complete-checklist-issue').addEventListener('click', () => {
|
|
||||||
qs('#close-issue').click();
|
|
||||||
});
|
|
||||||
qs('#keep-checklist-issue-open').addEventListener('click', () => {
|
|
||||||
dismissedChecklistBody = selectedIssueDetail?.body;
|
|
||||||
if (selectedIssueDetail) renderChecklistCompletion(selectedIssueDetail);
|
|
||||||
qs('#issue-sheet-body').focus();
|
|
||||||
});
|
|
||||||
qs('#issue-planning').addEventListener('toggle', event => {
|
qs('#issue-planning').addEventListener('toggle', event => {
|
||||||
if (event.currentTarget.open) loadIssuePlanning();
|
if (event.currentTarget.open) loadIssuePlanning();
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -605,11 +605,6 @@
|
||||||
<button class="issue-retry" id="retry-issue-load" type="button" hidden>Reload latest issue</button>
|
<button class="issue-retry" id="retry-issue-load" type="button" hidden>Reload latest issue</button>
|
||||||
<div class="row"><span id="issue-labels"></span><span class="small" id="issue-assignees"></span></div>
|
<div class="row"><span id="issue-labels"></span><span class="small" id="issue-assignees"></span></div>
|
||||||
<div class="issue-sheet-content markdown-content" id="issue-sheet-body"></div>
|
<div class="issue-sheet-content markdown-content" id="issue-sheet-body"></div>
|
||||||
<section class="checklist-completion" id="checklist-completion" aria-label="Completed checklist" hidden>
|
|
||||||
<span id="checklist-completion-status" role="status" aria-live="polite">Checklist complete</span>
|
|
||||||
<button id="complete-checklist-issue" type="button">Close issue</button>
|
|
||||||
<button id="keep-checklist-issue-open" type="button">Keep open</button>
|
|
||||||
</section>
|
|
||||||
<section class="issue-blockers" id="issue-blockers" aria-labelledby="issue-blockers-title" hidden>
|
<section class="issue-blockers" id="issue-blockers" aria-labelledby="issue-blockers-title" hidden>
|
||||||
<h2 id="issue-blockers-title">Blocked by</h2>
|
<h2 id="issue-blockers-title">Blocked by</h2>
|
||||||
<div class="issue-blocker-list" id="issue-blocker-list"></div>
|
<div class="issue-blocker-list" id="issue-blocker-list"></div>
|
||||||
|
|
|
||||||
|
|
@ -204,16 +204,6 @@ function createIssueSheet({ fetchJson, storage, renderMarkdown = globalThis.rend
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
checklistCompletion(container, { interactive, today, offline, dismissed = false }) {
|
|
||||||
const tasks = Array.from(container.querySelectorAll('input.task-list-toggle'));
|
|
||||||
const visible = Boolean(interactive && !dismissed && tasks.length && tasks.every(task => task.checked));
|
|
||||||
return {
|
|
||||||
visible,
|
|
||||||
status: offline ? 'Checklist complete · pending sync' : 'Checklist complete',
|
|
||||||
label: today ? (offline ? 'Queue close & next' : 'Close & next') :
|
|
||||||
(offline ? 'Queue issue closure' : 'Close issue'),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
mergeContent(snapshot, item, detail, confirmed, replace) {
|
mergeContent(snapshot, item, detail, confirmed, replace) {
|
||||||
return {
|
return {
|
||||||
snapshot: snapshot ? replace(snapshot, item.repository, item.number, confirmed) : snapshot,
|
snapshot: snapshot ? replace(snapshot, item.repository, item.number, confirmed) : snapshot,
|
||||||
|
|
|
||||||
|
|
@ -2224,84 +2224,6 @@ async def test_mobile_issue_detail_toggles_checklist_with_touch_safe_recovery():
|
||||||
assert '.checklist-pending .task-list-toggle { opacity:.65;' in html
|
assert '.checklist-pending .task-list-toggle { opacity:.65;' in html
|
||||||
|
|
||||||
|
|
||||||
def test_completed_issue_checklist_offers_close_and_next_during_today():
|
|
||||||
script = f"""
|
|
||||||
const createIssueSheet = require({json.dumps(str(ISSUE_SHEET))});
|
|
||||||
const controller = createIssueSheet({{storage:null}});
|
|
||||||
const container = {{querySelectorAll:() => [{{checked:true}}, {{checked:true}}]}};
|
|
||||||
process.stdout.write(JSON.stringify(controller.checklistCompletion(container, {{
|
|
||||||
interactive:true, today:true, offline:false,
|
|
||||||
}})));
|
|
||||||
"""
|
|
||||||
output = json.loads(subprocess.run(
|
|
||||||
["node", "-e", script], check=True, capture_output=True, text=True
|
|
||||||
).stdout)
|
|
||||||
|
|
||||||
assert output == {
|
|
||||||
"visible": True,
|
|
||||||
"status": "Checklist complete",
|
|
||||||
"label": "Close & next",
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def test_checklist_completion_stays_hidden_when_kept_open():
|
|
||||||
script = f"""
|
|
||||||
const createIssueSheet = require({json.dumps(str(ISSUE_SHEET))});
|
|
||||||
const controller = createIssueSheet({{storage:null}});
|
|
||||||
const container = {{querySelectorAll:() => [{{checked:true}}]}};
|
|
||||||
process.stdout.write(JSON.stringify(controller.checklistCompletion(container, {{
|
|
||||||
interactive:true, today:false, offline:false, dismissed:true,
|
|
||||||
}})));
|
|
||||||
"""
|
|
||||||
output = json.loads(subprocess.run(
|
|
||||||
["node", "-e", script], check=True, capture_output=True, text=True
|
|
||||||
).stdout)
|
|
||||||
|
|
||||||
assert output["visible"] is False
|
|
||||||
|
|
||||||
|
|
||||||
def test_checklist_completion_copy_matches_online_and_offline_close_modes():
|
|
||||||
script = f"""
|
|
||||||
const createIssueSheet = require({json.dumps(str(ISSUE_SHEET))});
|
|
||||||
const controller = createIssueSheet({{storage:null}});
|
|
||||||
const container = {{querySelectorAll:() => [{{checked:true}}]}};
|
|
||||||
process.stdout.write(JSON.stringify({{
|
|
||||||
outside:controller.checklistCompletion(container, {{interactive:true,today:false,offline:false}}),
|
|
||||||
offlineToday:controller.checklistCompletion(container, {{interactive:true,today:true,offline:true}}),
|
|
||||||
offlineOutside:controller.checklistCompletion(container, {{interactive:true,today:false,offline:true}}),
|
|
||||||
}}));
|
|
||||||
"""
|
|
||||||
output = json.loads(subprocess.run(
|
|
||||||
["node", "-e", script], check=True, capture_output=True, text=True
|
|
||||||
).stdout)
|
|
||||||
|
|
||||||
assert output["outside"]["label"] == "Close issue"
|
|
||||||
assert output["offlineToday"] == {
|
|
||||||
"visible": True,
|
|
||||||
"status": "Checklist complete · pending sync",
|
|
||||||
"label": "Queue close & next",
|
|
||||||
}
|
|
||||||
assert output["offlineOutside"]["label"] == "Queue issue closure"
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
|
||||||
async def test_mobile_checklist_completion_bar_reuses_confirmed_close_flow():
|
|
||||||
html = await dashboard()
|
|
||||||
|
|
||||||
assert 'id="checklist-completion"' in html
|
|
||||||
assert 'id="checklist-completion-status" role="status"' in html
|
|
||||||
assert 'id="complete-checklist-issue"' in html
|
|
||||||
assert 'id="keep-checklist-issue-open"' in html
|
|
||||||
assert "function renderChecklistCompletion(detail)" in html
|
|
||||||
assert "issueController.checklistCompletion(qs('#issue-sheet-body')" in html
|
|
||||||
assert "qs('#complete-checklist-issue').addEventListener('click', () =>" in html
|
|
||||||
assert "qs('#close-issue').click()" in html
|
|
||||||
assert "dismissedChecklistBody = selectedIssueDetail?.body" in html
|
|
||||||
assert ".checklist-completion { position:fixed;" in html
|
|
||||||
assert ".checklist-completion button { min-height:44px;" in html
|
|
||||||
assert "padding-bottom:calc(118px + env(safe-area-inset-bottom))" in html
|
|
||||||
|
|
||||||
|
|
||||||
def test_offline_issue_checklist_toggle_waits_for_durable_admission_and_returns_pending_detail():
|
def test_offline_issue_checklist_toggle_waits_for_durable_admission_and_returns_pending_detail():
|
||||||
script = f"""
|
script = f"""
|
||||||
const createIssueSheet = require({json.dumps(str(ISSUE_SHEET))});
|
const createIssueSheet = require({json.dumps(str(ISSUE_SHEET))});
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user