feat: launch mobile work from PWA shortcuts (Closes #737)
CI / lint (pull_request) Successful in 1m31s
CI / build-release (pull_request) Successful in 5s
CI / release-candidate (pull_request) Has been skipped

This commit is contained in:
timmy committed 2026-08-13 12:26:27 +00:00
1 parent e7d5196987
commit bb1712279c
10 files changed
+135

No files matched your search

+15
View File
@@ -876,6 +876,21 @@ async def test_anonymous_share_target_redirect_preserves_only_bounded_capture_fi
assert response.headers["cache-control"] == "no-store"
@pytest.mark.anyio
async def test_anonymous_app_shortcut_preserves_only_a_known_launch_action(access_control):
transport = httpx.ASGITransport(app=main.app)
async with httpx.AsyncClient(transport=transport, base_url="https://test") as client:
valid = await client.get("/", params={"launch": "agenda", "next": "https://evil.example"})
invalid = await client.get("/", params={"launch": "delete-account"})
assert valid.status_code == 303
assert parse_qs(urlsplit(valid.headers["location"]).query) == {
"continue": ["./?launch=agenda"]
}
assert invalid.status_code == 303
assert invalid.headers["location"] == "login"
@pytest.mark.anyio
async def test_oversized_share_target_is_not_carried_through_login(access_control):
transport = httpx.ASGITransport(app=main.app)
+32
View File
@@ -8,6 +8,33 @@ from tests.dashboard_bundle import dashboard
MOBILE_LAUNCH = Path(__file__).resolve().parents[1] / "frontend" / "mobile-launch.js"
APP_SHORTCUTS = Path(__file__).resolve().parents[1] / "frontend" / "mobile-app-shortcuts.js"
def test_installed_app_shortcut_accepts_only_bounded_actions_and_runs_once():
script = f"""
const shortcuts = require({json.dumps(str(APP_SHORTCUTS))});
const calls = [];
const controller = shortcuts.createController({{
search:'?launch=continue',
continueWork:() => calls.push('continue'),
newIssue:() => calls.push('new'),
agenda:() => calls.push('agenda'),
}});
Promise.resolve(controller.run()).then(() => controller.run()).then(() => {{
process.stdout.write(JSON.stringify({{
action:controller.action(), calls,
invalid:shortcuts.parse('?launch=delete-account'),
shared:shortcuts.parse('?launch=new&title=Shared'),
}}));
}});
"""
result = subprocess.run(["node", "-e", script], capture_output=True, text=True)
assert result.returncode == 0, result.stderr
assert json.loads(result.stdout) == {
"action": "continue", "calls": ["continue"], "invalid": None, "shared": "new"
}
def test_mobile_launch_selects_priority_queue_without_overriding_saved_choice():
@@ -63,8 +90,13 @@ async def test_mobile_launch_progressively_discloses_secondary_controls():
assert 'id="work-settings-toggle"' in html
assert 'id="active-work-queue"' in html
assert '<script src="static/mobile-launch.js"></script>' in html
assert '<script src="static/mobile-app-shortcuts.js"></script>' in html
service_worker = (Path(__file__).resolve().parents[1] / "frontend" / "service-worker.js").read_text()
assert "BASE + 'static/mobile-launch.js'" in service_worker
assert "BASE + 'static/mobile-app-shortcuts.js'" in service_worker
bootstrap = (Path(__file__).resolve().parents[1] / "frontend" / "dashboard.js").read_text()
assert "mobileAppShortcuts.createController" in bootstrap
assert bootstrap.index("await load();\n await appShortcut.run();") < bootstrap.index("contextPoller.start();")
assert "mobileLaunch.chooseFilter" in html
assert "agenda: counts.agenda" in html
assert "mobileLaunch.createDisclosure" in html
+1
View File
@@ -788,6 +788,7 @@ def test_install_precaches_complete_subpath_scoped_app_shell():
"/dashboard/static/mobile-queue-launcher.js",
"/dashboard/static/agenda-session-launcher.js",
"/dashboard/static/mobile-launch.js",
"/dashboard/static/mobile-app-shortcuts.js",
"/dashboard/static/install-app.js",
"/dashboard/static/mobile-device-setup.js",
"/dashboard/static/mobile-search-viewport.js",
+20
View File
@@ -82,6 +82,26 @@ async def test_pwa_assets_expose_root_scoped_share_target_without_caching_api_da
"action": "./", "method": "GET", "enctype": "application/x-www-form-urlencoded",
"params": {"title": "title", "text": "text", "url": "url"},
}
assert manifest.json()["shortcuts"] == [
{
"name": "Continue work", "short_name": "Continue",
"description": "Resume the highest-priority mobile work flow.",
"url": "./?launch=continue",
"icons": [{"src": "static/icons/stackchain-192.png", "sizes": "192x192", "type": "image/png"}],
},
{
"name": "New issue", "short_name": "New",
"description": "Capture work now and file it online or offline.",
"url": "./?launch=new",
"icons": [{"src": "static/icons/stackchain-192.png", "sizes": "192x192", "type": "image/png"}],
},
{
"name": "Open Agenda", "short_name": "Agenda",
"description": "Check every assigned deadline and continue the Agenda session.",
"url": "./?launch=agenda",
"icons": [{"src": "static/icons/stackchain-192.png", "sizes": "192x192", "type": "image/png"}],
},
]
assert worker.status_code == 200
assert worker.headers["content-type"].startswith("application/javascript")
assert worker.headers["service-worker-allowed"] == "/"