Keep background apps alive, and notify from their unread count
The report was that a backgrounded site dies slowly and stops fetching. It did, for three separate reasons, and the first two fixes each traded one failure for another: Hiding inactive views lets WebKit suspend them, so nothing is hidden any more - every view keeps its size and stays in the window, and the active one is ordered on top. Telling a background page it was hidden then made Gmail throttle its own syncing; its unread count sat unchanged for two and a half minutes. That spoof is gone. Neither was enough on its own: a background page keeps its timers but loses the connection its updates arrive on, and WKWebView has no equivalent of Electron's backgroundThrottling. Background apps are now poked every 45s with the events a page uses to catch up after you return to a tab - it fetches without losing a half-written reply. Notifications now come from the unread count in the title rather than the site's notification code, which Gmail will not run while it believes you are looking at it. Measured: Gmail behind Odoo went 155 -> 157 untouched and raised "Gmail / 1 new". Also adds a background-app probe to Settings, which is what turned this from guesswork into measurement, and drops the nav's blocked-element count and the hide-element button.
This commit is contained in:
+11
-14
@@ -1,5 +1,5 @@
|
||||
import type { Config, Group, WorkApp } from "../types";
|
||||
import { Back, Cog, Collapse, EyeOff, Forward, Reload } from "./icons";
|
||||
import { Back, Cog, Collapse, Forward, Reload } from "./icons";
|
||||
import { Favicon, HEADING, ICON_CHROME } from "./ui";
|
||||
|
||||
interface Props {
|
||||
@@ -13,7 +13,6 @@ interface Props {
|
||||
onBack: () => void;
|
||||
onForward: () => void;
|
||||
onReload: () => void;
|
||||
onPick: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -30,7 +29,7 @@ export const navWidth = (collapsed: boolean) => (collapsed ? RAIL : PANEL);
|
||||
export default function Nav({
|
||||
config, activeId, collapsed,
|
||||
onSelect, onToggleCollapse, onOpenSettings, onToggleGroup,
|
||||
onBack, onForward, onReload, onPick,
|
||||
onBack, onForward, onReload,
|
||||
}: Props) {
|
||||
const groups = [...config.groups].sort((a, b) => a.order - b.order);
|
||||
const inGroup = (id: string | null) =>
|
||||
@@ -42,8 +41,9 @@ export default function Nav({
|
||||
"flex shrink-0 flex-col overflow-hidden border-r border-slate-300 bg-white " +
|
||||
"dark:border-slate-800 dark:bg-slate-900";
|
||||
|
||||
/* Back, forward, reload, hide-an-element and settings, in that order —
|
||||
navigation first because it is what gets reached for most. */
|
||||
/* Back, forward, reload and settings. Nothing hides an element here — that
|
||||
lives on the right-click menu, where you are already pointing at the thing
|
||||
you want gone. */
|
||||
const controls = (
|
||||
<>
|
||||
<button onClick={onBack} disabled={disabled} title="Back (or swipe left)" className={ICON_CHROME}>
|
||||
@@ -55,14 +55,6 @@ export default function Nav({
|
||||
<button onClick={onReload} disabled={disabled} title="Reload" className={ICON_CHROME}>
|
||||
<Reload />
|
||||
</button>
|
||||
<button
|
||||
onClick={onPick}
|
||||
disabled={disabled}
|
||||
title="Hide an element on this page — or right-click it"
|
||||
className={ICON_CHROME}
|
||||
>
|
||||
<EyeOff />
|
||||
</button>
|
||||
<button onClick={onOpenSettings} title="Settings" className={ICON_CHROME}>
|
||||
<Cog />
|
||||
</button>
|
||||
@@ -95,8 +87,13 @@ export default function Nav({
|
||||
return (
|
||||
<aside className={`${shell} items-center`} style={{ width: RAIL }}>
|
||||
<div data-tauri-drag-region style={{ height: TITLEBAR }} className="w-full shrink-0" />
|
||||
{/* Only settings and the expander survive the rail. Back and forward
|
||||
are a two-finger swipe, reload is ⌘R, and five buttons across 72px
|
||||
is clutter standing in for a toolbar nobody asked for. */}
|
||||
<div className="flex w-full flex-col items-center border-b border-slate-200 pb-2 dark:border-slate-800">
|
||||
<div className="flex flex-wrap justify-center gap-0.5 px-1">{controls}</div>
|
||||
<button onClick={onOpenSettings} title="Settings" className={ICON_CHROME}>
|
||||
<Cog />
|
||||
</button>
|
||||
<button onClick={onToggleCollapse} title="Expand" className={ICON_CHROME}>
|
||||
<Collapse open={false} />
|
||||
</button>
|
||||
|
||||
@@ -42,6 +42,7 @@ export default function Settings({
|
||||
const [confirmDelete, setConfirmDelete] = useState<WorkApp | null>(null);
|
||||
|
||||
const [notifyStatus, setNotifyStatus] = useState<string | null>(null);
|
||||
const [reports, setReports] = useState<[string, string][]>([]);
|
||||
|
||||
const groups = [...config.groups].sort((a, b) => a.order - b.order);
|
||||
|
||||
@@ -272,7 +273,27 @@ export default function Settings({
|
||||
>
|
||||
Check permission
|
||||
</button>
|
||||
<span className={HELP}>Clicking one opens the message it is about.</span>
|
||||
<button
|
||||
onClick={async () => {
|
||||
await api.probeApps();
|
||||
await new Promise((r) => setTimeout(r, 500));
|
||||
setReports(await api.appReports());
|
||||
}}
|
||||
className={BTN}
|
||||
>
|
||||
Check background apps
|
||||
</button>
|
||||
</div>
|
||||
{reports.length > 0 && (
|
||||
<div className="space-y-0.5">
|
||||
{reports.map(([name, report]) => (
|
||||
<p key={name} className={`${HELP} font-mono`}>
|
||||
<span className="font-semibold">{name}</span> · {report}
|
||||
</p>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<div className="hidden">
|
||||
</div>
|
||||
{notifyStatus && (
|
||||
<p className={`${HELP} font-mono`}>{notifyStatus}</p>
|
||||
|
||||
@@ -14,9 +14,6 @@ export const Reload = (p: Props) => <S d="M20 11a8 8 0 10-2.3 5.7M20 5v6h-6" {..
|
||||
export const External = (p: Props) => (
|
||||
<S d="M14 5h5v5M19 5l-8 8M18 14v4a2 2 0 01-2 2H6a2 2 0 01-2-2V8a2 2 0 012-2h4" {...p} />
|
||||
);
|
||||
export const EyeOff = (p: Props) => (
|
||||
<S d="M3 3l18 18M10.6 10.6a2 2 0 002.8 2.8M9.4 5.4A9.5 9.5 0 0112 5c5 0 9 4.5 9 7a11 11 0 01-2.4 3.5M6.2 6.9C4 8.3 3 10.4 3 12c0 2.5 4 7 9 7a9.6 9.6 0 004-.85" {...p} />
|
||||
);
|
||||
export const Trash = ({ className = "size-3.5" }: Props) => (
|
||||
<svg viewBox="0 0 24 24" className={className} fill="none" stroke="currentColor"
|
||||
strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round">
|
||||
|
||||
Reference in New Issue
Block a user