feat(ui): add PWA support with vite-plugin-pwa

- Add vite.config.pwa.ts extending the base config with VitePWA plugin
- Generate PWA icons at build time from source logo via @vite-pwa/assets-generator
- Add web app manifest with name, theme color, display overrides
- Add Workbox runtime caching: NetworkFirst for API, CacheFirst for assets
- Set navigateFallback to null to preserve server-side auth redirects
- Server build uses build:pwa for PWA-enabled output; Electron/Tauri use
  the base build without PWA

Signed-off-by: Jesper Derehag <jderehag@hotmail.com>
This commit is contained in:
Jesper Derehag
2026-02-07 00:16:47 +01:00
parent 157fe9d6b4
commit 99474955af
5 changed files with 77 additions and 1 deletions

View File

@@ -62,6 +62,17 @@ You can configure the server using flags or environment variables:
Use this only when access is already protected by another layer (SSO proxy, VPN, Coder workspace auth, etc.). Use this only when access is already protected by another layer (SSO proxy, VPN, Coder workspace auth, etc.).
If you bind to `0.0.0.0` while skipping auth, anyone who can reach the port can access the API. If you bind to `0.0.0.0` while skipping auth, anyone who can reach the port can access the API.
### Progressive Web App (PWA)
When running as a server CodeNomad can also be installed as a PWA from any supported browser, giving you a native app experience just like the Electron installation but executing on the remote server instead.
1. Open the CodeNomad UI in a Chromium-based browser (Chrome, Edge, Brave, etc.).
2. Click the install icon in the address bar, or use the browser menu → "Install CodeNomad".
3. The app will open in a standalone window and appear in your OS app list.
> **TLS requirement**
> Browsers require a secure (`https://`) connection for PWA installation.
> If you host CodeNomad on a remote machine, serve it behind a reverse proxy (e.g. Caddy, nginx) with a valid TLS certificate — self-signed certificates will not work.
### Data Storage ### Data Storage
- **Config**: `~/.config/codenomad/config.json` - **Config**: `~/.config/codenomad/config.json`
- **Instance Data**: `~/.config/codenomad/instances` (chat history, etc.) - **Instance Data**: `~/.config/codenomad/instances` (chat history, etc.)

View File

@@ -18,7 +18,7 @@
}, },
"scripts": { "scripts": {
"build": "npm run build:ui && npm run prepare-ui && tsc -p tsconfig.json && node ./scripts/copy-auth-pages.mjs && npm run prepare-config", "build": "npm run build:ui && npm run prepare-ui && tsc -p tsconfig.json && node ./scripts/copy-auth-pages.mjs && npm run prepare-config",
"build:ui": "npm run build --prefix ../ui", "build:ui": "npm run build:pwa --prefix ../ui",
"prepare-ui": "node ./scripts/copy-ui-dist.mjs", "prepare-ui": "node ./scripts/copy-ui-dist.mjs",
"prepare-config": "node ./scripts/copy-opencode-config.mjs", "prepare-config": "node ./scripts/copy-opencode-config.mjs",
"dev": "cross-env CODENOMAD_DEV=1 CODENOMAD_SERVER_PASSWORD=codenomad-dev CLI_UI_DEV_SERVER=http://localhost:3000 tsx src/index.ts", "dev": "cross-env CODENOMAD_DEV=1 CODENOMAD_SERVER_PASSWORD=codenomad-dev CLI_UI_DEV_SERVER=http://localhost:3000 tsx src/index.ts",

View File

@@ -1,3 +1,4 @@
node_modules/ node_modules/
dist/ dist/
.vite/ .vite/
src/renderer/public/logo.png

View File

@@ -7,6 +7,7 @@
"scripts": { "scripts": {
"dev": "vite dev", "dev": "vite dev",
"build": "vite build", "build": "vite build",
"build:pwa": "vite build -c vite.config.pwa.ts",
"preview": "vite preview", "preview": "vite preview",
"typecheck": "tsc --noEmit -p tsconfig.json" "typecheck": "tsc --noEmit -p tsconfig.json"
}, },
@@ -30,11 +31,13 @@
"solid-toast": "^0.5.0" "solid-toast": "^0.5.0"
}, },
"devDependencies": { "devDependencies": {
"@vite-pwa/assets-generator": "^1.0.2",
"autoprefixer": "10.4.21", "autoprefixer": "10.4.21",
"postcss": "8.5.6", "postcss": "8.5.6",
"tailwindcss": "3", "tailwindcss": "3",
"typescript": "^5.3.0", "typescript": "^5.3.0",
"vite": "^5.0.0", "vite": "^5.0.0",
"vite-plugin-pwa": "^1.2.0",
"vite-plugin-solid": "^2.10.0" "vite-plugin-solid": "^2.10.0"
} }
} }

View File

@@ -0,0 +1,61 @@
import { copyFileSync } from "fs"
import { defineConfig } from "vite"
import { VitePWA } from "vite-plugin-pwa"
import { resolve } from "path"
import baseConfig from "./vite.config"
export default defineConfig({
...baseConfig,
plugins: [
...(baseConfig.plugins ?? []),
{
name: "copy-pwa-source-icon",
buildStart() {
// vite-pwa-assets requires the source image inside public/
copyFileSync(
resolve(__dirname, "src/images/CodeNomad-Icon.png"),
resolve(__dirname, "src/renderer/public/logo.png"),
)
},
},
VitePWA({
registerType: "autoUpdate",
injectRegister: "auto",
pwaAssets: {
preset: "minimal-2023",
image: "public/logo.png",
},
manifest: {
name: "CodeNomad",
short_name: "CodeNomad",
id: "/",
start_url: "/",
display: "standalone",
display_override: ["window-controls-overlay", "standalone"],
background_color: "#1a1a1a",
theme_color: "#1a1a1a",
},
workbox: {
navigateFallback: null,
runtimeCaching: [
{
urlPattern: /^\/api\/.*$/i,
handler: "NetworkFirst",
options: {
cacheName: "api-cache",
expiration: { maxEntries: 50, maxAgeSeconds: 60 },
},
},
{
urlPattern: /.*\.(?:js|css|png|jpg|jpeg|svg|webp|woff2?)$/i,
handler: "CacheFirst",
options: {
cacheName: "asset-cache",
expiration: { maxEntries: 200, maxAgeSeconds: 60 * 60 * 24 * 30 },
},
},
],
},
}),
],
})