feat: make mobile Home task-first with Insights (Closes #994)
CI / lint (pull_request) Successful in 3m2s
CI / build-release (pull_request) Successful in 7s
CI / browser-journey (pull_request) Successful in 2m8s
CI / release-candidate (pull_request) Has been skipped

This commit is contained in:
timmy committed 2026-08-17 00:08:48 +00:00
1 parent 0f30ca94ab
commit 0fcb7f5152
17 files changed
+310 -25

No files matched your search

+9
View File
@@ -26,6 +26,7 @@ header { position: sticky; top: 0; z-index: 20; padding: 12px 16px; display:flex
.app-menu { margin-left:auto; }
.app-menu > summary { display:none; }
.app-menu-panel { display:flex; gap:10px; align-items:center; flex-wrap:wrap; }
#open-insights { display:none; }
button { background: linear-gradient(180deg,#1f3a5f,#15324d); border:1px solid #2a496e; color:#e5e7eb; padding:8px 12px; border-radius:10px; cursor:pointer; }
.notification-undo { position:fixed; z-index:110; left:50%; bottom:calc(88px + env(safe-area-inset-bottom)); transform:translateX(-50%); box-sizing:border-box; width:min(520px,calc(100vw - 24px)); display:flex; align-items:center; justify-content:space-between; gap:12px; padding:10px 12px; border:1px solid #60a5fa; border-radius:12px; background:#10233d; box-shadow:0 12px 36px rgba(0,0,0,.5); overflow-wrap:anywhere; }
.notification-undo[hidden] { display:none; }
@@ -91,6 +92,8 @@ button:hover { filter: brightness(1.15); }
.panel[open] > summary { margin-bottom: 8px; }
aside, section { position: relative; z-index: 1; }
main { position: relative; z-index: 1; display: grid; grid-template-columns: 300px 1fr 340px; gap: 14px; padding: 14px; }
.insights-shell { display:contents; }
.insights-header { display:none; }
@media (max-width: 1100px) { main { grid-template-columns: 1fr; } }
.sidebar { display:flex; flex-direction:column; gap: 12px; }
.stack { display:flex; flex-direction:column; gap:10px; }
@@ -932,6 +935,12 @@ textarea { resize: vertical; min-height: 120px; }
.app-menu:not([open]) > .app-menu-panel { display:none; }
.app-menu-panel { position:absolute; top:calc(100% + 6px); right:0; width:min(280px,calc(100vw - 20px)); display:grid; gap:8px; padding:10px; border:1px solid #2a496e; border-radius:12px; background:#0b1526; box-shadow:0 18px 45px rgba(0,0,0,.5); }
.app-menu-panel button { min-height:44px; width:100%; }
#open-insights { display:block; }
.insights-shell { display:none; }
.insights-shell[data-mobile-open="true"] { position:fixed; inset:0; z-index:90; display:grid; align-content:start; gap:12px; overflow:auto; overflow-x:hidden; padding:max(12px,env(safe-area-inset-top)) max(12px,env(safe-area-inset-right)) max(12px,env(safe-area-inset-bottom)) max(12px,env(safe-area-inset-left)); background:#050c15; }
.insights-header { position:sticky; top:0; z-index:2; display:flex; align-items:flex-start; justify-content:space-between; gap:12px; padding:10px; border:1px solid #2a496e; border-radius:12px; background:#0b1526; }
.insights-header h2, .insights-header p { margin:0 0 4px; }
.insights-header button { min-height:44px; flex:none; }
.device-setup-panel { width:100%; padding:14px; padding-bottom:calc(14px + env(safe-area-inset-bottom)); }
.device-setup-step { grid-template-columns:1fr; }
.device-setup-action { width:100%; }
+14
View File
@@ -204,6 +204,20 @@
},
});
mobileTaskDock.start();
const mobileInsights = createMobileInsights({
root: qs('#insights-sheet'),
launcher: qs('#open-insights'),
closeButton: qs('#close-insights'),
dock: qs('#mobile-task-dock'),
hud: qs('[data-mobile-today-hud]'),
backgrounds: [qs('header'), qs('#my-work')],
menu: qs('.app-menu'),
history: window.history,
location: window.location,
eventTarget: window,
mediaQuery: window.matchMedia('(max-width: 600px)'),
});
mobileInsights.start();
qs('#empty-work-find').addEventListener('click', () => qs('#find-work').click());
qs('#empty-work-create').addEventListener('click', () => qs('#new-issue').click());
let liveMode = true;
+8
View File
@@ -16,6 +16,7 @@
<details class="app-menu">
<summary id="app-menu-toggle" aria-label="Open dashboard menu">Menu</summary>
<div class="app-menu-panel">
<button id="open-insights" type="button" aria-controls="insights-sheet">Insights</button>
<button id="refresh">Refresh</button>
<button id="open-palette">Command</button>
<button id="open-device-setup" type="button">Device setup</button>
@@ -273,6 +274,11 @@
</div>
</div>
</section>
<section class="insights-shell" id="insights-sheet" aria-labelledby="insights-heading">
<div class="insights-header">
<div><h2 id="insights-heading">Insights</h2><p class="small muted">Context, activity, live signals, and workspace tools.</p></div>
<button id="close-insights" type="button">Close</button>
</div>
<aside class="sidebar">
<details class="panel stack" data-panel-key="context" open>
<summary><h2>Context &amp; view</h2></summary>
@@ -334,6 +340,7 @@
<div id="md-preview" style="margin-top:8px;"></div>
</details>
</aside>
</section>
</main>
<div class="plan-today-sheet" id="plan-today-sheet" role="dialog" aria-modal="true" aria-labelledby="plan-today-title" hidden>
@@ -1628,6 +1635,7 @@
<script src="static/update-decision-transaction.js"></script>
<script src="static/agenda-session-launcher.js"></script>
<script src="static/mobile-launch.js"></script>
<script src="static/mobile-insights.js"></script>
<script src="static/mobile-app-shortcuts.js"></script>
<script src="static/install-app.js"></script>
<script src="static/mobile-device-setup.js"></script>
+74
View File
@@ -0,0 +1,74 @@
(function (root, factory) {
if (typeof module === 'object' && module.exports) module.exports = factory;
else root.createMobileInsights = factory;
})(typeof self !== 'undefined' ? self : this, function (o) {
const route = '#/insights';
let active = false;
let previous = '#/my-work';
function attr(element, name, value) {
if (value === null) element.removeAttribute(name);
else element.setAttribute(name, value);
}
function render(open, focus = false) {
active = Boolean(open && o.mediaQuery.matches);
attr(o.root, 'data-mobile-open', active ? 'true' : null);
attr(o.root, 'role', active ? 'dialog' : null);
attr(o.root, 'aria-modal', active ? 'true' : null);
attr(o.root, 'aria-hidden', active || !o.mediaQuery.matches ? null : 'true');
o.root.inert = !active && o.mediaQuery.matches;
o.backgrounds.forEach(element => { element.inert = active; });
o.dock.hidden = active;
attr(o.hud, 'data-overlay-hidden', active ? 'true' : null);
if (active) o.closeButton.focus();
else if (focus) o.launcher.focus();
}
function url(hash) {
return (o.location.pathname || '') + (o.location.search || '') + hash;
}
function open() {
if (!o.mediaQuery.matches || active) return false;
previous = o.location.hash && o.location.hash !== route ? o.location.hash : '#/my-work';
if (o.menu) o.menu.open = false;
o.history.pushState({ ...(o.history.state || {}), mobileInsights:true, previousHash:previous }, '', url(route));
render(true);
return true;
}
function sync() {
render(o.location.hash === route, active && o.location.hash !== route);
}
function close() {
if (!active) return false;
if (o.history.state?.mobileInsights) o.history.back();
else {
const state = { ...(o.history.state || {}) };
delete state.mobileInsights;
delete state.previousHash;
o.history.replaceState(state, '', url(previous));
render(false, true);
}
return true;
}
function start() {
o.launcher.addEventListener('click', open);
o.closeButton.addEventListener('click', close);
o.eventTarget.addEventListener('popstate', sync);
o.eventTarget.addEventListener('hashchange', sync);
o.eventTarget.addEventListener('keydown', event => {
if (active && event.key === 'Escape') {
event.preventDefault?.();
close();
}
});
o.mediaQuery.addEventListener?.('change', sync);
sync();
}
return { start, open, close, current:() => active };
});
+2 -1
View File
@@ -1,7 +1,7 @@
const BASE = new URL('./', self.location.href).pathname;
importScripts(BASE + 'static/private-data-registry.js');
importScripts(BASE + 'static/background-issue-sync.js');
const CACHE = 'stackchain-dashboard-shell-v115';
const CACHE = 'stackchain-dashboard-shell-v116';
const OFFLINE_LEASE_URL = new URL(BASE + '__offline-session-lease', self.location.origin).href;
const OUTAGE_STATUSES = new Set([500, 502, 503, 504]);
const NAVIGATION_TIMEOUT_MS = self.__STACKCHAIN_NAVIGATION_TIMEOUT_MS || 4000;
@@ -114,6 +114,7 @@ const SHELL = [
BASE + 'static/update-decision-transaction.js',
BASE + 'static/agenda-session-launcher.js',
BASE + 'static/mobile-launch.js',
BASE + 'static/mobile-insights.js',
BASE + 'static/mobile-app-shortcuts.js',
BASE + 'static/install-app.js',
BASE + 'static/mobile-device-setup.js',