fix: PWA improvements — clean up SW, fix chin bar, add shortcuts, improve mobile support

- Remove dead precache manifest from SW (no fetch handler to consume it)
- Set empty globPatterns in vite-plugin-pwa config
- Fix bottom chin bar padding: use scaled safe-area token instead of raw inset
- Add 'New Session' PWA shortcut (client + server manifest)
- Add PWA meta tags to mobile.html (manifest, Apple icons, theme-color)
- Fix static site.webmanifest: add id/scope, correct description
- Remove duplicate apple-mobile-web-app-title from index.html
- Move SW registration to module scope (earlier, no window.load delay)
- Update manifest route tests for new shortcut order
This commit is contained in:
2026-09-07 21:37:06 +00:00
parent 4b95b327dd
commit b464500310
9 changed files with 69 additions and 27 deletions
+7 -1
View File
@@ -192,6 +192,13 @@
const buildShortcuts = (recentSessions) => {
const shortcuts = [
{
name: 'New Session',
short_name: 'New',
description: 'Start a new coding session',
url: `${baseUrl}/`,
icons: [{ src: `${baseUrl}/pwa-192.png`, sizes: '192x192', type: 'image/png' }],
},
{
name: 'Appearance Settings',
short_name: 'Settings',
@@ -427,7 +434,6 @@
<title>OpenChamber - AI Coding Assistant</title>
<meta name="description" content="Web interface companion for OpenCode AI coding agent" />
<meta name="application-name" content="OpenChamber" />
<meta name="apple-mobile-web-app-title" content="OpenChamber" />
<!-- Inline CSS for loading screen and Nerd Fonts (before Tailwind loads) -->
<style>
+26
View File
@@ -5,6 +5,32 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover" />
<title>OpenChamber Mobile</title>
<!-- Favicon -->
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<link rel="icon" type="image/png" href="/favicon-32.png" sizes="32x32" />
<link rel="icon" type="image/png" href="/favicon-16.png" sizes="16x16" />
<link rel="mask-icon" href="/favicon.svg" color="#edb449" />
<!-- Apple touch icon - PNG format required for iOS PWA support -->
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon-180x180.png" />
<link rel="apple-touch-icon" sizes="167x167" href="/apple-touch-icon-167x167.png" />
<link rel="apple-touch-icon" sizes="152x152" href="/apple-touch-icon-152x152.png" />
<!-- Web app manifest -->
<link rel="manifest" href="/manifest.webmanifest" crossorigin="use-credentials" />
<!-- Theme color -->
<meta name="theme-color" content="#151313" />
<meta name="theme-color" content="#151313" media="(prefers-color-scheme: dark)" />
<!-- iOS Safari PWA styling -->
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="apple-mobile-web-app-title" content="OpenChamber" />
<meta name="description" content="Web interface companion for OpenCode AI coding agent" />
<meta name="application-name" content="OpenChamber" />
<script>
// Blocking script: resolve the theme and paint the correct background BEFORE first
// paint. Without this the WebView shows its default (light) canvas until React mounts
+3 -1
View File
@@ -1,8 +1,10 @@
{
"name": "OpenChamber",
"short_name": "OpenChamber",
"description": "OpenChamber desktop companion for the OpenCode AI coding agent",
"description": "Web interface companion for OpenCode AI coding agent",
"id": "/",
"start_url": "/",
"scope": "/",
"display": "standalone",
"background_color": "#151313",
"theme_color": "#edb449",
@@ -237,6 +237,13 @@ export const registerPwaManifestRoute = (app, dependencies) => {
{ src: '/favicon-16.png', sizes: '16x16', type: 'image/png' },
],
shortcuts: [
{
name: 'New Session',
short_name: 'New',
description: 'Start a new coding session',
url: '/',
icons: [{ src: '/pwa-192.png', sizes: '192x192', type: 'image/png' }],
},
{
name: 'Appearance Settings',
short_name: 'Settings',
@@ -65,6 +65,13 @@ describe('PWA manifest route', () => {
const manifest = JSON.parse(res.body);
expect(fetchCalls).toHaveLength(2);
expect(manifest.shortcuts).toEqual([
{
name: 'New Session',
short_name: 'New',
description: 'Start a new coding session',
url: '/',
icons: [{ src: '/pwa-192.png', sizes: '192x192', type: 'image/png' }],
},
{
name: 'Appearance Settings',
short_name: 'Settings',
+10 -11
View File
@@ -63,17 +63,16 @@ const runWhenDocumentCanRegisterServiceWorker = (task: () => void): void => {
};
const registerPwaServiceWorker = (): void => {
runWhenDocumentCanRegisterServiceWorker(() => {
try {
registerSW({
onRegisterError(error: unknown) {
console.warn('[PWA] service worker registration skipped:', error);
},
});
} catch (error) {
console.warn('[PWA] service worker registration skipped:', error);
}
});
if (!canUseServiceWorker()) return;
try {
registerSW({
onRegisterError(error: unknown) {
console.warn('[PWA] service worker registration skipped:', error);
},
});
} catch (error) {
console.warn('[PWA] service worker registration skipped:', error);
}
};
const unregisterDevelopmentServiceWorkers = (): void => {
+5 -10
View File
@@ -1,16 +1,11 @@
/// <reference lib="webworker" />
// NOTE: keep the Workbox injection point so vite-plugin-pwa can build.
// We intentionally do not use Workbox runtime helpers here: iOS Safari can be
// fragile with more complex SW bundles. For push notifications we only need a
// minimal SW.
// Minimal service worker: push notifications and click routing only.
// No fetch handler, no precache — the app relies on the server for assets
// and the event pipeline handles reconnection. iOS Safari is fragile with
// complex SW bundles, so we keep this as lean as possible.
declare const self: ServiceWorkerGlobalScope & {
__WB_MANIFEST: Array<string | { url: string; revision?: string }>;
};
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const __precacheManifest = self.__WB_MANIFEST;
declare const self: ServiceWorkerGlobalScope;
type PushPayload = {
title?: string;
+3 -3
View File
@@ -73,10 +73,10 @@ export default defineConfig({
injectRegister: false,
manifest: false,
injectManifest: {
globPatterns: ['**/*.{js,css,html,ico,png,svg,woff,woff2,ttf,otf,eot}'],
// iOS Safari/PWA is much more reliable with a classic (non-module) SW bundle.
// No precache — the SW has no fetch handler. Only the IIFE bundle
// is produced so vite-plugin-pwa can build the registration script.
globPatterns: [],
rollupFormat: 'iife',
// We already keep a custom manifest in index.html
injectionPoint: undefined,
},
devOptions: {