meatloaf-config/AGENTS.md

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 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
      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)

  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 fixeswebdav.ts: always decodeURIComponent paths; use entry.uri (not broken entry.path) for servers returning relative hrefs
  13. webdav3.py server fixesdisplayname 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 persistencesettings.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 badgeSaveStatusBadge 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.