Make the app an installable PWA with phone layout and Web Push

Add a generated manifest, icon set, offline page and service worker that never caches authenticated HTML, plus install and update UI. Turn the rail into a drawer below md with container-query internals, dvh and safe-area handling, and reconnect the socket after visibility/network changes. Add Web Push subscriptions, presence-aware delivery, notification settings and tests.
This commit is contained in:
2026-10-19 17:12:16 +01:00
parent 61098a114c
commit dbfe48796b
100 changed files with 3573 additions and 117 deletions
@@ -28,14 +28,22 @@ type Props = {
* settings page from the title. Every list uses it, so the row behaves the
* same, and the open button shows it in the viewer over the page.
*/
export default function ComputerListRow({ computer, badge, subtitle, children, linked = true }: Props) {
export default function ComputerListRow({
computer,
badge,
subtitle,
children,
linked = true,
}: Props) {
const name = computer.name || `Computer ${computer.id}`;
const ready = computer.status == "running";
return (
<SettingsListRow
avatar={<ComputerIconTile />}
href={linked ? `/admin/settings/computers/${computer.id}` : undefined}
href={
linked ? `/admin/settings/computers/${computer.id}` : undefined
}
title={name}
badge={badge}
subtitle={
@@ -3,6 +3,7 @@ import Row from "@moduletrace/twui/layout/Row";
import Stack from "@moduletrace/twui/layout/Stack";
import H1 from "@moduletrace/twui/layout/H1";
import Span from "@moduletrace/twui/layout/Span";
import RailDrawerButton from "@/src/layouts/bots/(partials)/rail-drawer-button";
type Props = {
title: string;
@@ -15,7 +16,8 @@ type Props = {
/**
* Same 64px header as a conversation, so moving between bots and settings
* doesn't shift the page.
* doesn't shift the page. Below `md` it also holds the rail's drawer
* button, since the rail is out of the layout there.
*/
export default function SettingsPaneHeader({
title,
@@ -23,7 +25,8 @@ export default function SettingsPaneHeader({
action,
}: Props) {
return (
<Row className="h-16 shrink-0 gap-3! flex-nowrap items-center px-6 border-0 border-b border-solid border-hairline">
<Row className="h-16 shrink-0 gap-2! md:gap-3! flex-nowrap items-center px-3 md:px-6 border-0 border-b border-solid border-hairline">
<RailDrawerButton />
<Stack className="min-w-0 gap-0!">
<H1 className="text-lg! leading-6! tracking-normal! font-bold text-ink truncate">
{title}
@@ -44,7 +44,7 @@ export default function SettingsShell({
<Stack className="grow min-h-0 overflow-y-auto gap-0! items-stretch">
<Stack
className={twMerge(
"w-full max-w-container mx-auto gap-0! items-stretch px-6 lg:px-10 pb-16",
"w-full max-w-container mx-auto gap-0! items-stretch px-4 md:px-6 lg:px-10 pb-16",
wide && "max-w-[1200px]",
)}
>