added versioning
This commit is contained in:
1 parent
5b2da87f05
commit
c82b112624
8 files changed
+3887
-106
No files matched your search
@@ -11,6 +11,8 @@ import SettingsModal from './components/SettingsModal';
|
||||
import Tooltip from './components/Tooltip';
|
||||
import { Brain, Info, ShieldCheck, Filter, Plus, BookOpen, Activity, Zap, Settings } from 'lucide-react';
|
||||
import { Button } from './components/ui/button';
|
||||
import { ReloadPrompt } from './components/ReloadPrompt';
|
||||
import { VersionBadge } from './components/VersionBadge';
|
||||
|
||||
const DEFAULT_SETTINGS: UserSettings = {
|
||||
voiceName: 'Zephyr',
|
||||
@@ -158,6 +160,7 @@ const App: React.FC = () => {
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-[#FDFDFF] text-slate-900 font-sans relative animate-in fade-in duration-500">
|
||||
<ReloadPrompt />
|
||||
{/* Modals */}
|
||||
{showGenerator && <ScenarioGenerator onGenerate={handleScenarioGenerated} onCancel={() => setShowGenerator(false)} />}
|
||||
{showReference && <CBTReference onClose={() => setShowReference(false)} />}
|
||||
@@ -283,8 +286,12 @@ const App: React.FC = () => {
|
||||
|
||||
<footer className="bg-white border-t border-slate-200 py-12">
|
||||
<div className="max-w-5xl mx-auto px-6 flex flex-col md:flex-row justify-between items-center gap-8">
|
||||
<div className="flex items-center gap-2 text-slate-400">
|
||||
<Brain size={20} /><span className="text-sm font-bold uppercase tracking-widest">Cognitive Clarity v1.2</span>
|
||||
<div className="flex items-center gap-4 text-slate-400">
|
||||
<div className="flex items-center gap-2">
|
||||
<Brain size={20} />
|
||||
<span className="text-sm font-bold uppercase tracking-widest text-slate-900">Cognitive Clarity</span>
|
||||
</div>
|
||||
<VersionBadge />
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { useRegisterSW } from 'virtual:pwa-register/react';
|
||||
import { Button } from './ui/button';
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle
|
||||
} from './ui/dialog';
|
||||
|
||||
export const ReloadPrompt: React.FC = () => {
|
||||
// Skip SW in dev mode
|
||||
if (import.meta.env.DEV) {
|
||||
useEffect(() => {
|
||||
if ('serviceWorker' in navigator) {
|
||||
navigator.serviceWorker.getRegistrations().then((registrations) => {
|
||||
registrations.forEach((registration) => registration.unregister());
|
||||
});
|
||||
}
|
||||
// Clear workbox caches
|
||||
if (typeof caches !== 'undefined') {
|
||||
caches.keys().then((keys) => {
|
||||
keys.forEach((key) => {
|
||||
if (key.toLowerCase().includes('workbox') || key.toLowerCase().includes('cognitive-clarity')) {
|
||||
caches.delete(key);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
return null;
|
||||
}
|
||||
|
||||
const {
|
||||
offlineReady: [offlineReady, setOfflineReady],
|
||||
needRefresh: [needRefresh, setNeedRefresh],
|
||||
updateServiceWorker,
|
||||
} = useRegisterSW({
|
||||
onRegisteredSW(swUrl, r) {
|
||||
if (r) {
|
||||
// Check for updates every 60 minutes
|
||||
setInterval(() => {
|
||||
console.log('[PWA] Periodic update check...');
|
||||
r.update();
|
||||
}, 60 * 60 * 1000);
|
||||
|
||||
// Check for updates when coming back online
|
||||
const handleOnline = () => {
|
||||
console.log('[PWA] Back online, checking for updates...');
|
||||
r.update();
|
||||
};
|
||||
window.addEventListener('online', handleOnline);
|
||||
}
|
||||
|
||||
// Auto-reload when a new service worker takes control
|
||||
if ('serviceWorker' in navigator) {
|
||||
navigator.serviceWorker.addEventListener('controllerchange', () => {
|
||||
console.log('[PWA] Controller changed, reloading...');
|
||||
window.location.reload();
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const close = () => {
|
||||
setOfflineReady(false);
|
||||
setNeedRefresh(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={needRefresh || offlineReady} onOpenChange={close}>
|
||||
<DialogContent className="sm:max-w-md bg-slate-900 border-slate-800 text-slate-100">
|
||||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
{needRefresh ? 'Update Available' : 'Offline Ready'}
|
||||
</DialogTitle>
|
||||
<DialogDescription className="text-slate-400">
|
||||
{needRefresh
|
||||
? 'A new version of Cognitive Clarity is available. Click reload to update immediately.'
|
||||
: 'The application is ready to work offline.'}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<DialogFooter className="sm:justify-start gap-2">
|
||||
{needRefresh && (
|
||||
<Button
|
||||
type="button"
|
||||
onClick={() => updateServiceWorker(true)}
|
||||
className="bg-blue-600 hover:bg-blue-500 text-white"
|
||||
>
|
||||
Reload and Update
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
type="button"
|
||||
variant="secondary"
|
||||
onClick={close}
|
||||
className="bg-slate-800 hover:bg-slate-700 text-slate-300 border-none"
|
||||
>
|
||||
Close
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,39 @@
|
||||
import React from 'react';
|
||||
import { Badge } from './ui/badge';
|
||||
|
||||
interface VersionBadgeProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const VersionBadge: React.FC<VersionBadgeProps> = ({ className = '' }) => {
|
||||
const version = typeof __APP_VERSION__ !== 'undefined' ? __APP_VERSION__ : 'dev';
|
||||
const buildTime = typeof __BUILD_TIMESTAMP__ !== 'undefined' ? __BUILD_TIMESTAMP__ : '';
|
||||
|
||||
// Format ISO string to a more readable short date/time
|
||||
const formattedTime = buildTime
|
||||
? new Date(buildTime).toLocaleString(undefined, {
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
})
|
||||
: '';
|
||||
|
||||
return (
|
||||
<div className={`flex flex-col items-end text-[10px] text-slate-500 ${className}`}>
|
||||
<Badge variant="outline" className="text-[10px] h-5 py-0 px-2 font-mono border-slate-800 text-slate-500">
|
||||
v{version}
|
||||
</Badge>
|
||||
{formattedTime && (
|
||||
<span className="mt-1 opacity-50 uppercase tracking-tighter">
|
||||
Built: {formattedTime}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const useAppVersion = () => ({
|
||||
version: typeof __APP_VERSION__ !== 'undefined' ? __APP_VERSION__ : 'dev',
|
||||
buildTimestamp: typeof __BUILD_TIMESTAMP__ !== 'undefined' ? __BUILD_TIMESTAMP__ : '',
|
||||
});
|
||||
Generated
+3635
-99
File diff suppressed because it is too large.
Load diff
+16
-3
@@ -1,11 +1,17 @@
|
||||
{
|
||||
"name": "cognitive-clarity",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"version": "1.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"build": "npm run version:patch && vite build",
|
||||
"build:prod": "npm run version:minor && vite build",
|
||||
"build:major": "npm run version:major && vite build",
|
||||
"build:nobump": "vite build",
|
||||
"version:patch": "npm version patch --no-git-tag-version",
|
||||
"version:minor": "npm version minor --no-git-tag-version",
|
||||
"version:major": "npm version major --no-git-tag-version",
|
||||
"preview": "vite preview",
|
||||
"lint": "tsc --noEmit"
|
||||
},
|
||||
@@ -26,7 +32,14 @@
|
||||
"react": "^19.2.3",
|
||||
"react-dom": "^19.2.3",
|
||||
"tailwind-merge": "^3.5.0",
|
||||
"tailwindcss-animate": "^1.0.7"
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"vite-plugin-pwa": "^1.2.0",
|
||||
"workbox-cacheable-response": "^7.4.0",
|
||||
"workbox-core": "^7.4.0",
|
||||
"workbox-expiration": "^7.4.0",
|
||||
"workbox-precaching": "^7.4.0",
|
||||
"workbox-routing": "^7.4.0",
|
||||
"workbox-strategies": "^7.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.19.15",
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
/// <reference lib="webworker" />
|
||||
declare const self: ServiceWorkerGlobalScope;
|
||||
|
||||
import { clientsClaim } from 'workbox-core';
|
||||
import { cleanupOutdatedCaches, precacheAndRoute } from 'workbox-precaching';
|
||||
|
||||
// Bump this version to force SW update - changes to this constant change the SW file hash
|
||||
const CACHE_VERSION = '2026-03-06-v1';
|
||||
|
||||
// @ts-ignore - __WB_MANIFEST is injected at build time by vite-plugin-pwa
|
||||
precacheAndRoute(self.__WB_MANIFEST);
|
||||
|
||||
cleanupOutdatedCaches();
|
||||
|
||||
// Force immediate activation of new SW
|
||||
self.addEventListener('install', () => {
|
||||
console.log('[SW] Installing, skipping waiting...');
|
||||
self.skipWaiting();
|
||||
});
|
||||
|
||||
self.addEventListener('activate', () => {
|
||||
console.log('[SW] Activating, claiming clients...');
|
||||
clientsClaim();
|
||||
});
|
||||
|
||||
// Handle SKIP_WAITING message from client
|
||||
self.addEventListener('message', (event) => {
|
||||
if (event.data?.type === 'SKIP_WAITING') {
|
||||
self.skipWaiting();
|
||||
}
|
||||
});
|
||||
Vendored
+6
@@ -0,0 +1,6 @@
|
||||
/// <reference types="vite/client" />
|
||||
/// <reference types="vite-plugin-pwa/client" />
|
||||
/// <reference types="vite-plugin-pwa/react" />
|
||||
|
||||
declare const __APP_VERSION__: string;
|
||||
declare const __BUILD_TIMESTAMP__: string;
|
||||
+44
-2
@@ -1,6 +1,8 @@
|
||||
import path from 'path';
|
||||
import { defineConfig, loadEnv } from 'vite';
|
||||
import react from '@vitejs/plugin-react';
|
||||
import { VitePWA } from 'vite-plugin-pwa';
|
||||
import pkg from './package.json';
|
||||
|
||||
export default defineConfig(({ mode }) => {
|
||||
const env = loadEnv(mode, '.', '');
|
||||
@@ -9,10 +11,50 @@ export default defineConfig(({ mode }) => {
|
||||
port: 3000,
|
||||
host: '0.0.0.0',
|
||||
},
|
||||
plugins: [react()],
|
||||
plugins: [
|
||||
react(),
|
||||
VitePWA({
|
||||
strategies: 'injectManifest',
|
||||
srcDir: '.',
|
||||
filename: 'sw.ts',
|
||||
registerType: 'autoUpdate',
|
||||
injectRegister: 'auto',
|
||||
devOptions: {
|
||||
enabled: false,
|
||||
},
|
||||
manifest: {
|
||||
name: 'Cognitive Clarity',
|
||||
short_name: 'Clarity',
|
||||
description: 'CBT Case Conceptualization & Roleplay AI',
|
||||
theme_color: '#0f172a',
|
||||
background_color: '#0f172a',
|
||||
display: 'standalone',
|
||||
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: 'any maskable'
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
],
|
||||
define: {
|
||||
'process.env.API_KEY': JSON.stringify(env.VITE_GEMINI_API_KEY),
|
||||
'process.env.GEMINI_API_KEY': JSON.stringify(env.VITE_GEMINI_API_KEY)
|
||||
'process.env.GEMINI_API_KEY': JSON.stringify(env.VITE_GEMINI_API_KEY),
|
||||
'__APP_VERSION__': JSON.stringify(pkg.version),
|
||||
'__BUILD_TIMESTAMP__': JSON.stringify(new Date().toISOString()),
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
|
||||
Reference in new issue
Block a user