From 3f7cc16ce1b8adfa8c902231443f111fe4955691 Mon Sep 17 00:00:00 2001 From: Jaime Idolpx Date: Sun, 7 Jun 2026 20:37:06 -0400 Subject: [PATCH] feat: update project structure and enhance WebDAV integration with settings persistence and save status management --- AGENTS.md | 46 ++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 40 insertions(+), 6 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index cb91043..bf34d20 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -17,7 +17,9 @@ ``` src/ app/ - App.tsx # Root component: routing, nav, layout + App.tsx # Root component: routing, nav, layout, SaveStatusBadge + settings.ts # useSettings() hook — load/save config.json via WebDAV + webdav.ts # WebDAV abstraction (listDirectory, stat, put/get, etc.) components/ StatusPage.tsx # System status, activity log, file info, progress, overlays DevicesPage.tsx # Device list @@ -29,18 +31,23 @@ src/ ToolsPage.tsx # Tools SearchOverlay.tsx WiFiScanOverlay.tsx - FileBrowser.tsx + FileBrowser.tsx # WebDAV file browser (file click = select, kebab menu) figma/ # Figma-generated components ui/ # shadcn/Radix UI wrappers + vendor/ + webdav-component/ # Vendored ESM WebDAV client (no external deps) + esm/index.js + package.json imports/ logo.svg - config.json # Device config data shape + # config.json removed — config now loaded at runtime from /.sys/config.json main.tsx styles/ public/ manifest.webmanifest icon.192.png / icon.512.png service-worker.js # PWA service worker +webdav3.py # Dev Python WebDAV server (port 80, serves files/) index.html vite.config.ts ``` @@ -83,13 +90,40 @@ All individual app pages are currently stubs (`AppPage` component with "coming s 8. **PWA support** — added `manifest.webmanifest`, service worker, PWA icons (`icon.192.png`, `icon.512.png`) 9. **Vite base path** — set `base: process.env.BASE_PATH || '/config/'` in `vite.config.ts` 10. **Icon/manifest path fix** — updated icon paths in manifest and HTML; adjusted service worker registration +11. **WebDAV client** — replaced `webdav@5` npm package with vendored `webdav-component` (browser-native `fetch` + `DOMParser`, no external deps). All pages import from `webdav.ts` abstraction layer. +12. **WebDAV path fixes** — `webdav.ts`: always `decodeURIComponent` paths; use `entry.uri` (not broken `entry.path`) for servers returning relative hrefs +13. **`webdav3.py` server fixes** — `displayname` now returns leaf name only (not full path); PROPFIND depth-1 guard prevents crash when called on a file +14. **FileBrowser redesign** — file click = `onSelect` + close; folder click = navigate; per-row kebab (`MoreVert`) opens a Dialog with contextual actions; permanent "Select Folder" button in footer; no mode-toggle buttons +15. **Settings persistence** — `settings.ts` + `useSettings()` hook: loads `/.sys/config.json` via WebDAV on mount, auto-saves 3 s after last change, exposes `saveStatus` / `pendingCount` / `flushNow`; `beforeunload` flushes via `fetch keepalive` +16. **Save-status badge** — `SaveStatusBadge` in `App.tsx` header shows: idle (hidden), loading spinner, amber "N unsaved + Save button", saving spinner, saved checkmark, red error + retry ## Known Issues / Open Work -- **Service worker 404**: `https://meatloaf.cc/service-worker.js` returns 404. The SW is likely not being copied to the dist output at the correct path, or the registration URL does not account for the `/config/` base path. +- **Service worker 404**: `https://meatloaf.cc/service-worker.js` returns 404. The SW is not being served at the correct path relative to the `/config/` base. - **App pages**: All individual app pages (Directory Editor, Sector Editor, etc.) are stubs. Actual implementations are pending. -- **`apple-mobile-web-app-capable` deprecation**: Already resolved in `index.html` (uses `mobile-web-app-capable` instead). ## Configuration Shape -Config is loaded from `src/imports/config.json` and passed as props (`config`, `setConfig`) to all page components. +Config is stored at `files/.sys/config.json` on the device and loaded at runtime via WebDAV. Top-level keys: `general`, `host`, `hardware`, `wifi`, `network`, `bluetooth`, `modem`, `cassette`, `iec`, `boip`. The `useSettings()` hook in `settings.ts` provides `config` / `setConfig` to all page components (passed as props from `App.tsx`). + +## WebDAV Layer + +All WebDAV I/O goes through `src/app/webdav.ts`: + +| Export | Purpose | +|---|---| +| `getWebDAVClient()` | Returns (or creates) the singleton `WebDAVManager` pointed at `window.location.hostname` | +| `listDirectory(path)` | PROPFIND depth-1, returns `EntryInfo[]` (filters self-entry + non-direct-children) | +| `stat(path)` | PROPFIND depth-0, returns `EntryInfo \| null` | +| `putFileContents(path, data)` | PUT | +| `getFileContents(path)` | GET → `string` | +| `createFolder(path)` | MKCOL | +| `deletePath(path)` | DELETE | +| `movePath(from, to)` | MOVE | +| `fileExists(path)` | HEAD | +| `humanFileSize(bytes)` | Formatting helper | +| `normalizePath` / `splitPath` / `joinPath` / `basename` | Path utilities | + +`EntryInfo`: `{ name, path, type: 'folder'\|'file', size, lastModified, contentType }` + +The vendored `webdav-component` lives at `src/app/vendor/webdav-component/esm/index.js`. Known quirk: `entry.path` is broken for servers that return relative hrefs — always use `entry.uri` in the wrapper.