feat: promote checklist steps to related issues (Closes #921)
CI / lint (pull_request) Successful in 1m58s
CI / build-release (pull_request) Successful in 5s
CI / browser-journey (pull_request) Successful in 58s
CI / release-candidate (pull_request) Has been skipped

This commit is contained in:
timmy committed 2026-08-15 23:40:17 +00:00
1 parent 08139bc3ee
commit 170828b308
8 files changed
+254 -5

No files matched your search

+34
View File
@@ -72,6 +72,40 @@ outbox.flush('timmy').then(result=>process.stdout.write(JSON.stringify({{result,
assert "relatedDraft" not in output["sentBody"]
def test_confirmed_delivery_returns_durable_checklist_promotion_without_sending_parent_context():
script = f"""
const createIssueOutbox = require({json.dumps(str(OUTBOX))});
const values=new Map(); let sentBody;
const storage={{getItem:key=>values.get(key)||null,setItem:(key,value)=>values.set(key,value),removeItem:key=>values.delete(key)}};
const outbox=createIssueOutbox({{
storage,getOwnerLogin:()=> 'timmy',createOperationId:()=> 'promote-operation',
fetchJson:async (_url,options)=>{{sentBody=JSON.parse(options.body);return {{repository:'stackchain/dashboard',number:44,title:'Build',url:'https://forge.example/issues/44'}};}},
}});
outbox.enqueue({{
repository:'stackchain/dashboard',title:'Build',body:'Related parent',
relatedDraft:{{repository:'stackchain/dashboard',title:'',body:'',labelIds:[],
checklistPromotion:{{repository:'stackchain/dashboard',number:17,url:'https://forge.example/issues/17',
title:'Parent',body:'- [ ] Build',updatedAt:'old',taskIndex:0}}}},
}});
outbox.flush('timmy').then(result=>process.stdout.write(JSON.stringify({{result,sentBody}})));
"""
output = run_node(script)
assert output["result"]["filings"] == [{
"issue": {"repository": "stackchain/dashboard", "number": 44,
"title": "Build", "url": "https://forge.example/issues/44"},
"relatedDraft": {
"repository": "stackchain/dashboard", "title": "", "body": "", "labelIds": [],
"checklistPromotion": {
"repository": "stackchain/dashboard", "number": 17,
"url": "https://forge.example/issues/17", "title": "Parent",
"body": "- [ ] Build", "updatedAt": "old", "taskIndex": 0,
},
},
}]
assert "checklistPromotion" not in output["sentBody"]
def test_editing_a_queued_issue_refreshes_its_related_plan():
script = f"""
const createIssueOutbox = require({json.dumps(str(OUTBOX))});
+63
View File
@@ -2266,6 +2266,69 @@ async def test_mobile_issue_detail_manages_checklist_steps_with_touch_safe_inlin
assert "label.focus()" in controller
@pytest.mark.anyio
async def test_mobile_checklist_manager_files_related_issue_and_links_confirmed_child():
html = await dashboard()
controller = ISSUE_SHEET.read_text()
capture_controller = CREATE_ISSUE_SHEET.read_text()
assert 'id="file-checklist-step-related" type="button">File as related issue</button>' in html
assert "fileRelated, onFileRelated, remove, cancel" in controller
assert "onFileRelated({ item:state.item, detail:state.detail, taskIndex:selectedIndex" in controller
assert "relatedTaskDraft" in controller
assert "linkRelatedTask" in controller
assert "startChecklistPromotion" in html
assert "createIssueCapture.createChecklistPromotion({" in html
assert "checklistPromotion.start(context)" in html
assert "checklistPromotion?.cancel()" in html
assert "checklistPromotion?.deliveryContext()" in html
assert "await issueController.linkRelatedTask(" in capture_controller
assert "Related issue created, but the parent link needs attention" in capture_controller
assert ".checklist-step-editor button, .checklist-step-editor input { min-height:44px;" in html
def test_checklist_step_promotion_builds_related_draft_and_revision_checked_parent_link():
script = f"""
const createIssueSheet = require({json.dumps(str(ISSUE_SHEET))});
const createIssueCapture = require({json.dumps(str(CREATE_ISSUE_SHEET))});
const calls = [];
const controller = createIssueSheet({{
storage:null,
relatedTaskDraft:createIssueCapture.relatedChecklistDraft,
linkRelatedTask:createIssueCapture.linkChecklistTask,
fetchJson:async (url, options) => {{
calls.push({{url, body:JSON.parse(options.body)}});
return {{number:17,title:'Release',body:options && JSON.parse(options.body).body,updated_at:'new'}};
}},
}});
const item = {{repository:'stackchain/api',number:17,url:'https://forge.example/stackchain/api/issues/17'}};
const detail = {{title:'Release',body:'Intro\\r\\n - [x] Build package\\r\\n - [ ] Ship',updated_at:'old'}};
const draft = controller.relatedTaskDraft(item, detail, 0, ' Build package ');
controller.linkRelatedTask(item, detail, 0, {{
repository:'stackchain/api',number:44,url:'https://forge.example/stackchain/api/issues/44',title:'Build package',
}}).then(result => process.stdout.write(JSON.stringify({{draft,calls,result}})));
"""
output = json.loads(subprocess.run(
["node", "-e", script], check=True, capture_output=True, text=True
).stdout)
assert output["draft"] == {
"repository": "stackchain/api",
"title": "Build package",
"body": "Related to [stackchain/api#17](https://forge.example/stackchain/api/issues/17).",
"labelIds": [],
}
assert output["calls"] == [{
"url": "api/v1/repos/stackchain/api/issues/17/content",
"body": {
"title": "Release",
"body": "Intro\r\n - [x] [Build package](https://forge.example/stackchain/api/issues/44)\r\n - [ ] Ship",
"expected_updated_at": "old",
},
}]
assert output["result"]["updated_at"] == "new"
@pytest.mark.anyio
async def test_mobile_drafts_reviews_and_retries_an_unambiguous_checklist_conflict():
html = await dashboard()