feat: explain Following changes before review (Closes #1322)
CI / lint (pull_request) Successful in 4m4s
CI / build-release (pull_request) Successful in 7s
CI / browser-journey (pull_request) Successful in 5m48s
CI / release-candidate (pull_request) Has been skipped

This commit is contained in:
timmy committed 2026-08-23 21:28:50 +00:00
1 parent 31700d5c71
commit 5921027e80
8 files changed
+174 -18

No files matched your search

+3 -2
View File
@@ -41,7 +41,8 @@ def test_following_queue_is_phone_usable_at_narrow_viewport(viewport):
json:async () => ({revision:1,items:[{
repository:'stackchain/api', kind:'pull', number:42,
title:'Changed pull request with a long mobile title', state:'open',
updated_at:'2026-08-23T05:00:00Z', has_unseen_change:true
updated_at:'2026-08-23T05:00:00Z', has_unseen_change:true,
change_summary:'Closed since last review'
}]})
});
globalThis.followingReleaseQueue = attachFollowing(() => {});
@@ -50,7 +51,7 @@ def test_following_queue_is_phone_usable_at_narrow_viewport(viewport):
expect(page.locator("#following-sheet")).to_be_visible()
expect(page.locator(".following-card")).to_be_visible()
expect(page.locator(".following-card")).to_contain_text("New activity")
expect(page.locator(".following-card")).to_contain_text("Closed since last review")
expect(page.locator(".following-card")).to_contain_text("Pull request")
expect(page.locator("#review-following")).to_have_text("Review new activity")
assert page.locator("#review-following").bounding_box()["height"] >= 44
+63
View File
@@ -113,6 +113,69 @@ def test_following_preview_wires_keep_for_later_action_and_retryable_failure():
assert "keepForLater:feature.keepForLater" in following
def test_following_conversation_marks_messages_newer_than_last_review():
preview_module = ROOT / "frontend" / "search-preview.js"
script = f"""
require({json.dumps(str(preview_module))});
const nodes={{
'#search-preview-comments':{{innerHTML:'',textContent:''}},
'#search-preview-conversation-status':{{textContent:''}},
'#retry-search-preview-conversation':{{hidden:true}},
'#load-older-search-preview-comments':{{hidden:true,disabled:false}},
}};
const document={{querySelector:selector=>nodes[selector]}};
global.renderSearchPreviewConversation({{
status:'ready', reviewedAt:'2026-08-23T03:00:00Z', comments:[
{{id:1,author:'Timmy',created_at:'2026-08-23T02:00:00Z',body:'Earlier'}},
{{id:2,author:'Alex',created_at:'2026-08-23T04:00:00Z',body:'New decision'}},
]
}},document,String,String,String);
process.stdout.write(JSON.stringify({{html:nodes['#search-preview-comments'].innerHTML,status:nodes['#search-preview-conversation-status'].textContent}}));
"""
result = json.loads(subprocess.run(
["node", "-e", script], text=True, capture_output=True, check=True
).stdout)
assert result["html"].count("new-since-review") == 1
assert "New since last review" in result["html"]
assert result["status"] == "2 messages · 1 new since last review."
def test_following_revision_is_acknowledged_only_after_conversation_context_loads():
preview_module = ROOT / "frontend" / "search-preview.js"
script = f"""
const createSearchPreview=require({json.dumps(str(preview_module))});
let unavailable=true;
const opened=[];
const preview=createSearchPreview({{
fetchJson:async item=>({{...item,title:'Changed issue'}}),
fetchConversation:async()=>{{if(unavailable)throw new Error('offline');return {{comments:[]}};}},
onOpened:async item=>opened.push(item.updated_at),
onState:()=>{{}},
}});
(async()=>{{
await preview.open({{
repository:'stackchain/api',kind:'issue',number:42,following:true,
reviewed_at:'2026-08-23T03:00:00Z',updated_at:'2026-08-23T04:00:00Z'
}});
const afterFailure=[...opened];
unavailable=false;
await preview.retryConversation();
process.stdout.write(JSON.stringify({{afterFailure,opened}}));
}})().catch(error=>{{console.error(error);process.exit(1);}});
"""
result = json.loads(subprocess.run(
["node", "-e", script], text=True, capture_output=True, check=True
).stdout)
assert result == {
"afterFailure": [],
"opened": ["2026-08-23T04:00:00Z"],
}
def test_following_typed_identity_prevents_issue_pull_collisions():
script = f"""
const createFollowing = require({json.dumps(str(MODULE))});
+28
View File
@@ -51,6 +51,34 @@ def test_refresh_marks_changed_items_unseen_and_sorts_them_first(tmp_path):
assert snapshot["items"][0]["state"] == "closed"
def test_refresh_explains_change_from_last_reviewed_snapshot(tmp_path):
store = FollowingStore(tmp_path / "following.sqlite3", encryption_key=KEY)
store.set_watching("timmy", ITEM, True)
closed = store.refresh("timmy", [{
**ITEM,
"title": "Make mobile review trustworthy",
"state": "closed",
"updated_at": "2026-08-23T04:00:00Z",
}])
assert closed["items"][0]["reviewed_at"] == ITEM["updated_at"]
assert closed["items"][0]["change_summary"] == "Closed since last review"
store.acknowledge(
"timmy", ITEM["repository"], ITEM["number"], "2026-08-23T04:00:00Z"
)
title_change = store.refresh("timmy", [{
**ITEM,
"title": "Make mobile review understandable",
"state": "closed",
"updated_at": "2026-08-23T05:00:00Z",
}])
assert title_change["items"][0]["reviewed_at"] == "2026-08-23T04:00:00Z"
assert title_change["items"][0]["change_summary"] == "Title changed since last review"
def test_acknowledgement_is_revision_conditional_and_later_change_is_unseen(tmp_path):
store = FollowingStore(tmp_path / "following.sqlite3", encryption_key=KEY)
store.set_watching("timmy", ITEM, True)