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:
@@ -716,7 +716,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
:root:not(.oc-capacitor-app):not(.oc-browser-keyboard-open) .oc-mobile-composer {
|
:root:not(.oc-capacitor-app):not(.oc-browser-keyboard-open) .oc-mobile-composer {
|
||||||
padding-bottom: calc(0.75rem + max(16px, var(--oc-safe-area-bottom, env(safe-area-inset-bottom, 0px)))) !important;
|
padding-bottom: calc(0.75rem + var(--oc-safe-area-bottom-visual, 0px)) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Fullscreen composer: ChatInput pins the form to the visual viewport from
|
/* Fullscreen composer: ChatInput pins the form to the visual viewport from
|
||||||
|
|||||||
@@ -192,6 +192,13 @@
|
|||||||
|
|
||||||
const buildShortcuts = (recentSessions) => {
|
const buildShortcuts = (recentSessions) => {
|
||||||
const shortcuts = [
|
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',
|
name: 'Appearance Settings',
|
||||||
short_name: 'Settings',
|
short_name: 'Settings',
|
||||||
@@ -427,7 +434,6 @@
|
|||||||
<title>OpenChamber - AI Coding Assistant</title>
|
<title>OpenChamber - AI Coding Assistant</title>
|
||||||
<meta name="description" content="Web interface companion for OpenCode AI coding agent" />
|
<meta name="description" content="Web interface companion for OpenCode AI coding agent" />
|
||||||
<meta name="application-name" content="OpenChamber" />
|
<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) -->
|
<!-- Inline CSS for loading screen and Nerd Fonts (before Tailwind loads) -->
|
||||||
<style>
|
<style>
|
||||||
|
|||||||
@@ -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" />
|
<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>
|
<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>
|
<script>
|
||||||
// Blocking script: resolve the theme and paint the correct background BEFORE first
|
// 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
|
// paint. Without this the WebView shows its default (light) canvas until React mounts
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
{
|
{
|
||||||
"name": "OpenChamber",
|
"name": "OpenChamber",
|
||||||
"short_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": "/",
|
"start_url": "/",
|
||||||
|
"scope": "/",
|
||||||
"display": "standalone",
|
"display": "standalone",
|
||||||
"background_color": "#151313",
|
"background_color": "#151313",
|
||||||
"theme_color": "#edb449",
|
"theme_color": "#edb449",
|
||||||
|
|||||||
@@ -237,6 +237,13 @@ export const registerPwaManifestRoute = (app, dependencies) => {
|
|||||||
{ src: '/favicon-16.png', sizes: '16x16', type: 'image/png' },
|
{ src: '/favicon-16.png', sizes: '16x16', type: 'image/png' },
|
||||||
],
|
],
|
||||||
shortcuts: [
|
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',
|
name: 'Appearance Settings',
|
||||||
short_name: 'Settings',
|
short_name: 'Settings',
|
||||||
|
|||||||
@@ -65,6 +65,13 @@ describe('PWA manifest route', () => {
|
|||||||
const manifest = JSON.parse(res.body);
|
const manifest = JSON.parse(res.body);
|
||||||
expect(fetchCalls).toHaveLength(2);
|
expect(fetchCalls).toHaveLength(2);
|
||||||
expect(manifest.shortcuts).toEqual([
|
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',
|
name: 'Appearance Settings',
|
||||||
short_name: 'Settings',
|
short_name: 'Settings',
|
||||||
|
|||||||
+10
-11
@@ -63,17 +63,16 @@ const runWhenDocumentCanRegisterServiceWorker = (task: () => void): void => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const registerPwaServiceWorker = (): void => {
|
const registerPwaServiceWorker = (): void => {
|
||||||
runWhenDocumentCanRegisterServiceWorker(() => {
|
if (!canUseServiceWorker()) return;
|
||||||
try {
|
try {
|
||||||
registerSW({
|
registerSW({
|
||||||
onRegisterError(error: unknown) {
|
onRegisterError(error: unknown) {
|
||||||
console.warn('[PWA] service worker registration skipped:', error);
|
console.warn('[PWA] service worker registration skipped:', error);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn('[PWA] service worker registration skipped:', error);
|
console.warn('[PWA] service worker registration skipped:', error);
|
||||||
}
|
}
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const unregisterDevelopmentServiceWorkers = (): void => {
|
const unregisterDevelopmentServiceWorkers = (): void => {
|
||||||
|
|||||||
+5
-10
@@ -1,16 +1,11 @@
|
|||||||
/// <reference lib="webworker" />
|
/// <reference lib="webworker" />
|
||||||
|
|
||||||
// NOTE: keep the Workbox injection point so vite-plugin-pwa can build.
|
// Minimal service worker: push notifications and click routing only.
|
||||||
// We intentionally do not use Workbox runtime helpers here: iOS Safari can be
|
// No fetch handler, no precache — the app relies on the server for assets
|
||||||
// fragile with more complex SW bundles. For push notifications we only need a
|
// and the event pipeline handles reconnection. iOS Safari is fragile with
|
||||||
// minimal SW.
|
// complex SW bundles, so we keep this as lean as possible.
|
||||||
|
|
||||||
declare const self: ServiceWorkerGlobalScope & {
|
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;
|
|
||||||
|
|
||||||
type PushPayload = {
|
type PushPayload = {
|
||||||
title?: string;
|
title?: string;
|
||||||
|
|||||||
@@ -73,10 +73,10 @@ export default defineConfig({
|
|||||||
injectRegister: false,
|
injectRegister: false,
|
||||||
manifest: false,
|
manifest: false,
|
||||||
injectManifest: {
|
injectManifest: {
|
||||||
globPatterns: ['**/*.{js,css,html,ico,png,svg,woff,woff2,ttf,otf,eot}'],
|
// No precache — the SW has no fetch handler. Only the IIFE bundle
|
||||||
// iOS Safari/PWA is much more reliable with a classic (non-module) SW bundle.
|
// is produced so vite-plugin-pwa can build the registration script.
|
||||||
|
globPatterns: [],
|
||||||
rollupFormat: 'iife',
|
rollupFormat: 'iife',
|
||||||
// We already keep a custom manifest in index.html
|
|
||||||
injectionPoint: undefined,
|
injectionPoint: undefined,
|
||||||
},
|
},
|
||||||
devOptions: {
|
devOptions: {
|
||||||
|
|||||||
Reference in New Issue
Block a user