fix: finish identity recovery before workspace readiness
This commit is contained in:
parent
5ccf499b1a
commit
02b9fdd2e4
|
|
@ -126,7 +126,7 @@ function createContextPoller({
|
||||||
|
|
||||||
let request;
|
let request;
|
||||||
try {
|
try {
|
||||||
request = fetchContext({ ...revisions }, { signal: controller.signal });
|
request = fetchContext(options.full ? {} : { ...revisions }, { signal: controller.signal });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
request = Promise.reject(error);
|
request = Promise.reject(error);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -358,6 +358,7 @@
|
||||||
qs('#empty-work-find').addEventListener('click', () => qs('#find-work').click());
|
qs('#empty-work-find').addEventListener('click', () => qs('#find-work').click());
|
||||||
qs('#empty-work-create').addEventListener('click', () => qs('#new-issue').click());
|
qs('#empty-work-create').addEventListener('click', () => qs('#new-issue').click());
|
||||||
let liveMode = true;
|
let liveMode = true;
|
||||||
|
let initialAccountRecovery = Promise.resolve(false);
|
||||||
const WORK_FILTER_KEY = 'stackchain.my-work-filter.v1';
|
const WORK_FILTER_KEY = 'stackchain.my-work-filter.v1';
|
||||||
const WORK_MILESTONE_KEY = 'stackchain.my-work-milestone.v1';
|
const WORK_MILESTONE_KEY = 'stackchain.my-work-milestone.v1';
|
||||||
const WORK_FILTERS = ['all', 'today', 'agenda', 'attention', 'filed', 'authored', 'issue', 'pull', 'review', 'update', 'later', 'draft'];
|
const WORK_FILTERS = ['all', 'today', 'agenda', 'attention', 'filed', 'authored', 'issue', 'pull', 'review', 'update', 'later', 'draft'];
|
||||||
|
|
@ -5644,6 +5645,9 @@
|
||||||
activeFlushLogin = contextIdentityFresh ? String(snapshot.context.user?.login || '').trim() : '';
|
activeFlushLogin = contextIdentityFresh ? String(snapshot.context.user?.login || '').trim() : '';
|
||||||
if (activeFlushLogin) {
|
if (activeFlushLogin) {
|
||||||
confirmedOwnerLogin = activeFlushLogin;
|
confirmedOwnerLogin = activeFlushLogin;
|
||||||
|
planningOwnerLogin = activeFlushLogin;
|
||||||
|
planningOwnerAccountKey = snapshot.context.user?.id ?
|
||||||
|
String(snapshot.context.user.id) + ':' + activeFlushLogin : '';
|
||||||
mobileQueuePriority.render();
|
mobileQueuePriority.render();
|
||||||
renderMobileQueuePresentation();
|
renderMobileQueuePresentation();
|
||||||
void mobileQueuePriority.load();
|
void mobileQueuePriority.load();
|
||||||
|
|
@ -5666,7 +5670,10 @@
|
||||||
if (planningOwnerLogin) {
|
if (planningOwnerLogin) {
|
||||||
syncPendingTomorrow();
|
syncPendingTomorrow();
|
||||||
todaySync.migrate(todayWork.read());
|
todaySync.migrate(todayWork.read());
|
||||||
todaySync.flush();
|
initialAccountRecovery = Promise.all([
|
||||||
|
initialAccountRecovery,
|
||||||
|
todaySync.flush(),
|
||||||
|
]).then(() => true);
|
||||||
laterSync.migrate(laterWork.read());
|
laterSync.migrate(laterWork.read());
|
||||||
laterSync.flush();
|
laterSync.flush();
|
||||||
}
|
}
|
||||||
|
|
@ -8597,6 +8604,20 @@
|
||||||
adoptedProgressiveSnapshot = await contextPoller.adoptPending(progressiveWorkHandoff.liveSnapshotPromise);
|
adoptedProgressiveSnapshot = await contextPoller.adoptPending(progressiveWorkHandoff.liveSnapshotPromise);
|
||||||
}
|
}
|
||||||
if (!adoptedProgressiveSnapshot) await load();
|
if (!adoptedProgressiveSnapshot) await load();
|
||||||
|
else if (!confirmedOwnerLogin) {
|
||||||
|
await contextPoller.refresh({ force:true, full:true });
|
||||||
|
if (!confirmedOwnerLogin) {
|
||||||
|
try {
|
||||||
|
const identity = await fetchReviewJson('api/v1/background-identity');
|
||||||
|
const login = String(identity?.login || '').trim();
|
||||||
|
if (login) {
|
||||||
|
confirmedOwnerLogin = login;
|
||||||
|
planningOwnerLogin = login;
|
||||||
|
initialAccountRecovery = timerView.restore(todaySync.flush());
|
||||||
|
}
|
||||||
|
} catch (_error) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
if (progressiveWorkHandoff?.openWork) {
|
if (progressiveWorkHandoff?.openWork) {
|
||||||
const progressiveItem = lastMyWork.find(item =>
|
const progressiveItem = lastMyWork.find(item =>
|
||||||
item.repository === progressiveWorkHandoff.openWork.repository &&
|
item.repository === progressiveWorkHandoff.openWork.repository &&
|
||||||
|
|
@ -8615,6 +8636,8 @@
|
||||||
|
|
||||||
function widgetTick() { const el=qs('#widget-clock'); if(el) el.textContent = fmt(new Date()); }
|
function widgetTick() { const el=qs('#widget-clock'); if(el) el.textContent = fmt(new Date()); }
|
||||||
setInterval(widgetTick, 1000);
|
setInterval(widgetTick, 1000);
|
||||||
|
await initialAccountRecovery;
|
||||||
|
if (planningOwnerLogin) await todaySync.flush();
|
||||||
await timerView.restore(Promise.resolve(true));
|
await timerView.restore(Promise.resolve(true));
|
||||||
workspaceLifecycle.markWorkspaceReady?.();
|
workspaceLifecycle.markWorkspaceReady?.();
|
||||||
})();
|
})();
|
||||||
|
|
|
||||||
|
|
@ -489,7 +489,10 @@ function createTodayTimerView({ timer, isActive, queryAll, formatEstimate, getRu
|
||||||
const snapshot = timer.snapshot();
|
const snapshot = timer.snapshot();
|
||||||
breakView?.render();
|
breakView?.render();
|
||||||
const active = Boolean(progress && isActive() && snapshot.identity);
|
const active = Boolean(progress && isActive() && snapshot.identity);
|
||||||
queryAll('[data-mobile-today-hud]').forEach(element => { element.hidden = !active; });
|
queryAll('[data-mobile-today-hud]').forEach(element => {
|
||||||
|
const coaching = element.querySelector?.('[data-mobile-first-task-coach]:not([hidden])');
|
||||||
|
element.hidden = !active && !coaching;
|
||||||
|
});
|
||||||
queryAll('[data-mobile-today-open]').forEach(element => {
|
queryAll('[data-mobile-today-open]').forEach(element => {
|
||||||
element.textContent = active ? String(getItem?.(snapshot.identity)?.title || 'Current Today item') : '';
|
element.textContent = active ? String(getItem?.(snapshot.identity)?.title || 'Current Today item') : '';
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,7 @@ def test_release_artifact_guides_an_empty_mobile_account_to_first_work(
|
||||||
expect(sheet).to_be_hidden()
|
expect(sheet).to_be_hidden()
|
||||||
expect(page.locator("#find-work-sheet")).to_be_visible()
|
expect(page.locator("#find-work-sheet")).to_be_visible()
|
||||||
page.locator("#close-find-work").click()
|
page.locator("#close-find-work").click()
|
||||||
|
expect(page.locator("#find-work-sheet")).to_be_hidden()
|
||||||
|
|
||||||
context.set_offline(True)
|
context.set_offline(True)
|
||||||
page.locator('[data-mobile-task="work"]').click()
|
page.locator('[data-mobile-task="work"]').click()
|
||||||
|
|
@ -70,8 +71,9 @@ def test_release_artifact_guides_an_empty_mobile_account_to_first_work(
|
||||||
expect(page.locator("#mobile-first-task-create")).to_be_focused()
|
expect(page.locator("#mobile-first-task-create")).to_be_focused()
|
||||||
|
|
||||||
page.locator("#close-mobile-first-task").click()
|
page.locator("#close-mobile-first-task").click()
|
||||||
|
expect(sheet).to_be_hidden()
|
||||||
context.set_offline(False)
|
context.set_offline(False)
|
||||||
page.evaluate(
|
probe_result = page.evaluate(
|
||||||
"""() => {
|
"""() => {
|
||||||
document.querySelector('[data-mobile-today-hud]').hidden = false;
|
document.querySelector('[data-mobile-today-hud]').hidden = false;
|
||||||
localStorage.setItem('stackchain.first-task.v1:timmy', 'coaching');
|
localStorage.setItem('stackchain.first-task.v1:timmy', 'coaching');
|
||||||
|
|
@ -82,17 +84,54 @@ def test_release_artifact_guides_an_empty_mobile_account_to_first_work(
|
||||||
getLogin: () => 'timmy', hasWork: () => true, isTodayActive: () => true,
|
getLogin: () => 'timmy', hasWork: () => true, isTodayActive: () => true,
|
||||||
coach: isolatedCoach,
|
coach: isolatedCoach,
|
||||||
});
|
});
|
||||||
return window.firstTaskOutcomeProbe.refresh();
|
return {
|
||||||
|
result: window.firstTaskOutcomeProbe.refresh(),
|
||||||
|
state: localStorage.getItem('stackchain.first-task.v1:timmy'),
|
||||||
|
mobile: matchMedia('(max-width: 600px)').matches,
|
||||||
|
};
|
||||||
}"""
|
}"""
|
||||||
)
|
)
|
||||||
|
assert probe_result == {"result": "coaching", "state": "coaching", "mobile": True}
|
||||||
coach = page.locator("[data-mobile-first-task-coach]")
|
coach = page.locator("[data-mobile-first-task-coach]")
|
||||||
expect(coach).to_be_visible()
|
try:
|
||||||
|
expect(coach).to_be_visible()
|
||||||
|
except AssertionError as error:
|
||||||
|
raise AssertionError(page.evaluate("""() => {
|
||||||
|
const coach = document.querySelector('[data-mobile-first-task-coach]');
|
||||||
|
const hud = coach.closest('[data-mobile-today-hud]');
|
||||||
|
return {
|
||||||
|
coachHidden: coach.hidden,
|
||||||
|
coachDisplay: getComputedStyle(coach).display,
|
||||||
|
hudHidden: hud.hidden,
|
||||||
|
hudOverlay: hud.getAttribute('data-overlay-hidden'),
|
||||||
|
hudDisplay: getComputedStyle(hud).display,
|
||||||
|
open: Array.from(document.querySelectorAll('dialog, .open'))
|
||||||
|
.filter(element => element.open || element.classList.contains('open'))
|
||||||
|
.map(element => element.id || element.className),
|
||||||
|
};
|
||||||
|
}""")) from error
|
||||||
expect(coach).to_contain_text("Complete your first task")
|
expect(coach).to_contain_text("Complete your first task")
|
||||||
assert page.evaluate("document.documentElement.scrollWidth <= window.innerWidth")
|
assert page.evaluate("document.documentElement.scrollWidth <= window.innerWidth")
|
||||||
assert page.evaluate("window.firstTaskOutcomeProbe.completeOutcome()") is True
|
completion = page.evaluate("""() => {
|
||||||
|
const completed = window.firstTaskOutcomeProbe.completeOutcome();
|
||||||
|
return {
|
||||||
|
completed,
|
||||||
|
status: document.querySelector('#today-sync-status').textContent,
|
||||||
|
pending: Object.keys(localStorage).filter(key =>
|
||||||
|
key.startsWith('stackchain.today-sync.v1.timmy.operation.')),
|
||||||
|
};
|
||||||
|
}""")
|
||||||
|
assert completion["completed"] is True
|
||||||
|
assert completion["pending"]
|
||||||
|
assert "sync pending" in completion["status"]
|
||||||
expect(page.locator("#mobile-first-task-receipt")).to_be_visible()
|
expect(page.locator("#mobile-first-task-receipt")).to_be_visible()
|
||||||
expect(coach).to_be_hidden()
|
expect(coach).to_be_hidden()
|
||||||
expect(page.locator("#today-sync-status")).to_contain_text("Today saved to account")
|
expect(page.locator("#today-sync-status")).to_contain_text("Today saved to account")
|
||||||
|
remote_activation = page.evaluate("""async () => {
|
||||||
|
const response = await fetch('api/v1/today');
|
||||||
|
return (await response.json()).first_task_state;
|
||||||
|
}""")
|
||||||
|
assert remote_activation == "complete"
|
||||||
assert page.evaluate("document.documentElement.scrollWidth <= window.innerWidth")
|
assert page.evaluate("document.documentElement.scrollWidth <= window.innerWidth")
|
||||||
|
|
||||||
fresh_context = browser.new_context(
|
fresh_context = browser.new_context(
|
||||||
|
|
|
||||||
|
|
@ -278,7 +278,16 @@ def test_release_artifact_plans_hands_off_and_opens_next_mobile_issue(tmp_path:
|
||||||
assert page.evaluate("""() => !Object.keys(localStorage).some(
|
assert page.evaluate("""() => !Object.keys(localStorage).some(
|
||||||
key => key.startsWith('stackchain.today-sync.v1.timmy.operation.')
|
key => key.startsWith('stackchain.today-sync.v1.timmy.operation.')
|
||||||
)""")
|
)""")
|
||||||
expect(page.locator("#today-break-status")).to_contain_text("On break · resume in")
|
try:
|
||||||
|
expect(page.locator("#today-break-status")).to_contain_text("On break · resume in")
|
||||||
|
except AssertionError as error:
|
||||||
|
raise AssertionError(page.evaluate("""() => ({
|
||||||
|
login: document.querySelector('#current-user')?.textContent,
|
||||||
|
timers: Object.fromEntries(Object.keys(localStorage)
|
||||||
|
.filter(key => key.startsWith('stackchain.today-timer.v1.'))
|
||||||
|
.map(key => [key, JSON.parse(localStorage.getItem(key))])),
|
||||||
|
status: document.querySelector('#today-break-status').outerHTML,
|
||||||
|
})""")) from error
|
||||||
if page.locator("#issue-sheet").get_attribute("class") == "issue-sheet open":
|
if page.locator("#issue-sheet").get_attribute("class") == "issue-sheet open":
|
||||||
page.locator("#close-issue-sheet").click()
|
page.locator("#close-issue-sheet").click()
|
||||||
if page.locator("#plan-today-sheet").is_visible():
|
if page.locator("#plan-today-sheet").is_visible():
|
||||||
|
|
|
||||||
|
|
@ -117,6 +117,32 @@ const poller = createContextPoller({{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_context_poller_can_force_a_full_identity_refresh_after_adoption():
|
||||||
|
script = f"""
|
||||||
|
const createContextPoller = require({json.dumps(str(POLLER))});
|
||||||
|
const requested = [];
|
||||||
|
const seed = {{
|
||||||
|
context:{{user:{{login:'timmy'}}}}, events:[], notifications:[],
|
||||||
|
revisions:{{context:'0123456789abcdef.1',events:'fedcba9876543210.2'}},
|
||||||
|
}};
|
||||||
|
const poller = createContextPoller({{
|
||||||
|
fetchContext: revisions => {{
|
||||||
|
requested.push({{...revisions}});
|
||||||
|
return Promise.resolve({{...seed, freshness:{{sections:{{context:{{degraded:false}}}}}}}});
|
||||||
|
}},
|
||||||
|
onSnapshot: () => {{}}, onError: error => {{ throw error; }},
|
||||||
|
setTimer: () => 1, clearTimer: () => {{}},
|
||||||
|
}});
|
||||||
|
poller.adopt(seed);
|
||||||
|
(async () => {{
|
||||||
|
await poller.refresh({{force:true, full:true}});
|
||||||
|
process.stdout.write(JSON.stringify({{requested}}));
|
||||||
|
}})();
|
||||||
|
"""
|
||||||
|
|
||||||
|
assert run_node(script) == {"requested": [{}]}
|
||||||
|
|
||||||
|
|
||||||
def test_context_poller_adopts_progressive_snapshot_before_revision_conditional_refresh():
|
def test_context_poller_adopts_progressive_snapshot_before_revision_conditional_refresh():
|
||||||
script = f"""
|
script = f"""
|
||||||
const createContextPoller = require({json.dumps(str(POLLER))});
|
const createContextPoller = require({json.dumps(str(POLLER))});
|
||||||
|
|
@ -200,6 +226,9 @@ def test_dashboard_adopts_progressive_snapshot_or_falls_back_to_immediate_load()
|
||||||
assert "let adoptedProgressiveSnapshot = contextPoller.adopt(progressiveWorkHandoff?.liveSnapshot);" in source
|
assert "let adoptedProgressiveSnapshot = contextPoller.adopt(progressiveWorkHandoff?.liveSnapshot);" in source
|
||||||
assert "contextPoller.adoptPending(progressiveWorkHandoff.liveSnapshotPromise)" in source
|
assert "contextPoller.adoptPending(progressiveWorkHandoff.liveSnapshotPromise)" in source
|
||||||
assert "if (!adoptedProgressiveSnapshot) await load();" in source
|
assert "if (!adoptedProgressiveSnapshot) await load();" in source
|
||||||
|
assert "contextPoller.refresh({ force:true, full:true })" in source
|
||||||
|
assert "fetchReviewJson('api/v1/background-identity')" in source
|
||||||
|
assert "initialAccountRecovery = timerView.restore(todaySync.flush());" in source
|
||||||
|
|
||||||
|
|
||||||
def test_context_poller_uses_failed_section_retry_before_healthy_freshness_deadline():
|
def test_context_poller_uses_failed_section_retry_before_healthy_freshness_deadline():
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user