Merge pull request #121 from jderehag/dev

feat(ui): add PWA support with vite-plugin-pwa
This commit is contained in:
Shantur Rathore
2026-02-07 21:34:29 +00:00
committed by GitHub
5 changed files with 4637 additions and 46 deletions

4618
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -62,6 +62,18 @@ 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 generally won't work unless they are explicitly trusted by the device/browser (e.g., via a custom CA).
### 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

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

View File

@@ -30,11 +30,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

@@ -1,6 +1,7 @@
import fs from "fs" import fs from "fs"
import { defineConfig } from "vite" import { defineConfig } from "vite"
import solid from "vite-plugin-solid" import solid from "vite-plugin-solid"
import { VitePWA } from "vite-plugin-pwa"
import { resolve } from "path" import { resolve } from "path"
const uiPackageJson = JSON.parse(fs.readFileSync(resolve(__dirname, "package.json"), "utf-8")) as { version?: string } const uiPackageJson = JSON.parse(fs.readFileSync(resolve(__dirname, "package.json"), "utf-8")) as { version?: string }
@@ -20,6 +21,55 @@ export default defineConfig({
}) })
}, },
}, },
{
name: "prepare-pwa-source-icon",
apply: "build",
buildStart() {
// vite-pwa-assets requires the source image inside root/public/
const source = resolve(__dirname, "src/images/CodeNomad-Icon.png")
const publicDir = resolve(__dirname, "src/renderer/public")
const dest = resolve(publicDir, "logo.png")
fs.mkdirSync(publicDir, { recursive: true })
fs.copyFileSync(source, dest)
},
},
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: {
// Preserve server-side auth redirects (e.g., /login) instead of serving cached index.html.
navigateFallback: null,
// Only cache static UI assets; never cache API traffic.
runtimeCaching: [
{
urlPattern: ({ url, request }) => {
if (url.pathname.startsWith("/api/")) return false
return ["script", "style", "image", "font"].includes(request.destination)
},
handler: "CacheFirst",
options: {
cacheName: "asset-cache",
expiration: { maxEntries: 200, maxAgeSeconds: 60 * 60 * 24 * 30 },
cacheableResponse: { statuses: [0, 200] },
},
},
],
},
}),
], ],
css: { css: {
postcss: "./postcss.config.js", postcss: "./postcss.config.js",