# 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, Lucide React icons - **3D / animation**: Three.js r0.160 (`WebGLRenderer`, `EffectComposer`, `UnrealBloomPass`, custom GLSL shaders) - **Terminal**: xterm.js (`@xterm/xterm`, `@xterm/addon-fit`) — SerialConsolePage only - **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, WsProvider wrapper # fileManagerInitialPath / fileManagerReturnPage state for # deep-linking into MediaManager from StatusPage settings.ts # useSettings() hook — loads config.json + devices.json, saves split; # version-based migration: migrateConfig() renames old keys and # restructures; deepMergeDefaults() fills missing keys from bundled # defaults; auto-saves migrated config immediately on load webdav.ts # WebDAV abstraction (listDirectory, stat, put/get, fileExists, etc.); # FetchProgress type; clearDirectory(path) deletes all files in a dir; # streamFetch(res, onProgress) streams Response → ArrayBuffer with progress; # listDirectory accepts optional 4th signal?: AbortSignal param ws.tsx # WsProvider + useWs() — single shared WebSocket connection context components/ StatusPage.tsx # System status, activity log, reset (via WS), WS status indicator # Active Device panel: FolderOpen → MediaManager, DirectorySlideshow # or per-entry cover image, MediaSet with named entries DevicesPage.tsx # Device list, Rescan Bus (sends "iec scan" via WS) DeviceDetailOverlay.tsx GeneralPage.tsx # General/settings IECPage.tsx # IEC bus configuration NetworkPage.tsx # Network settings, WiFi scan/connect (via WS) OtherPage.tsx # Misc settings ToolsPage.tsx # Tools SearchPane.tsx # Swipeable search shell: spring slide-up, 4 tabs (Local | Assembly64 | # CommoServe | CSDb-ng), swipe dot indicators (pill = active), horizontal # snap-x scroll container; initialTab prop (0-3); onScroll updates active # tab; load bar removed (progress now inline in SearchLocal) SearchLocal.tsx # Local file search panel (panel inside SearchPane, no fixed positioning); # TOSEC/No-Intro tag parsing (PAL/NTSC, system, ISO 639-1 language); # wildcard search (* → %, ? → _); MediaEntry rows with badges + path; # faceted filter chips; module-level _store persists state across unmounts; # actions dialog: "Mount on virtual drive" + "Open containing folder"; # SD card check on mount: stat('/sd') → if missing show red error, skip # engine load; fileExists('/sd/.locate') → if missing highlight scan button; # inline load status: hides search input while loading engine/DB, shows # progress bar with bytes in its place; scan controls: Stop button aborts # via AbortController (also auto-aborts on unmount); scan shows dir/file # counts + elapsed time (Xh Xm Xs) + current path (rtl direction); # "Search your device" empty state hidden during scan SearchAssembly64.tsx # Assembly64 Leet API browser panel (panel inside SearchPane); # fetches /search/categories + /search/aql/presets on mount; # AQL search with pagination; category filter chips; ContentItem result rows; # item tap → /search/entries → file list with Download + Mount actions; # Download saves to /sd/downloads/ via putFileContents; # Client-Id: Ultimate + User-Agent: Assembly Query headers (identifies # the 1541 Ultimate cartridge; server returns 464 for unknown Client-Id # and 463 for missing/foreign User-Agent); module-level _store SearchCommoServe.tsx # CommoServe Leet API browser (panel inside SearchPane); # AQL search + preset filter chips + paginated ContentItem rows; # Assembly64-parity mount: tapping a file entry clears /sd/downloads/commoserve/, # streams main file via leetFetch+streamFetch, saves to device, downloads # cover image (matching image entry), then mounts by local dest path; # isMounting overlay in device picker shows fetching progress bar / saving # spinner / image spinner; isMounted checks local DOWNLOAD_DIR path; # LEET_BASE='https://commoserve.files.commodore.net/leet', Client-Id:'Commodore', # DOWNLOAD_DIR='/sd/downloads/commoserve'; module-level _store WiFiScanOverlay.tsx # WiFi scan results; Connect sends "connect []" via WS MediaManager.tsx # WebDAV file browser: file icons by type, kebab actions, # Configure Folder (.config editor), base_url mount logic, # media set existence check, navigate into new folder; # initialPath takes priority over localStorage when set; # Duplicate action: "name copy.ext" / "name copy N.ext" via copyPath; # .lst and .vms playlist mount: parses lines into MediaSetEntry[] MediaBrowser.tsx # Lightweight file picker (used in device mount dialogs) MediaEntry.tsx # Shared entry row: icon by extension, hover highlight (blue left border), # leftSlot / nameSlot props; exports EntryIcon + extension sets; # PLAYLIST_EXTS = {lst, vms} → Layers icon (indigo) MediaViewerEditor.tsx # Viewer/editor shell: tiled icon background (z-index:-1), toolbar MediaSet.tsx # Disk-swap button row; MediaSetEntry = string | {url, name?}; # exports mediaSetEntryUrl() helper DirectorySlideshow.tsx # Auto-advancing image slideshow from a WebDAV directory; # prev/next arrows, dot indicators, centered pause/play button; # controls appear on hover or tap; paused + idx persisted to localStorage; # module-level imgCache preloads all images; dotIdx/showIdx split # prevents flicker — showIdx only advances once target is loaded HexEditor.tsx # Hex viewer: responsive 8/16 col via ResizeObserver; bright address col ConfigEditor.tsx # YAML-style .config editor CodeEditor.tsx # CodeMirror code editor (transparent background) SerialConsolePage.tsx # xterm.js terminal over shared WS; line-buffered input with echo # suppression; tiled background overlay ProfilePage.tsx # Full-page profile menu (replaced dropdown); iOS-style grouped list AboutMeatloafPage.tsx # Project info table, GPL3 license RealityOverridePage.tsx # Full-screen WS command display; Three.js Digital Tokamak # vortex + star field; double-tap to toggle background RealityOverrideAdminPage.tsx # Command palette (Image/Audio/Video); freeform input; WS send figma/ # Figma-generated components ui/ lazy-loader.tsx # Animated progress bar for Suspense fallbacks (staged steps) marquee-text.tsx # — ping-pong scroll when text overflows; per-instance # @keyframes injected into via ResizeObserver; module-level _seq confirm-dialog.tsx # … other shadcn/Radix UI wrappers vendor/ webdav-component/ # Vendored ESM WebDAV client (no external deps) esm/index.js package.json imports/ logo.svg config.json # Bundled fallback (full merged config including devices.iec) main.tsx styles/ public/ manifest.webmanifest icon.192.png / icon.512.png service-worker.js # PWA service worker files/ .sys/ config.json # Runtime config: version, preferences, host, wifi, network, settings devices.json # Runtime devices: { "devices": { "iec": {...}, "userport": {...}, ... } } webdav3.py # Dev Python WebDAV + WebSocket server (port 80, serves files/); # _log(tag, msg) timestamped logger; ws_broadcast(text) module-level # broadcast returns sent count; _repl_thread() daemon reads stdin # and broadcasts each line; log_message() override emits # [DAV] METHOD /path → status for every request 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 | | System | `tools` | ToolsPage | | (header) | `apps` | Apps grid | | (header) | `profile` | ProfilePage | | (profile) | `general` | GeneralPage | | (profile) | `about-meatloaf` | AboutMeatloafPage | Header: fullscreen toggle, search, apps grid, profile button (→ ProfilePage). Logo click → status page. Toast messages appear `bottom-center` just above the navbar (`offset="calc(4rem + env(safe-area-inset-bottom))"`) with `richColors`, `closeButton`, and 5 s duration. ## Apps Page Grid of app cards grouped by category, each navigates to a stub `AppPage` unless implemented: - **Management**: Media Manager, **Assembly64** (implemented — opens SearchPane at Assembly64 tab), Print Manager, **Serial Console** (implemented), Short Codes - **Disk**: RAM/ROM Explorer, BAM Editor, Directory Editor, Sector Editor, Disk Visualizer, Dump/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, **Reality Override** (implemented), **Override Admin** (implemented) Header search icon opens SearchPane at tab 0 (Local). "Assembly64" AppCard opens SearchPane at tab 1. ## 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 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 17. **File icons by extension** — MediaManager shows `Disc` icon for disk extensions (`.d64`, `.d71`, `.d81`, etc.) and `HardDrive` icon for HD extensions (`.img`, `.iso`, etc.) 18. **Device URL display** — StatusPage shows `device.base_url + device.url` concatenated 19. **WebSocket server** — `webdav3.py` upgraded with RFC 6455 WebSocket handler at `/ws`; echoes received frames to all connected clients; thread-safe broadcast 20. **RealityOverridePage** — full-screen page that subscribes to WS commands and displays them centered on screen; Three.js Digital Tokamak vortex background with `UnrealBloomPass`; twinkling star field canvas always running beneath 21. **RealityOverrideAdminPage** — command palette (Image/Audio/Video conversion groups, 30 commands); freeform command input with Enter key support; sent-command flash indicator 22. **Source map warnings fixed** — stripped all `//# sourceMappingURL=` comments from 26 vendored webdav-component ESM files 23. **MediaManager: navigate into new folder** — after `handleCreateFolder`, automatically calls `load(newPath)` to navigate into the created folder 24. **MediaManager: Configure Folder** — "Configure Folder" action in kebab menu creates `.config` if absent and opens it in ConfigEditor; saving re-parses content into `folderConfig` state immediately and closes editor 25. **Mount logic: base_url=`.`** — in `.config`, `base_url: .` resolves to the current folder path; mounting a file inside the base sets `url` to the relative path; mounting outside clears `base_url` 26. **Media Set existence check** — `Promise.all(fileExists)` filter applied before building media set in both MediaManager and DeviceDetailOverlay; toast warning shown if some files are missing 27. **Mobile PWA fullscreen** — `viewport-fit=cover`, `apple-mobile-web-app-capable`, `black-translucent` status bar style, `apple-mobile-web-app-title`; body `overscroll-behavior: none`; safe-area CSS env() insets on header and nav 28. **Fullscreen API button** — header Maximize2/Minimize2 button; webkit prefix fallback; `isFullscreen` state tracks `fullscreenchange` event 29. **Logo click → status page** — logo wrapped in `