feat(pwa): favicon, maskable icon, splash screens, runtime cache, safe-area insets

- Add favicon.ico, favicon-32x32.png, favicon-16x16.png
- Add dedicated maskable-512x512.png with safe-zone padding for Android adaptive icons
- Add iOS splash screens for 5 common device sizes (excluded from SW precache)
- Add NetworkFirst runtime caching for stable API routes (excludes volatile endpoints)
- Add safe-top inset to all view headers for PWA standalone mode
- Bump version to 0.2.2

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
kuannnn
2026-03-27 22:16:38 +08:00
parent 5207d2b0dd
commit 9c2158961c
19 changed files with 37 additions and 9 deletions
+9
View File
@@ -7,6 +7,15 @@
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="apple-mobile-web-app-title" content="ClawTap" />
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
<link rel="icon" href="/favicon.ico" sizes="48x48" />
<!-- iOS Splash Screens -->
<link rel="apple-touch-startup-image" href="/splash/apple-splash-1290x2796.png" media="(device-width: 430px) and (device-height: 932px) and (-webkit-device-pixel-ratio: 3)" />
<link rel="apple-touch-startup-image" href="/splash/apple-splash-1179x2556.png" media="(device-width: 393px) and (device-height: 852px) and (-webkit-device-pixel-ratio: 3)" />
<link rel="apple-touch-startup-image" href="/splash/apple-splash-1170x2532.png" media="(device-width: 390px) and (device-height: 844px) and (-webkit-device-pixel-ratio: 3)" />
<link rel="apple-touch-startup-image" href="/splash/apple-splash-750x1334.png" media="(device-width: 375px) and (device-height: 667px) and (-webkit-device-pixel-ratio: 2)" />
<link rel="apple-touch-startup-image" href="/splash/apple-splash-2048x2732.png" media="(device-width: 1024px) and (device-height: 1366px) and (-webkit-device-pixel-ratio: 2)" />
<meta name="theme-color" content="#09090b" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@kuannnn/clawtap",
"version": "0.2.1",
"version": "0.2.2",
"description": "Mobile UI for AI coding assistants. Real-time sync with Claude Code, Codex CLI, and Gemini CLI via tmux.",
"type": "module",
"bin": {
Binary file not shown.

After

Width:  |  Height:  |  Size: 404 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 671 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

+1 -1
View File
@@ -36,7 +36,7 @@ export function AdapterSettingsSection({ adapter, onBack }: { adapter: string; o
return (
<div className="flex flex-col h-full bg-bg">
<div className="flex items-center px-4 py-3 border-b border-border gap-2">
<div className="flex items-center px-4 py-3 border-b border-border gap-2 safe-top">
<button onClick={onBack} className="text-text-dim hover:text-text"><ChevronLeft className="w-5 h-5" /></button>
<AdapterIcon adapterId={adapter} size={20} />
<span className="font-medium text-text font-mono tracking-wide">{brand.displayName} Settings</span>
+1 -1
View File
@@ -477,7 +477,7 @@ export function ChatView({
}
return (
<div className="flex flex-col h-dvh bg-bg relative overflow-hidden">
<div className="flex flex-col h-dvh bg-bg relative overflow-hidden safe-top">
{/* Header — auto-hides when scrolling up to view history */}
<div className={`flex items-center gap-2 px-4 py-3 border-b border-border shrink-0 transition-all duration-200 ${headerHidden ? 'max-h-0 py-0 overflow-hidden opacity-0 border-b-0' : 'max-h-16 opacity-100'}`}>
<Button variant="ghost" size="icon" onClick={onBack}>
+1 -1
View File
@@ -142,7 +142,7 @@ export function NewChatView({
return (
<div className="flex flex-col h-screen bg-bg">
{/* Header */}
<div className="flex items-center gap-2 px-4 py-3 border-b border-border shrink-0">
<div className="flex items-center gap-2 px-4 py-3 border-b border-border shrink-0 safe-top">
<Button variant="ghost" size="icon" onClick={onBack}>
<ChevronLeft className="w-5 h-5" />
</Button>
+1 -1
View File
@@ -42,7 +42,7 @@ export function SavedInstructionsView({ onBack }: { onBack: () => void }) {
return (
<div className="flex flex-col h-full bg-bg">
{/* Header */}
<div className="flex items-center px-4 py-3 border-b border-border">
<div className="flex items-center px-4 py-3 border-b border-border safe-top">
<button
onClick={onBack}
className="text-text-dim hover:text-text mr-2"
+1 -1
View File
@@ -228,7 +228,7 @@ export function SessionsView({
// --- Projects list (default view) ---
return (
<div className="min-h-screen bg-bg flex flex-col">
<div className="flex items-center justify-between px-4 py-3 border-b border-border shrink-0">
<div className="flex items-center justify-between px-4 py-3 border-b border-border shrink-0 safe-top">
<span className="flex items-center gap-1.5 text-lg font-medium text-text font-mono tracking-wider">
<svg width="20" height="15" viewBox="0 0 8 6" style={{ imageRendering: 'pixelated' }} className="text-accent">
<rect x="4" y="0" width="4" height="1" fill="currentColor"/>
+1 -1
View File
@@ -55,7 +55,7 @@ export function SettingsView({ onBack }: { onBack: () => void }) {
return (
<div className="flex flex-col h-full bg-bg">
{/* Header */}
<div className="flex items-center px-4 py-3 border-b border-border">
<div className="flex items-center px-4 py-3 border-b border-border safe-top">
<button onClick={onBack} className="text-text-dim hover:text-text mr-2"><ChevronLeft className="w-5 h-5" /></button>
<span className="text-lg font-medium text-text font-mono tracking-wide">Settings</span>
</div>
+19
View File
@@ -1,11 +1,30 @@
/// <reference lib="webworker" />
import { precacheAndRoute } from 'workbox-precaching';
import { registerRoute } from 'workbox-routing';
import { NetworkFirst } from 'workbox-strategies';
declare const self: ServiceWorkerGlobalScope;
// Precache static assets (injected by vite-plugin-pwa at build time)
precacheAndRoute(self.__WB_MANIFEST);
// Cache stable API responses — show last-known data when offline.
// Exclude volatile real-time endpoints (messages, active sessions, reviews).
registerRoute(
({ url }) => {
const p = url.pathname;
if (!p.startsWith('/api/')) return false;
if (p.includes('/messages')) return false;
if (p.startsWith('/api/active-sessions')) return false;
if (p.startsWith('/api/reviews')) return false;
return true;
},
new NetworkFirst({
cacheName: 'api-cache',
networkTimeoutSeconds: 5,
})
);
// Push notification handler — server already filters by clientCount,
// so we always show the notification if one is received.
self.addEventListener('push', (event) => {
+2 -2
View File
@@ -26,7 +26,7 @@ export default defineConfig({
icons: [
{ src: '/pwa-192x192.png', sizes: '192x192', type: 'image/png' },
{ src: '/pwa-512x512.png', sizes: '512x512', type: 'image/png' },
{ src: '/pwa-512x512.png', sizes: '512x512', type: 'image/png', purpose: 'maskable' },
{ src: '/maskable-512x512.png', sizes: '512x512', type: 'image/png', purpose: 'maskable' },
],
shortcuts: [
{
@@ -39,7 +39,7 @@ export default defineConfig({
categories: ['developer-tools', 'productivity'],
},
injectManifest: {
globPatterns: ['**/*.{js,css,html,ico,png,svg,woff2}'],
globPatterns: ['**/*.{js,css,html,ico,svg,woff2}', '*.png', 'mascot/*.png'],
},
}),
],