feat(web): redesign UI as an OS-style desktop shell
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>
This commit is contained in:
@@ -50,6 +50,21 @@ export async function fetchMessages(sessionId: string): Promise<Message[]> {
|
||||
return data.messages ?? []
|
||||
}
|
||||
|
||||
// null distinguishes "session doesn't exist" (404 — the session was deleted,
|
||||
// or a persisted/deep-linked window id was never valid) from a transient
|
||||
// fetch failure, which should keep returning []/retrying rather than
|
||||
// permanently flip a window into a "not found" state. Only the initial
|
||||
// per-window load (chat.ts's loadSessionChat) needs this distinction — the
|
||||
// polling loops keep using fetchMessages, where swallowing a blip into []
|
||||
// and trying again next tick is the right behavior.
|
||||
export async function fetchMessagesOrNotFound(sessionId: string): Promise<Message[] | null> {
|
||||
const res = await fetchWithAuth(`${BASE}/sessions/${sessionId}`)
|
||||
if (res.status === 404) return null
|
||||
if (!res.ok) return []
|
||||
const data = await res.json()
|
||||
return data.messages ?? []
|
||||
}
|
||||
|
||||
export async function deleteSession(sessionId: string): Promise<boolean> {
|
||||
const res = await fetchWithAuth(`${BASE}/sessions/${sessionId}`, { method: 'DELETE' })
|
||||
return res.ok
|
||||
|
||||
Reference in New Issue
Block a user