feat: open Following alerts in changed review (Closes #1317)
All checks were successful
CI / lint (pull_request) Successful in 3m29s
CI / build-release (pull_request) Successful in 7s
CI / browser-journey (pull_request) Successful in 5m14s
CI / release-candidate (pull_request) Has been skipped

This commit is contained in:
timmy 2026-08-23 18:37:57 +00:00
parent 2b6730288a
commit a2501e47c3
6 changed files with 88 additions and 6 deletions

View File

@ -8233,6 +8233,7 @@
return true;
}
async function openWorkQueueRoute(filter, action = null) {
if (filter === 'following') return followingQueue.route();
if (!selectWorkQueue(filter, { preserveRoute:true })) return;
qs('#my-work').scrollIntoView({block:'start'});
qs('#my-work').focus();

View File

@ -137,6 +137,7 @@
return payload;
};
let feature;
const show = () => query('#following-sheet').open || query('#following-sheet').showModal();
function render(state) {
hooks.onStatus?.(state.status);
const list = query('#following-list');
@ -191,16 +192,18 @@
query('#review-following').addEventListener('click', () => {
query('#following-sheet').close();
feature.startReview().catch(() => {
if (!query('#following-sheet').open) query('#following-sheet').showModal();
show();
});
});
query('#retry-following').addEventListener('click', () => feature.load().catch(() => {}));
return {
load:feature.load,
review:feature.startReview,
async route() {
await feature.load().then(feature.startReview).catch(() => false) || show();
},
open() {
const sheet = query('#following-sheet');
if (!sheet.open) sheet.showModal();
show();
feature.load().catch(() => {});
return 'opened-following';
},
@ -210,8 +213,7 @@
returnToFollowing() {
const completed = feature.finishReview();
if (completed) return 'completed-following';
const sheet = query('#following-sheet');
if (!sheet.open) sheet.showModal();
show();
globalThis.requestAnimationFrame?.(() => {
const target = query('#review-following:not([hidden])') || query('.following-card') || query('#close-following');
target?.focus();

View File

@ -6,7 +6,7 @@
'use strict';
const repositoryPart = /^[A-Za-z0-9_.-]+$/;
const queueFilters = ['today', 'agenda', 'attention', 'filed', 'update', 'later', 'draft'];
const queueFilters = ['today', 'agenda', 'attention', 'filed', 'update', 'later', 'draft', 'following'];
const sections = {
issue: ['overview', 'conversation', 'reply', 'actions'],
filed: ['overview', 'conversation', 'reply', 'actions'],

View File

@ -80,3 +80,34 @@ def test_following_queue_is_phone_usable_at_narrow_viewport(viewport):
overflow = page.evaluate("document.documentElement.scrollWidth > document.documentElement.clientWidth")
assert overflow is False
browser.close()
def test_following_alert_route_opens_truthful_empty_state_with_one_refresh():
with sync_playwright() as playwright:
browser = playwright.chromium.launch(headless=True)
page = browser.new_page(viewport={"width": 390, "height": 844})
page.set_content((FRONTEND / "index.html").read_text())
page.add_style_tag(path=FRONTEND / "dashboard.css")
page.add_script_tag(path=FRONTEND / "following.js")
result = page.evaluate("""async () => {
let requests=0;
globalThis.fetch=async () => {
requests += 1;
return {ok:true,json:async () => ({revision:2,items:[]})};
};
const queue=attachFollowing(() => {});
await queue.route();
return {requests,open:document.querySelector('#following-sheet').open,
status:document.querySelector('#following-status').textContent};
}""")
assert result == {
"requests": 1,
"open": True,
"status": "No watched items yet. Watch an issue or pull request from Search to keep it here.",
}
expect(page.locator("#following-sheet")).to_be_visible()
assert page.locator("#close-following").bounding_box()["height"] >= 44
browser.close()

View File

@ -76,6 +76,26 @@ const feature=createFollowing({{
assert result["session"]["items"][0]["kind"] == "pull"
def test_following_route_loads_authoritative_state_before_starting_review():
following = MODULE.read_text()
start = following.index("async route()")
route = following[start:following.index("open()", start)]
assert "await feature.load().then(feature.startReview)" in route
assert ".catch(() => false)" in route
assert "show();" in route
def test_dashboard_dispatches_following_route_to_changed_first_review():
dashboard = (ROOT / "frontend" / "dashboard.js").read_text()
start = dashboard.index("async function openWorkQueueRoute")
route_handler = dashboard[start:dashboard.index("qs('#work-milestone-filter')", start)]
assert "if (filter === 'following')" in route_handler
assert "return followingQueue.route();" in route_handler
assert route_handler.index("followingQueue.route") < route_handler.index("selectWorkQueue")
def test_following_typed_identity_prevents_issue_pull_collisions():
script = f"""
const createFollowing = require({json.dumps(str(MODULE))});

View File

@ -676,6 +676,34 @@ process.stdout.write(JSON.stringify(routes.parse('#/my-work/agenda/protect-today
assert json.loads(result.stdout) == {
"kind": "queue", "filter": "agenda", "action": "protect-today"
}
def test_following_route_waits_for_hydration_then_opens_once():
script = f"""
const routes = require({json.dumps(str(WORK_ROUTE))});
const listeners = {{}};
const location = {{hash:'#/my-work/following'}};
const calls = [];
const controller = routes.createController({{
location,
history: {{pushState() {{}}, replaceState() {{}}, back() {{}}}},
eventTarget: {{addEventListener(name, fn) {{ listeners[name] = fn; }}}},
onQueue: (queue, action) => calls.push([queue, action]),
onInvalid: () => calls.push(['invalid']),
}});
controller.start();
controller.setItems([]);
controller.sync();
process.stdout.write(JSON.stringify({{route:routes.parse(location.hash), calls}}));
"""
result = subprocess.run(
["node", "-e", script], check=True, capture_output=True, text=True
)
assert json.loads(result.stdout) == {
"route": {"kind": "queue", "filter": "following"},
"calls": [["following", None]],
}
CARD_PLANNING = Path(__file__).parents[1] / "frontend" / "card-planning.js"
TODAY_WORK = Path(__file__).parents[1] / "frontend" / "today-work.js"
WORK_SELECTION = Path(__file__).parents[1] / "frontend" / "work-selection.js"