130 lines
6.8 KiB
Markdown
130 lines
6.8 KiB
Markdown
# Meatloaf Manipulator — Agent Context
|
|
|
|
## Project Overview
|
|
|
|
**Meatloaf Manipulator** is a React/Vite PWA for configuring and managing Meatloaf devices (Commodore 64 retro computing hardware). It is served at `https://meatloaf.cc/config/`.
|
|
|
|
## Tech Stack
|
|
|
|
- **Framework**: React 18 + Vite 6
|
|
- **Styling**: Tailwind CSS 4 (via `@tailwindcss/vite`)
|
|
- **UI components**: Radix UI primitives, MUI icons (`@mui/icons-material`), Lucide React icons
|
|
- **Routing**: React Router 7 (single-page, page state managed in `App.tsx`)
|
|
- **Build base path**: `/config/` (overridable via `BASE_PATH` env var)
|
|
|
|
## Project Structure
|
|
|
|
```
|
|
src/
|
|
app/
|
|
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
|
|
DeviceDetailOverlay.tsx
|
|
GeneralPage.tsx # General/settings
|
|
IECPage.tsx # IEC bus configuration
|
|
NetworkPage.tsx # Network settings
|
|
OtherPage.tsx # Misc settings
|
|
ToolsPage.tsx # Tools
|
|
SearchOverlay.tsx
|
|
WiFiScanOverlay.tsx
|
|
MediaBrowser.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 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
|
|
```
|
|
|
|
## Navigation
|
|
|
|
Bottom tab bar + header icons:
|
|
|
|
| Nav item | Page key | Component |
|
|
|-------------|-------------|------------------|
|
|
| Status | `status` | StatusPage |
|
|
| Devices | `devices` | DevicesPage |
|
|
| IEC | `iec` | IECPage |
|
|
| Network | `network` | NetworkPage |
|
|
| More | `other` | OtherPage |
|
|
| (header) | `apps` | Apps grid |
|
|
| (profile) | `general` | GeneralPage |
|
|
| (profile) | `tools` | ToolsPage |
|
|
|
|
## Apps Page
|
|
|
|
Grid of app cards grouped by category, each navigates to a stub `AppPage`:
|
|
|
|
- **Disk**: Directory Editor, Sector Editor, BAM Editor, Disk Visualizer, RAM/ROM Explorer, Dump Disk Image, Write Disk Image
|
|
- **Cartridge**: PRG to CRT, Magic Desk Cart Builder, Easy Flash Cart Builder
|
|
- **Development**: Basic Editor, Assembler, Sprite Editor, Character Set Editor, Petscii Editor
|
|
- **Display**: Idle Animation, Loading Animation
|
|
|
|
All individual app pages are currently stubs (`AppPage` component with "coming soon" message).
|
|
|
|
## Work to Date (chronological)
|
|
|
|
1. **Initial commit** — project scaffolded with basic pages
|
|
2. **StatusPage enhancements** — file info display, loading progress bar
|
|
3. **StatusPage overlays** — reset functionality, directory map overlay, disk map overlay
|
|
4. **SearchOverlay polish** — improved layout, responsiveness, background opacity
|
|
5. **DeviceDetailOverlay** — media button titles improved
|
|
6. **Apps page** — added `apps` nav item; built `AppCard` grid with category groupings; added stub `AppPage` for all individual apps
|
|
7. **StatusPage layout** — reorganized action buttons; enhanced system status display and activity log
|
|
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. **MediaBrowser 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 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.
|
|
|
|
## Configuration Shape
|
|
|
|
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.
|