feat: ClawTap v0.1.0 — initial release
Multi-adapter mobile UI for AI coding assistants. Supports Claude Code, Codex CLI, and Gemini CLI through one interface. Features: - Real-time bidirectional sync via tmux + WebSocket - Cross-AI review (send one AI's output to another for review) - Multi-review tabs with minimize/expand - Push notifications (PWA) with smart session-aware filtering - Three-channel event system (hooks, file watcher, pane monitor) - Voice input, image paste, draft persistence - Terminal-native design (JetBrains Mono, dark theme, pixel art claw) - CLI with --adapter flag on every command - Zero-overhead fire-and-forget hooks
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
/// <reference lib="webworker" />
|
||||
import { precacheAndRoute } from 'workbox-precaching';
|
||||
|
||||
declare const self: ServiceWorkerGlobalScope;
|
||||
|
||||
// Precache static assets (injected by vite-plugin-pwa at build time)
|
||||
precacheAndRoute(self.__WB_MANIFEST);
|
||||
|
||||
// Push notification handler — server already filters by clientCount,
|
||||
// so we always show the notification if one is received.
|
||||
self.addEventListener('push', (event) => {
|
||||
if (!event.data) return;
|
||||
const payload = event.data.json();
|
||||
event.waitUntil((async () => {
|
||||
// Update badge (handle 0 explicitly to clear)
|
||||
const badgeValue = payload.data?.badge;
|
||||
if (typeof badgeValue === 'number') {
|
||||
badgeValue > 0
|
||||
? await navigator.setAppBadge?.(badgeValue)
|
||||
: await navigator.clearAppBadge?.();
|
||||
}
|
||||
|
||||
// Forward to app clients for real-time UI updates (e.g. pending badges)
|
||||
const allClients = await self.clients.matchAll({ type: 'window', includeUncontrolled: true });
|
||||
for (const c of allClients) {
|
||||
c.postMessage({ type: 'PUSH_RECEIVED', sessionId: payload.data?.sessionId, badge: badgeValue });
|
||||
}
|
||||
|
||||
// Silent push (no title) — badge-only update, don't show notification
|
||||
if (!payload.title) return;
|
||||
|
||||
// Always show notification — server already filtered by clientCount
|
||||
return self.registration.showNotification(payload.title, {
|
||||
body: payload.body,
|
||||
icon: '/pwa-192x192.png',
|
||||
badge: '/pwa-192x192.png',
|
||||
tag: payload.tag || payload.data?.sessionId || 'default',
|
||||
data: payload.data,
|
||||
});
|
||||
})());
|
||||
});
|
||||
|
||||
// Notification click — open app and navigate to session
|
||||
self.addEventListener('notificationclick', (event) => {
|
||||
event.notification.close();
|
||||
const sessionId = event.notification.data?.sessionId;
|
||||
const url = sessionId ? `/?session=${sessionId}` : '/';
|
||||
event.waitUntil(
|
||||
self.clients.matchAll({ type: 'window', includeUncontrolled: true }).then(clients => {
|
||||
for (const c of clients) {
|
||||
if ('focus' in c) {
|
||||
c.postMessage({ type: 'OPEN_SESSION', sessionId });
|
||||
return (c as WindowClient).focus();
|
||||
}
|
||||
}
|
||||
return self.clients.openWindow(url);
|
||||
})
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user