feat: lazy-load security center (Closes #605)
All checks were successful
CI / lint (pull_request) Successful in 1m31s
CI / build-release (pull_request) Successful in 6s
CI / release-candidate (pull_request) Has been skipped

This commit is contained in:
timmy 2026-08-12 01:00:32 +00:00
parent 17823352f2
commit fb58ba30b2
11 changed files with 308 additions and 186 deletions

View File

@ -47,7 +47,8 @@ function createFeatureLoader({ document, urls, timeoutMs = 10000 }) {
} catch (_error) {
if (status) {
const label = name === 'issue-capture' ? 'Issue capture' :
name === 'pull-workflow' ? 'Pull workspace' : name.replace(/-/g, ' ');
name === 'pull-workflow' ? 'Pull workspace' :
name === 'security-center' ? 'Security center' : name.replace(/-/g, ' ');
status.textContent = label + ' could not load. ' + retryLabel;
}
return false;

View File

@ -831,6 +831,7 @@
<script src="static/session.js"></script>
<script src="static/feature-loader.js"></script>
<script src="static/security-center.js"></script>
<script src="static/markdown.js"></script>
<script src="static/commands.js"></script>
<script src="static/search-preview.js"></script>

185
frontend/security-center.js Normal file
View File

@ -0,0 +1,185 @@
(function (root, factory) {
if (typeof module !== 'undefined' && module.exports) module.exports = factory;
else root.attachSecurityCenter = boundary => factory({ root, boundary });
})(typeof window !== 'undefined' ? window : this, function attachSecurityCenter({ root, boundary }) {
const devicesButton = root.document.getElementById('active-devices');
const devicesSheet = root.document.getElementById('active-devices-sheet');
const devicesList = root.document.getElementById('active-devices-list');
const devicesStatus = root.document.getElementById('active-devices-status');
const passkeysList = root.document.getElementById('enrolled-passkeys-list');
const passkeysStatus = root.document.getElementById('enrolled-passkeys-status');
const enrollPasskey = root.document.getElementById('enroll-passkey');
const activityList = root.document.getElementById('security-activity-list');
const activityStatus = root.document.getElementById('security-activity-status');
const loadMoreActivity = root.document.getElementById('load-more-security-activity');
let activityCursor = null;
const renderDevices = async () => {
devicesStatus.textContent = 'Loading active devices…';
devicesList.replaceChildren();
try {
const devices = await boundary.listActiveDevices();
devices.forEach(device => {
const row = root.document.createElement('article');
row.className = 'active-device';
const details = root.document.createElement('div');
const label = root.document.createElement('strong');
label.textContent = device.device_label;
const timing = root.document.createElement('span');
timing.className = 'small muted';
timing.textContent = `Signed in ${new Date(device.created_at * 1000).toLocaleString()} · expires ${new Date(device.expires_at * 1000).toLocaleString()}`;
details.append(label, timing);
if (device.current) {
const current = root.document.createElement('span');
current.className = 'active-device-current';
current.textContent = 'This device';
details.append(current);
}
const revoke = root.document.createElement('button');
revoke.type = 'button';
revoke.textContent = device.current ? 'Sign out' : 'Revoke';
revoke.addEventListener('click', async () => {
if (device.current) await boundary.signOut();
else if (await boundary.revokeActiveDevice(device)) await renderDevices();
});
row.append(details, revoke);
devicesList.append(row);
});
devicesStatus.textContent = devices.length ? `${devices.length} active device${devices.length === 1 ? '' : 's'}` : 'No active devices.';
} catch (_error) {
devicesStatus.textContent = 'Active devices could not be loaded. Try again.';
}
};
const renderPasskeys = async () => {
if (!passkeysList || !passkeysStatus) return;
passkeysStatus.textContent = 'Loading enrolled passkeys…';
passkeysList.replaceChildren();
try {
const enrolled = await boundary.listPasskeys();
enrolled.forEach(passkey => {
const row = root.document.createElement('article');
row.className = 'enrolled-passkey';
const details = root.document.createElement('div');
const label = root.document.createElement('strong');
label.textContent = passkey.device_label;
const timing = root.document.createElement('span');
timing.className = 'small muted';
const state = passkey.current ? 'This active device' : (passkey.active ? 'Active device' : 'No active session');
timing.textContent = `Enrolled ${new Date(passkey.created_at * 1000).toLocaleString()} · ${state}`;
details.append(label, timing);
const remove = root.document.createElement('button');
remove.type = 'button';
remove.textContent = 'Remove passkey';
remove.addEventListener('click', async () => {
remove.disabled = true;
passkeysStatus.textContent = `Removing passkey for ${passkey.device_label}`;
try {
const outcome = await boundary.revokePasskey(passkey);
if (outcome) {
passkeysStatus.textContent = outcome.current_session
? 'Passkey removed. This session remains active; keep your recovery token available.'
: 'Passkey removed.';
await renderPasskeys();
if (outcome.session_revoked) await renderDevices();
} else {
remove.disabled = false;
passkeysStatus.textContent = `${enrolled.length} enrolled passkey${enrolled.length === 1 ? '' : 's'}`;
}
} catch (_error) {
passkeysStatus.textContent = 'Passkey could not be removed. Refresh to verify before retrying.';
remove.disabled = false;
}
});
row.append(details, remove);
passkeysList.append(row);
});
passkeysStatus.textContent = enrolled.length
? `${enrolled.length} enrolled passkey${enrolled.length === 1 ? '' : 's'}`
: 'No passkeys enrolled.';
} catch (_error) {
passkeysStatus.textContent = 'Enrolled passkeys could not be loaded. Try again.';
}
};
const renderSecurityActivity = async (append = false) => {
if (!activityList || !activityStatus || !loadMoreActivity) return;
activityStatus.textContent = append ? 'Loading older activity…' : 'Loading security activity…';
loadMoreActivity.hidden = true;
if (!append) {
activityCursor = null;
activityList.replaceChildren();
}
try {
const page = await boundary.listSecurityEvents(append ? activityCursor : null);
if (!append) {
page.authentication_alerts.forEach(alert => {
const row = root.document.createElement('article');
row.className = 'security-event authentication-alert';
const formatted = boundary.formatAuthenticationAlert(alert);
const title = root.document.createElement('strong');
title.textContent = formatted.title;
const details = root.document.createElement('span');
details.className = 'small muted';
details.textContent = formatted.detail;
row.append(title, details);
activityList.append(row);
});
}
const labels = {
sign_in: 'Signed in', sign_out: 'Signed out', passkey_enrolled: 'Passkey enrolled',
device_revoked: 'Device access revoked', all_sessions_revoked: 'All device access revoked',
issue_closed: 'Issue closed', pull_merged: 'Pull request merged',
pull_review_approved: 'Pull request approved',
pull_review_changes_requested: 'Changes requested', gitea_time_logged: 'Gitea time logged',
};
page.events.forEach(event => {
const row = root.document.createElement('article');
row.className = 'security-event';
const title = root.document.createElement('strong');
title.textContent = labels[event.kind] || 'Security event';
const details = root.document.createElement('span');
details.className = 'small muted';
const context = [event.device_label, event.method, event.target,
event.status === 'pending' ? 'Outcome confirmation pending' : null]
.filter(value => typeof value === 'string' && value).join(' · ');
details.textContent = `${new Date(event.created_at * 1000).toLocaleString()}${context ? ' · ' + context : ''}`;
row.append(title, details);
activityList.append(row);
});
activityCursor = page.next_cursor;
activityStatus.textContent = activityList.children.length
? `${activityList.children.length} recent security event${activityList.children.length === 1 ? '' : 's'}`
: 'No security activity yet.';
loadMoreActivity.textContent = 'Load older activity';
loadMoreActivity.hidden = !activityCursor;
} catch (_error) {
activityStatus.textContent = 'Security activity could not be loaded.';
loadMoreActivity.textContent = 'Retry activity';
loadMoreActivity.hidden = false;
}
};
const open = () => {
if (!devicesSheet) return;
devicesSheet.hidden = false;
root.document.getElementById('close-active-devices')?.focus();
return Promise.all([renderDevices(), renderPasskeys(), renderSecurityActivity()]);
};
devicesButton?.addEventListener('click', open);
loadMoreActivity?.addEventListener('click', () => renderSecurityActivity(Boolean(activityCursor)));
enrollPasskey?.addEventListener('click', async () => {
enrollPasskey.disabled = true;
devicesStatus.textContent = 'Waiting for your device passkey…';
try {
await boundary.enrollPasskey();
devicesStatus.textContent = 'Passkey enrolled. You can use it at sign-in and authorization prompts.';
await renderPasskeys();
} catch (_error) {
devicesStatus.textContent = 'Passkey enrollment was not completed. Try again.';
} finally {
enrollPasskey.disabled = false;
}
});
return { open, renderDevices, renderPasskeys, renderSecurityActivity };
});

View File

@ -14,6 +14,7 @@ const SHELL = [
BASE + 'static/icons/stackchain-512.png',
BASE + 'static/session.js',
BASE + 'static/feature-loader.js',
BASE + 'static/security-center.js',
BASE + 'static/markdown.js',
BASE + 'static/commands.js',
BASE + 'static/search-preview.js',

View File

@ -36,192 +36,42 @@
if (allDevicesButton) allDevicesButton.addEventListener('click', () => boundary.signOutAllDevices());
const devicesButton = root.document.getElementById('active-devices');
const devicesSheet = root.document.getElementById('active-devices-sheet');
const devicesList = root.document.getElementById('active-devices-list');
const devicesStatus = root.document.getElementById('active-devices-status');
const passkeysList = root.document.getElementById('enrolled-passkeys-list');
const passkeysStatus = root.document.getElementById('enrolled-passkeys-status');
const closeDevices = root.document.getElementById('close-active-devices');
const enrollPasskey = root.document.getElementById('enroll-passkey');
const activityList = root.document.getElementById('security-activity-list');
const activityStatus = root.document.getElementById('security-activity-status');
const loadMoreActivity = root.document.getElementById('load-more-security-activity');
let activityCursor = null;
const renderDevices = async () => {
devicesStatus.textContent = 'Loading active devices…';
devicesList.replaceChildren();
try {
const devices = await boundary.listActiveDevices();
devices.forEach(device => {
const row = root.document.createElement('article');
row.className = 'active-device';
const details = root.document.createElement('div');
const label = root.document.createElement('strong');
label.textContent = device.device_label;
const timing = root.document.createElement('span');
timing.className = 'small muted';
timing.textContent = `Signed in ${new Date(device.created_at * 1000).toLocaleString()} · expires ${new Date(device.expires_at * 1000).toLocaleString()}`;
details.append(label, timing);
if (device.current) {
const current = root.document.createElement('span');
current.className = 'active-device-current';
current.textContent = 'This device';
details.append(current);
}
const revoke = root.document.createElement('button');
revoke.type = 'button';
revoke.textContent = device.current ? 'Sign out' : 'Revoke';
revoke.addEventListener('click', async () => {
if (device.current) await boundary.signOut();
else if (await boundary.revokeActiveDevice(device)) await renderDevices();
const securityFeatures = root.createFeatureLoader({
document: root.document,
urls: {
'security-center': root.document.querySelector(
'meta[name="stackchain-feature-security-center"]'
)?.content || '',
},
});
row.append(details, revoke);
devicesList.append(row);
});
devicesStatus.textContent = devices.length ? `${devices.length} active device${devices.length === 1 ? '' : 's'}` : 'No active devices.';
} catch (_error) {
devicesStatus.textContent = 'Active devices could not be loaded. Try again.';
}
};
const renderPasskeys = async () => {
if (!passkeysList || !passkeysStatus) return;
passkeysStatus.textContent = 'Loading enrolled passkeys…';
passkeysList.replaceChildren();
try {
const enrolled = await boundary.listPasskeys();
enrolled.forEach(passkey => {
const row = root.document.createElement('article');
row.className = 'enrolled-passkey';
const details = root.document.createElement('div');
const label = root.document.createElement('strong');
label.textContent = passkey.device_label;
const timing = root.document.createElement('span');
timing.className = 'small muted';
const state = passkey.current ? 'This active device' : (passkey.active ? 'Active device' : 'No active session');
timing.textContent = `Enrolled ${new Date(passkey.created_at * 1000).toLocaleString()} · ${state}`;
details.append(label, timing);
const remove = root.document.createElement('button');
remove.type = 'button';
remove.textContent = 'Remove passkey';
remove.addEventListener('click', async () => {
remove.disabled = true;
passkeysStatus.textContent = `Removing passkey for ${passkey.device_label}`;
try {
const outcome = await boundary.revokePasskey(passkey);
if (outcome) {
passkeysStatus.textContent = outcome.current_session
? 'Passkey removed. This session remains active; keep your recovery token available.'
: 'Passkey removed.';
await renderPasskeys();
if (outcome.session_revoked) await renderDevices();
} else {
remove.disabled = false;
passkeysStatus.textContent = `${enrolled.length} enrolled passkey${enrolled.length === 1 ? '' : 's'}`;
}
} catch (_error) {
passkeysStatus.textContent = 'Passkey could not be removed. Refresh to verify before retrying.';
remove.disabled = false;
}
});
row.append(details, remove);
passkeysList.append(row);
});
passkeysStatus.textContent = enrolled.length
? `${enrolled.length} enrolled passkey${enrolled.length === 1 ? '' : 's'}`
: 'No passkeys enrolled.';
} catch (_error) {
passkeysStatus.textContent = 'Enrolled passkeys could not be loaded. Try again.';
}
};
const renderSecurityActivity = async (append = false) => {
if (!activityList || !activityStatus || !loadMoreActivity) return;
activityStatus.textContent = append ? 'Loading older activity…' : 'Loading security activity…';
loadMoreActivity.hidden = true;
if (!append) {
activityCursor = null;
activityList.replaceChildren();
}
try {
const page = await boundary.listSecurityEvents(append ? activityCursor : null);
if (!append) {
page.authentication_alerts.forEach(alert => {
const row = root.document.createElement('article');
row.className = 'security-event authentication-alert';
const formatted = boundary.formatAuthenticationAlert(alert);
const title = root.document.createElement('strong');
title.textContent = formatted.title;
const details = root.document.createElement('span');
details.className = 'small muted';
details.textContent = formatted.detail;
row.append(title, details);
activityList.append(row);
});
}
const labels = {
sign_in: 'Signed in',
sign_out: 'Signed out',
passkey_enrolled: 'Passkey enrolled',
device_revoked: 'Device access revoked',
all_sessions_revoked: 'All device access revoked',
issue_closed: 'Issue closed',
pull_merged: 'Pull request merged',
pull_review_approved: 'Pull request approved',
pull_review_changes_requested: 'Changes requested',
gitea_time_logged: 'Gitea time logged',
};
page.events.forEach(event => {
const row = root.document.createElement('article');
row.className = 'security-event';
const title = root.document.createElement('strong');
title.textContent = labels[event.kind] || 'Security event';
const details = root.document.createElement('span');
details.className = 'small muted';
const context = [
event.device_label,
event.method,
event.target,
event.status === 'pending' ? 'Outcome confirmation pending' : null,
].filter(value => typeof value === 'string' && value).join(' · ');
details.textContent = `${new Date(event.created_at * 1000).toLocaleString()}${context ? ' · ' + context : ''}`;
row.append(title, details);
activityList.append(row);
});
activityCursor = page.next_cursor;
activityStatus.textContent = activityList.children.length
? `${activityList.children.length} recent security event${activityList.children.length === 1 ? '' : 's'}`
: 'No security activity yet.';
loadMoreActivity.textContent = 'Load older activity';
loadMoreActivity.hidden = !activityCursor;
} catch (_error) {
activityStatus.textContent = 'Security activity could not be loaded.';
loadMoreActivity.textContent = 'Retry activity';
loadMoreActivity.hidden = false;
}
};
loadMoreActivity?.addEventListener('click', () => renderSecurityActivity(Boolean(activityCursor)));
if (devicesButton && devicesSheet) devicesButton.addEventListener('click', () => {
let loadingSecurityCenter = false;
const openSecurityCenter = async () => {
if (loadingSecurityCenter) return;
loadingSecurityCenter = true;
devicesSheet.hidden = false;
closeDevices?.focus();
renderDevices();
renderPasskeys();
renderSecurityActivity();
try {
await securityFeatures.run('security-center', {
trigger: devicesButton,
status: devicesStatus,
retryLabel: 'Tap Active devices to retry.',
}, () => {
devicesButton.removeEventListener('click', openSecurityCenter);
root.attachSecurityCenter(boundary).open();
});
} finally {
loadingSecurityCenter = false;
}
};
if (devicesButton && devicesSheet) {
devicesButton.addEventListener('click', openSecurityCenter);
}
if (closeDevices && devicesSheet) closeDevices.addEventListener('click', () => {
devicesSheet.hidden = true;
devicesButton?.focus();
});
enrollPasskey?.addEventListener('click', async () => {
enrollPasskey.disabled = true;
devicesStatus.textContent = 'Waiting for your device passkey…';
try {
await boundary.enrollPasskey();
devicesStatus.textContent = 'Passkey enrolled. You can use it at sign-in and authorization prompts.';
await renderPasskeys();
} catch (_error) {
devicesStatus.textContent = 'Passkey enrollment was not completed. Try again.';
} finally {
enrollPasskey.disabled = false;
}
});
boundary.refreshOfflineLease().then(valid => {
if (valid) boundary.resumeQueuedWork();
});

View File

@ -26,6 +26,7 @@ FEATURE_SOURCES = {
"pull-workflow": ("static/pull-sheet.js", "static/review-sheet.js"),
"push-notifications": ("static/push-notifications.js",),
"device-setup": ("static/install-app.js", "static/mobile-device-setup.js"),
"security-center": ("static/security-center.js",),
"today-timer": (
"static/mobile-task-dock.js", "static/today-timer.js", "static/today-recap.js",
"static/today-rollover.js",

View File

@ -9,6 +9,7 @@ from tests.dashboard_bundle import dashboard
ROOT = Path(__file__).resolve().parents[1]
SESSION_JS = ROOT / "frontend" / "session.js"
SECURITY_CENTER_JS = ROOT / "frontend" / "security-center.js"
def run_session_scenario(scenario: str) -> dict:
@ -753,7 +754,7 @@ process.stdout.write(JSON.stringify(state));
def test_security_activity_renders_authentication_alerts_before_journal_events():
source = SESSION_JS.read_text()
source = SECURITY_CENTER_JS.read_text()
alerts = source.index("page.authentication_alerts.forEach")
events = source.index("page.events.forEach")
@ -765,14 +766,14 @@ def test_security_activity_renders_authentication_alerts_before_journal_events()
def test_security_activity_explains_pending_outcome_confirmation():
source = SESSION_JS.read_text()
source = SECURITY_CENTER_JS.read_text()
assert "Outcome confirmation pending" in source
assert "event.status === 'pending'" in source
def test_security_activity_labels_passkey_enrollment_without_html_rendering():
source = SESSION_JS.read_text()
source = SECURITY_CENTER_JS.read_text()
assert "passkey_enrolled: 'Passkey enrolled'" in source
assert "title.textContent = labels[event.kind]" in source
@ -780,7 +781,7 @@ def test_security_activity_labels_passkey_enrollment_without_html_rendering():
def test_security_activity_labels_consequential_pull_review_decisions():
source = SESSION_JS.read_text()
source = SECURITY_CENTER_JS.read_text()
assert "pull_review_approved: 'Pull request approved'" in source
assert "pull_review_changes_requested: 'Changes requested'" in source
@ -818,6 +819,6 @@ async def test_dashboard_loads_session_boundary_first_and_offers_sign_out():
def test_security_activity_names_confirmed_gitea_time_logging():
source = SESSION_JS.read_text()
source = SECURITY_CENTER_JS.read_text()
assert "gitea_time_logged: 'Gitea time logged'" in source

View File

@ -89,3 +89,22 @@ console.log(JSON.stringify({first,second,failedStatus,opened,appends:state.appen
"opened": 1,
"appends": 2,
}
def test_security_center_failure_is_accessible_and_retryable_from_active_devices():
result = run_loader("""
const trigger={disabled:false}; const status={textContent:''}; let opened=0;
const loader=createFeatureLoader({document, urls:{'security-center':'feature-security.js'}, timeoutMs:100});
const failed=loader.run('security-center',{trigger,status,retryLabel:'Tap Active devices to retry.'},()=>{opened++;});
state.node.onerror(); const first=await failed; const failedStatus=status.textContent;
const retry=loader.run('security-center',{trigger,status,retryLabel:'Tap Active devices to retry.'},()=>{opened++;});
state.node.onload(); const second=await retry;
console.log(JSON.stringify({first,second,failedStatus,opened,appends:state.appends}));
""")
assert result == {
"first": False,
"second": True,
"failedStatus": "Security center could not load. Tap Active devices to retry.",
"opened": 1,
"appends": 2,
}

View File

@ -49,7 +49,7 @@ def test_product_workflows_are_stable_lazy_feature_chunks(tmp_path):
assert set(first.feature_bundles) == {
"comment-actions", "issue-capture", "pull-workflow", "push-notifications", "device-setup",
"today-timer",
"today-timer", "security-center",
}
assert (
f'<script src="{first.feature_bundles["today-timer"].runtime_name}"></script>\n'
@ -63,12 +63,19 @@ def test_product_workflows_are_stable_lazy_feature_chunks(tmp_path):
assert b"function createReviewController" not in first.runtime_bytes
assert b"function createPullSheet" in pull_workflow.runtime_bytes
assert b"function createReviewController" in pull_workflow.runtime_bytes
security_center = first.feature_bundles["security-center"]
assert b"function attachSecurityCenter" not in first.runtime_bytes
assert b"function attachSecurityCenter" in security_center.runtime_bytes
assert b"gitea_time_logged" not in first.runtime_bytes
assert b"gitea_time_logged" in security_center.runtime_bytes
# The recap adds only startup wiring; its UI remains in the lazy Today bundle.
assert len(first.runtime_gzip_bytes) <= 96 * 1024
assert len(first.runtime_gzip_bytes) <= 95 * 1024
assert f'name="stackchain-feature-issue-capture" content="{capture.runtime_name}"' in first.dashboard_html
assert f'name="stackchain-feature-pull-workflow" content="{pull_workflow.runtime_name}"' in first.dashboard_html
assert f"BASE + '{capture.runtime_name}'" in first.service_worker_source
assert f"BASE + '{pull_workflow.runtime_name}'" in first.service_worker_source
assert f'name="stackchain-feature-security-center" content="{security_center.runtime_name}"' in first.dashboard_html
assert f"BASE + '{security_center.runtime_name}'" in first.service_worker_source
changed_frontend = tmp_path / "frontend"
shutil.copytree(FRONTEND, changed_frontend)
@ -85,6 +92,12 @@ def test_product_workflows_are_stable_lazy_feature_chunks(tmp_path):
assert pull_changed.runtime_name == first.runtime_name
assert pull_changed.feature_bundles["pull-workflow"].runtime_name != pull_workflow.runtime_name
security_source = changed_frontend / "security-center.js"
security_source.write_text(security_source.read_text() + "\n// security-center-only change\n")
security_changed = build_frontend(changed_frontend)
assert security_changed.runtime_name == first.runtime_name
assert security_changed.feature_bundles["security-center"].runtime_name != security_center.runtime_name
def test_shipped_browser_bundles_are_valid_javascript(tmp_path):
build = build_frontend(FRONTEND)

View File

@ -0,0 +1,49 @@
import json
import subprocess
from pathlib import Path
SECURITY_CENTER = Path(__file__).parents[1] / "frontend" / "security-center.js"
def test_open_security_center_loads_all_sections_concurrently_and_is_awaitable():
harness = f"""
const attachSecurityCenter=require({json.dumps(str(SECURITY_CENTER))});
const calls=[];
const element=()=>({{
hidden:true, disabled:false, textContent:'', children:[],
addEventListener(){{}}, focus(){{this.focused=true;}},
replaceChildren(){{this.children=[];}}, append(value){{this.children.push(value);}},
}});
const ids={{
'active-devices':element(), 'active-devices-sheet':element(),
'active-devices-list':element(), 'active-devices-status':element(),
'enrolled-passkeys-list':element(), 'enrolled-passkeys-status':element(),
'enroll-passkey':element(), 'security-activity-list':element(),
'security-activity-status':element(), 'load-more-security-activity':element(),
'close-active-devices':element(),
}};
const root={{document:{{getElementById:id=>ids[id]||null,createElement:()=>element()}}}};
const boundary={{
listActiveDevices:async()=>{{calls.push('devices');return[];}},
listPasskeys:async()=>{{calls.push('passkeys');return[];}},
listSecurityEvents:async()=>{{calls.push('activity');return{{events:[],authentication_alerts:[],next_cursor:null}};}},
}};
(async()=>{{
const controller=attachSecurityCenter({{root,boundary}});
const loading=controller.open();
const awaitable=Boolean(loading&&typeof loading.then==='function');
if (loading) await loading;
console.log(JSON.stringify({{calls,awaitable,hidden:ids['active-devices-sheet'].hidden,focused:ids['close-active-devices'].focused||false}}));
}})().catch(error=>{{console.error(error);process.exit(1);}});
"""
completed = subprocess.run(
["node", "-e", harness], check=True, capture_output=True, text=True
)
assert json.loads(completed.stdout) == {
"calls": ["devices", "passkeys", "activity"],
"awaitable": True,
"hidden": False,
"focused": True,
}

View File

@ -661,6 +661,7 @@ def test_install_precaches_complete_subpath_scoped_app_shell():
"/dashboard/static/icons/stackchain-512.png",
"/dashboard/static/session.js",
"/dashboard/static/feature-loader.js",
"/dashboard/static/security-center.js",
"/dashboard/static/markdown.js",
"/dashboard/static/commands.js",
"/dashboard/static/search-preview.js",