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 Tooltip from './components/Tooltip';
|
||||||
import { Brain, Info, ShieldCheck, Filter, Plus, BookOpen, Activity, Zap, Settings } from 'lucide-react';
|
import { Brain, Info, ShieldCheck, Filter, Plus, BookOpen, Activity, Zap, Settings } from 'lucide-react';
|
||||||
import { Button } from './components/ui/button';
|
import { Button } from './components/ui/button';
|
||||||
|
import { ReloadPrompt } from './components/ReloadPrompt';
|
||||||
|
import { VersionBadge } from './components/VersionBadge';
|
||||||
|
|
||||||
const DEFAULT_SETTINGS: UserSettings = {
|
const DEFAULT_SETTINGS: UserSettings = {
|
||||||
voiceName: 'Zephyr',
|
voiceName: 'Zephyr',
|
||||||
@@ -158,6 +160,7 @@ const App: React.FC = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-[#FDFDFF] text-slate-900 font-sans relative animate-in fade-in duration-500">
|
<div className="min-h-screen bg-[#FDFDFF] text-slate-900 font-sans relative animate-in fade-in duration-500">
|
||||||
|
<ReloadPrompt />
|
||||||
{/* Modals */}
|
{/* Modals */}
|
||||||
{showGenerator && <ScenarioGenerator onGenerate={handleScenarioGenerated} onCancel={() => setShowGenerator(false)} />}
|
{showGenerator && <ScenarioGenerator onGenerate={handleScenarioGenerated} onCancel={() => setShowGenerator(false)} />}
|
||||||
{showReference && <CBTReference onClose={() => setShowReference(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">
|
<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="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">
|
<div className="flex items-center gap-4 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-2">
|
||||||
|
<Brain size={20} />
|
||||||
|
<span className="text-sm font-bold uppercase tracking-widest text-slate-900">Cognitive Clarity</span>
|
||||||
|
</div>
|
||||||
|
<VersionBadge />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</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",
|
"name": "cognitive-clarity",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.0.0",
|
"version": "1.0.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"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",
|
"preview": "vite preview",
|
||||||
"lint": "tsc --noEmit"
|
"lint": "tsc --noEmit"
|
||||||
},
|
},
|
||||||
@@ -26,7 +32,14 @@
|
|||||||
"react": "^19.2.3",
|
"react": "^19.2.3",
|
||||||
"react-dom": "^19.2.3",
|
"react-dom": "^19.2.3",
|
||||||
"tailwind-merge": "^3.5.0",
|
"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": {
|
"devDependencies": {
|
||||||
"@types/node": "^22.19.15",
|
"@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 path from 'path';
|
||||||
import { defineConfig, loadEnv } from 'vite';
|
import { defineConfig, loadEnv } from 'vite';
|
||||||
import react from '@vitejs/plugin-react';
|
import react from '@vitejs/plugin-react';
|
||||||
|
import { VitePWA } from 'vite-plugin-pwa';
|
||||||
|
import pkg from './package.json';
|
||||||
|
|
||||||
export default defineConfig(({ mode }) => {
|
export default defineConfig(({ mode }) => {
|
||||||
const env = loadEnv(mode, '.', '');
|
const env = loadEnv(mode, '.', '');
|
||||||
@@ -9,10 +11,50 @@ export default defineConfig(({ mode }) => {
|
|||||||
port: 3000,
|
port: 3000,
|
||||||
host: '0.0.0.0',
|
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: {
|
define: {
|
||||||
'process.env.API_KEY': JSON.stringify(env.VITE_GEMINI_API_KEY),
|
'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: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
|
|||||||
Reference in new issue
Block a user