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>
@@ -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,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": {
|
||||
|
||||
|
After Width: | Height: | Size: 404 B |
|
After Width: | Height: | Size: 671 B |
|
After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 5.9 KiB |
@@ -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>
|
||||
|
||||
@@ -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}>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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'],
|
||||
},
|
||||
}),
|
||||
],
|
||||
|
||||