Replace the sidebar + hash-routed page shell with a desktop metaphor:
draggable app icons, apps opening as floating wmkit windows, a centered
"What should Nomos do?" task launcher, and a bottom taskbar showing all
open windows plus a system tray.
- New app registry ($lib/apps.ts) — adding an app is one entry, nothing
else to touch.
- New desktop shell components (Desktop, WindowLayer, DesktopIcon,
Taskbar, TaskLauncher) under $lib/components/desktop-shell/.
- Icon positions are a persisted, collision-avoiding grid ($lib/stores/icons.ts).
- Window layout persists across reloads (wmkit persist), with
drag-to-maximize, F6 window cycling, and now a right-click desktop menu
(cascade/tile/show desktop/reset icons) plus Cmd/Ctrl+Z undo/redo for
window moves, resizes, and closes.
- Taskbar buttons get a hover-close and self-correct their title once a
new task's real goal is known.
- New task windows (desktop launcher and the Tasks app's "New task"
button) open as a window, not a dialog, and hand off to the real
session window once the backend assigns an id.
- Fixed a real gap along the way: GET /sessions/{id} couldn't tell
"session deleted" from "session has no messages yet" (both returned
200 with an empty list) — cmd/nomos/main.go now checks existence and
404s, so a stale/persisted task window shows "Task not found" instead
of a misleadingly empty, live-looking chat.
- Test coverage for the new pure logic (icon placement/collision
avoidance, app registry id helpers) plus a vitest matchMedia polyfill
needed to import anything touching the theme store.
Deletes the now-superseded sidebar shell, MinimizedWindowsBar, and the
standalone Chat/EntityDetail pages (folded into the window layer).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
70 lines
2.5 KiB
TypeScript
70 lines
2.5 KiB
TypeScript
/// <reference types="vitest/config" />
|
|
import type { ProxyOptions } from 'vite'
|
|
import { svelte } from '@sveltejs/vite-plugin-svelte'
|
|
import tailwindcss from '@tailwindcss/vite'
|
|
import { readFileSync, existsSync } from 'fs'
|
|
import { defineConfig } from 'vite'
|
|
|
|
// In Docker, VERSION is copied into the build WORKDIR (/build/web/VERSION).
|
|
// In local dev, the cwd is web/ and VERSION is two dirs up (../../VERSION
|
|
// from vite.config.ts location in web/). Try both.
|
|
let version: string
|
|
if (existsSync('../VERSION')) {
|
|
version = readFileSync('../VERSION', 'utf-8').trim()
|
|
} else {
|
|
version = readFileSync('VERSION', 'utf-8').trim()
|
|
}
|
|
|
|
// Injects OIKOS_API_TOKEN into proxied /api requests in dev — the API no
|
|
// longer has a dev-open bypass (plans/2026-07-12-wails-desktop-app.md 0.4),
|
|
// so `OIKOS_API_TOKEN=dev-token npm run dev` needs this to reach it.
|
|
function authProxy(target: string, rewrite?: (path: string) => string): ProxyOptions {
|
|
return {
|
|
target,
|
|
...(rewrite ? { rewrite } : {}),
|
|
configure: (proxy) => {
|
|
proxy.on('proxyReq', (proxyReq) => {
|
|
const token = process.env.OIKOS_API_TOKEN
|
|
if (token) proxyReq.setHeader('Authorization', `Bearer ${token}`)
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
export default defineConfig({
|
|
plugins: [tailwindcss(), svelte()],
|
|
base: '/',
|
|
define: {
|
|
__OIKOS_VERSION__: JSON.stringify(`v${version}`),
|
|
// Lets the dev server auto-configure the SPA with the same token it
|
|
// already injects into proxied requests (see authProxy above), so `npm
|
|
// run dev` skips the "Connect to Oikos" prompt instead of re-asking for
|
|
// a token every time localStorage gets cleared. Empty string (never a
|
|
// real prod secret — see main.ts, only consulted in import.meta.env.DEV)
|
|
// when OIKOS_API_TOKEN isn't set, so the prompt still shows if unconfigured.
|
|
__OIKOS_DEV_TOKEN__: JSON.stringify(process.env.OIKOS_API_TOKEN ?? ''),
|
|
},
|
|
resolve: {
|
|
alias: { $lib: '/src/lib' }
|
|
},
|
|
build: {
|
|
outDir: 'dist',
|
|
emptyOutDir: true
|
|
},
|
|
server: {
|
|
proxy: {
|
|
'/api': authProxy('http://localhost:8090'),
|
|
// Production Caddy strips /agent before forwarding to nomos
|
|
// (compose/caddy/Caddyfile.oikos handle_path /agent/*); match that
|
|
// here so dev and prod agree on nomos's actual route paths.
|
|
'/agent': authProxy('http://localhost:8092', (path) => path.replace(/^\/agent/, ''))
|
|
}
|
|
},
|
|
test: {
|
|
environment: 'jsdom',
|
|
globals: true,
|
|
setupFiles: ['src/test-setup.ts'],
|
|
include: ['src/**/*.{test,spec}.{ts,js}']
|
|
}
|
|
})
|