6.8 KiB
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 viaBASE_PATHenv 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
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 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)
- Initial commit — project scaffolded with basic pages
- StatusPage enhancements — file info display, loading progress bar
- StatusPage overlays — reset functionality, directory map overlay, disk map overlay
- SearchOverlay polish — improved layout, responsiveness, background opacity
- DeviceDetailOverlay — media button titles improved
- Apps page — added
appsnav item; builtAppCardgrid with category groupings; added stubAppPagefor all individual apps - StatusPage layout — reorganized action buttons; enhanced system status display and activity log
- PWA support — added
manifest.webmanifest, service worker, PWA icons (icon.192.png,icon.512.png) - Vite base path — set
base: process.env.BASE_PATH || '/config/'invite.config.ts - Icon/manifest path fix — updated icon paths in manifest and HTML; adjusted service worker registration
- WebDAV client — replaced
webdav@5npm package with vendoredwebdav-component(browser-nativefetch+DOMParser, no external deps). All pages import fromwebdav.tsabstraction layer. - WebDAV path fixes —
webdav.ts: alwaysdecodeURIComponentpaths; useentry.uri(not brokenentry.path) for servers returning relative hrefs webdav3.pyserver fixes —displaynamenow returns leaf name only (not full path); PROPFIND depth-1 guard prevents crash when called on a file- 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 - Settings persistence —
settings.ts+useSettings()hook: loads/.sys/config.jsonvia WebDAV on mount, auto-saves 3 s after last change, exposessaveStatus/pendingCount/flushNow;beforeunloadflushes viafetch keepalive - Save-status badge —
SaveStatusBadgeinApp.tsxheader 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.jsreturns 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.