chore: release 0.3.1 — iOS PWA fixes, notification improvements, docs update

- fix(pwa): iOS keyboard gap caused by WebKit viewport-fit=cover bug.
  After keyboard open/close, 100dvh permanently shrinks. Track max
  innerHeight in --app-height CSS variable as stable replacement.
- feat(pwa): auto-prompt notification permission on first login in
  standalone mode (once only, skips if denied).
- refactor: remove duplicate notification toggle from header menu
  (already in Settings).
- feat(dev): expose Vite dev server on network (host: true) for
  mobile testing via Tailscale.
- docs: update README — add Task Progress FAB, fix notification
  flow description, document OPENAI_API_KEY / VAPID_EMAIL env vars,
  clarify voice input backends, add CLI --version/--help, update
  .env.example.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
kuannnn
2026-03-30 05:58:56 +08:00
parent a1079766bd
commit 35b4519b94
8 changed files with 68 additions and 16 deletions
+39
View File
@@ -1,6 +1,7 @@
import { useState, useCallback, useEffect, useRef } from 'react';
import { STORAGE } from './lib/storage-keys';
import { isAuthenticated, clearToken } from './lib/api';
import { usePushNotifications } from './hooks/usePushNotifications';
import { LoginView } from './components/LoginView';
import { SessionsView } from './components/SessionsView';
import { ChatView } from './components/ChatView';
@@ -59,6 +60,7 @@ export function App() {
const [deviceOnline, setDeviceOnline] = useState(navigator.onLine);
const consecutiveFails = useRef(0);
const initialized = useRef(false);
const { supported: pushSupported, subscribed: pushSubscribed, subscribe: pushSubscribe } = usePushNotifications();
const [installPrompt, setInstallPrompt] = useState<BeforeInstallPromptEvent | null>(null);
const [installDismissed, setInstallDismissed] = useState(
@@ -120,6 +122,43 @@ export function App() {
return () => navigator.serviceWorker?.removeEventListener('controllerchange', handleControllerChange);
}, []);
// PWA iOS: Sync --app-height CSS variable to the true viewport height.
// WebKit bug: after a keyboard open/close cycle, viewport-fit=cover's layout
// extension is lost, causing 100dvh to shrink permanently. We track the max
// observed innerHeight and lock it as the app height.
useEffect(() => {
let maxHeight = window.innerHeight;
const sync = () => {
const h = window.innerHeight;
if (h > maxHeight) maxHeight = h;
document.documentElement.style.setProperty('--app-height', `${maxHeight}px`);
};
sync();
window.visualViewport?.addEventListener('resize', sync);
window.addEventListener('resize', sync);
return () => {
window.visualViewport?.removeEventListener('resize', sync);
window.removeEventListener('resize', sync);
};
}, []);
// PWA: Auto-prompt notification permission on first login in standalone mode
useEffect(() => {
if (!authed || !pushSupported || pushSubscribed) return;
if (localStorage.getItem(STORAGE.PUSH_PROMPTED)) return;
const isStandalone = window.matchMedia('(display-mode: standalone)').matches
|| (navigator as any).standalone === true;
if (!isStandalone) return;
if (typeof Notification !== 'undefined' && Notification.permission === 'denied') return;
// Small delay so the UI has time to settle after login
const timer = setTimeout(() => {
localStorage.setItem(STORAGE.PUSH_PROMPTED, '1');
pushSubscribe().catch(() => {});
}, 1500);
return () => clearTimeout(timer);
}, [authed, pushSupported, pushSubscribed, pushSubscribe]);
// PWA: Clear app badge on focus
useEffect(() => {
const handleVisibility = () => {