Private
Public Access
0
0
Files
manual_slop/docs/superpowers/plans/2026-06-02-docker-web-frontend.md
T

591 lines
18 KiB
Markdown

# Docker & Web Frontend Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Containerize Manual Slop for Unraid deployment. Add a web frontend via the imgui-bundle web backend (per the [explorer docs](https://imgui-bundle.pages.dev/explorer/)). Preserve the existing Hook API on :8999 for agent access.
**Architecture:** Python 3.11-slim base image with `uv` for dependency management. The container runs `sloppy.py` in web mode (via a new `--web-host` / `--web-port` arg), which uses the imgui-bundle Hello ImGui web backend to render ImGui to a WebGL canvas in the browser. Two exposed ports: 8080 (web client) and 8999 (hook API). Volumes for project data and app state.
**Tech Stack:** Docker, docker-compose, Python 3.11, imgui-bundle (web backend), FastAPI/Uvicorn (existing, for hooks)
- **[checkpoint: 99a84bd]**
- **No subagents.**
- **Pre-edit checkpoint:** `git add .` before any file edit.
- **Per-file atomic commits.**
- **Commit message format:** `<type>(<scope>): <imperative description>`.
- **Git note format:** 3-8 line rationale per commit.
- **Style baseline:** 1-space indent, no comments, type hints.
---
## File Structure
| File | Action | Responsibility |
|---|---|---|
| `Dockerfile` | Create | Container build for Manual Slop |
| `docker-compose.yml` | Create | Multi-container orchestration for Unraid |
| `scripts/docker_build.sh` | Create | Build helper |
| `scripts/docker_run.sh` | Create | Run helper with env var wiring |
| `sloppy.py` | Modify | Add `--web-host` and `--web-port` args |
| `docs/guide_docker_deployment.md` | Create | Unraid setup guide |
| `tests/test_docker_build.py` | Create | Opt-in Docker build test |
| `pyproject.toml` | Modify | Add `docker` marker |
---
## Task 1: Add web args to `sloppy.py`
**Files:**
- Modify: `sloppy.py`
- [ ] **Step 1.1: Pre-edit checkpoint**
```powershell
git -C C:\projects\manual_slop add .
```
- [ ] **Step 1.2: Read current `sloppy.py`**
Use `manual-slop_read_file` to see current contents.
- [ ] **Step 1.3: Add the new args**
Find the existing argument parser setup and add the web args. If there isn't an argparse, look for how args are handled. Add:
```python
# In sloppy.py, after existing argument definitions
import argparse
parser = argparse.ArgumentParser(description="Manual Slop entry point")
# ... existing args ...
parser.add_argument("--web-host", default=None, help="Enable web mode and bind to this host (e.g., 0.0.0.0)")
parser.add_argument("--web-port", type=int, default=8080, help="Web mode port (default: 8080)")
parser.add_argument("--enable-test-hooks", action="store_true", help="Enable the HookServer on :8999 for external automation")
args = parser.parse_args()
```
If the existing entry point uses a different mechanism, integrate the args there.
- [ ] **Step 1.4: Add the web mode branch**
After argument parsing, before the existing main launch, add:
```python
if args.web_host is not None:
from imgui_bundle import hello_imgui
from src.api_hooks import HookServer
if args.enable_test_hooks:
hook_server = HookServer()
hook_server.start()
runner_params = hello_imgui.RunnerParams()
runner_params.app_window_params.window_title = "Manual Slop (Web)"
runner_params.app_window_params.borderless = True
runner_params.imgui_window_params.default_imgui_window_type = hello_imgui.DefaultImGuiWindowType.provide_full_screen_docker_space
runner_params.app_window_params.restore_previous_window_size = True
from src.gui_2 import App
app = App()
hello_imgui.run(runner_params, lambda: app.render_frame())
```
- [ ] **Step 1.5: Commit**
```bash
git -C C:\projects\manual_slop add sloppy.py
git -C C:\projects\manual_slop commit -m "feat(sloppy): add --web-host and --web-port args for web mode"
git -C C:\projects\manual_slop log -1 --format='%H' | ForEach-Object { git -C C:\projects\manual_slop notes add -m "Adds --web-host (enables web mode) and --web-port (default 8080) args. When --web-host is set, launches via Hello ImGui web backend with full-screen docking. Preserves --enable-test-hooks for agent access via :8999." $_ }
```
---
## Task 2: Create the Dockerfile
**Files:**
- Create: `Dockerfile`
- [ ] **Step 2.1: Pre-edit checkpoint**
```powershell
git -C C:\projects\manual_slop add .
```
- [ ] **Step 2.2: Create the Dockerfile**
```dockerfile
FROM python:3.11-slim
RUN apt-get update && apt-get install -y --no-install-recommends \
git curl ca-certificates \
&& rm -rf /var/lib/apt/lists/*
RUN pip install uv
WORKDIR /app
COPY pyproject.toml uv.lock ./
RUN uv sync --frozen
COPY . .
RUN mkdir -p /projects /config
VOLUME ["/projects", "/config"]
EXPOSE 8080 8999
HEALTHCHECK --interval=30s --timeout=5s --start-period=30s --retries=3 \
CMD curl -f http://127.0.0.1:8999/status || exit 1
ENTRYPOINT ["uv", "run", "sloppy.py", "--enable-test-hooks", "--web-host=0.0.0.0", "--web-port=8080"]
```
- [ ] **Step 2.3: Add a `.dockerignore`**
```dockerignore
# .dockerignore
.git
__pycache__
*.pyc
.pytest_cache
.ruff_cache
.venv
.env
tests/artifacts/
tests/logs/
logs/
md_gen/
*.log
```
- [ ] **Step 2.4: Commit**
```bash
git -C C:\projects\manual_slop add Dockerfile .dockerignore
git -C C:\projects\manual_slop commit -m "feat(docker): add Dockerfile and .dockerignore for containerized deployment"
git -C C:\projects\manual_slop log -1 --format='%H' | ForEach-Object { git -C C:\projects\manual_slop notes add -m "Python 3.11-slim base, uv for dep management, copies pyproject + uv.lock first for layer caching. Exposes 8080 (web) and 8999 (hooks). Volumes for /projects and /config. Healthcheck on hook status. .dockerignore excludes test artifacts and git." $_ }
```
---
## Task 3: Create the docker-compose.yml
**Files:**
- Create: `docker-compose.yml`
- [ ] **Step 3.1: Pre-edit checkpoint**
```powershell
git -C C:\projects\manual_slop add .
```
- [ ] **Step 3.2: Create the compose file**
```yaml
version: '3.8'
services:
manual_slop:
build: .
image: manual_slop:latest
container_name: manual_slop
ports:
- "8999:8999"
- "8080:8080"
volumes:
- /mnt/user/projects:/projects:rw
- /mnt/user/appdata/manual_slop:/config:rw
environment:
- GEMINI_API_KEY=${GEMINI_API_KEY:-}
- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY:-}
- DEEPSEEK_API_KEY=${DEEPSEEK_API_KEY:-}
- MINIMAX_API_KEY=${MINIMAX_API_KEY:-}
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://127.0.0.1:8999/status"]
interval: 30s
timeout: 5s
retries: 3
start_period: 30s
```
- [ ] **Step 3.3: Commit**
```bash
git -C C:\projects\manual_slop add docker-compose.yml
git -C C:\projects\manual_slop commit -m "feat(docker): add docker-compose.yml for Unraid deployment"
git -C C:\projects\manual_slop log -1 --format='%H' | ForEach-Object { git -C C:\projects\manual_slop notes add -m "Two ports (8999 hooks, 8080 web), two volumes (projects, appdata config), env vars for 4 providers, healthcheck on hook status. Unraid-friendly paths." $_ }
```
---
## Task 4: Create the build/run scripts
**Files:**
- Create: `scripts/docker_build.sh`
- Create: `scripts/docker_run.sh`
- [ ] **Step 4.1: Pre-edit checkpoint**
```powershell
git -C C:\projects\manual_slop add .
```
- [ ] **Step 4.2: Create `scripts/docker_build.sh`**
```bash
#!/usr/bin/env bash
# scripts/docker_build.sh
# Build the Manual Slop Docker image.
set -euo pipefail
cd "$(dirname "$0")/.."
docker build -t manual_slop:latest .
```
- [ ] **Step 4.3: Create `scripts/docker_run.sh`**
```bash
#!/usr/bin/env bash
# scripts/docker_run.sh
# Run the Manual Slop container.
set -euo pipefail
cd "$(dirname "$0")/.."
docker compose up -d
```
- [ ] **Step 4.4: Make scripts executable and commit**
```bash
git -C C:\projects\manual_slop add scripts/docker_build.sh scripts/docker_run.sh
git -C C:\projects\manual_slop commit -m "feat(docker): add build and run shell scripts"
git -C C:\projects\manual_slop log -1 --format='%H' | ForEach-Object { git -C C:\projects\manual_slop notes add -m "Two thin shell scripts: build (docker build -t manual_slop:latest .) and run (docker compose up -d). Will be made executable in chmod step during deployment." $_ }
```
---
## Task 5: Add the `docker` marker to `pyproject.toml`
**Files:**
- Modify: `pyproject.toml`
- [ ] **Step 5.1: Pre-edit checkpoint**
```powershell
git -C C:\projects\manual_slop add .
```
- [ ] **Step 5.2: Add the marker**
Find the markers list (from Task 1 of the clean-install plan) and add:
```toml
markers = [
"integration: integration tests requiring live GUI",
"strict: tests that require strict mode",
"clean_install: clean install verification (opt-in via RUN_CLEAN_INSTALL_TEST=1)",
"docker: docker build and run test (opt-in via RUN_DOCKER_TEST=1)",
]
```
- [ ] **Step 5.3: Commit**
```bash
git -C C:\projects\manual_slop add pyproject.toml
git -C C:\projects\manual_slop commit -m "test(pytest): add docker marker"
git -C C:\projects\manual_slop log -1 --format='%H' | ForEach-Object { git -C C:\projects\manual_slop notes add -m "Registers the docker marker. Tests using Docker can be filtered with -m docker or -m 'not docker'." $_ }
```
---
## Task 6: Write the Docker build test
**Files:**
- Create: `tests/test_docker_build.py`
- [ ] **Step 6.1: Pre-edit checkpoint**
```powershell
git -C C:\projects\manual_slop add .
```
- [ ] **Step 6.2: Create the test file**
```python
# tests/test_docker_build.py
import os
import shutil
import subprocess
import time
import pytest
import requests
IMAGE_NAME = "manual_slop:test"
CONTAINER_NAME = "manual_slop_test_container"
WEB_PORT = 18080
HOOK_PORT = 18999
@pytest.mark.docker
def test_docker_image_builds(tmp_path):
"""Build the Docker image. Slow; opt-in."""
if os.environ.get("RUN_DOCKER_TEST") != "1":
pytest.skip("Set RUN_DOCKER_TEST=1 to enable")
if not _docker_available():
pytest.skip("Docker not available in this environment")
repo_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
result = subprocess.run(
["docker", "build", "-t", IMAGE_NAME, "."],
cwd=repo_root,
capture_output=True, text=True, timeout=600,
)
assert result.returncode == 0, f"Docker build failed: {result.stderr}"
@pytest.mark.docker
def test_docker_container_starts_and_responds():
"""Run the container, verify web and hook endpoints respond."""
if os.environ.get("RUN_DOCKER_TEST") != "1":
pytest.skip("Set RUN_DOCKER_TEST=1 to enable")
if not _docker_available():
pytest.skip("Docker not available in this environment")
_cleanup_container()
repo_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
result = subprocess.run(
[
"docker", "run", "-d",
"--name", CONTAINER_NAME,
"-p", f"{WEB_PORT}:8080",
"-p", f"{HOOK_PORT}:8999",
IMAGE_NAME,
],
cwd=repo_root,
capture_output=True, text=True, timeout=30,
)
assert result.returncode == 0, f"Docker run failed: {result.stderr}"
try:
start = time.time()
ready = False
while time.time() - start < 90:
try:
r = requests.get(f"http://127.0.0.1:{HOOK_PORT}/status", timeout=1)
if r.status_code == 200:
ready = True
break
except (requests.ConnectionError, requests.Timeout):
pass
time.sleep(1)
assert ready, "Container hook API did not respond within 90s"
r = requests.get(f"http://127.0.0.1:{WEB_PORT}/", timeout=5)
assert r.status_code == 200
body = r.content.lower()
assert b"<html" in body or b"<!doctype" in body, "Web endpoint did not return HTML"
finally:
_cleanup_container()
def _docker_available() -> bool:
return shutil.which("docker") is not None
def _cleanup_container() -> None:
subprocess.run(
["docker", "rm", "-f", CONTAINER_NAME],
capture_output=True,
)
```
- [ ] **Step 6.3: Run in skip mode**
```powershell
uv run pytest tests/test_docker_build.py -v
```
Expected: 2 skipped.
- [ ] **Step 6.4: Commit**
```bash
git -C C:\projects\manual_slop add tests/test_docker_build.py
git -C C:\projects\manual_slop commit -m "test(docker): add opt-in build and container-run tests"
git -C C:\projects\manual_slop log -1 --format='%H' | ForEach-Object { git -C C:\projects\manual_slop notes add -m "Two opt-in tests: build image (RUN_DOCKER_TEST=1), run container and verify hook + web endpoints. Skipped by default. Docker daemon required." $_ }
```
---
## Task 7: Write the Unraid deployment guide
**Files:**
- Create: `docs/guide_docker_deployment.md`
- [ ] **Step 7.1: Pre-edit checkpoint**
```powershell
git -C C:\projects\manual_slop add .
```
- [ ] **Step 7.2: Create the guide**
```markdown
# Docker Deployment Guide (Unraid)
[Top](../README.md) | [Architecture](guide_architecture.md) | [Tools & IPC](guide_tools.md)
---
## Overview
This guide covers deploying Manual Slop on Unraid (or any Docker host) using the containerized image. The deployment provides:
- A web-accessible ImGui GUI (browser-based, no local display required)
- The Hook API on `:8999` for agent access
- Persistent volumes for projects and app state
## Prerequisites
- Unraid 6.10+ (or any Docker host with compose support)
- A project share mounted at `/mnt/user/projects` (or edit `docker-compose.yml` to match your path)
- API keys for the providers you want to use
## Building the Image
From the repo root:
```bash
docker build -t manual_slop:latest .
```
Or use the helper:
```bash
./scripts/docker_build.sh
```
## Running the Container
Edit `docker-compose.yml` to set your volume paths and provider keys (via `.env` file or environment).
```bash
# Create a .env file with your API keys
cat > .env <<EOF
GEMINI_API_KEY=your-key-here
ANTHROPIC_API_KEY=your-key-here
DEEPSEEK_API_KEY=your-key-here
MINIMAX_API_KEY=your-key-here
EOF
# Start the container
docker compose up -d
```
## Accessing the GUI
Open a browser and navigate to:
- `http://<your-unraid-ip>:8080` for the web client
- `http://<your-unraid-ip>:8999/status` for the hook API health check
The web client renders the ImGui panels via WebGL. The Hello ImGui web backend streams frame deltas over WebSocket.
## Agent Access
Agents interact with the running container via the Hook API on `:8999`. Examples:
```bash
# Check status
curl http://<your-unraid-ip>:8999/status
# Get MMA state
curl http://<your-unraid-ip>:8999/api/gui/mma_status
```
See [guide_tools.md](guide_tools.md) for the full Hook API reference.
## Volumes
- `/projects` — Mounted from `/mnt/user/projects` by default. Your project workspaces live here. The `manual_slop.toml` per project is in this directory.
- `/config` — Mounted from `/mnt/user/appdata/manual_slop` by default. App state: presets, personas, log directory, workspace profiles.
## Updating
```bash
git pull
docker build -t manual_slop:latest .
docker compose up -d
```
## Backup
Back up `/config` to preserve presets, personas, and workspace profiles. Back up `/projects/<project>/conductor/` to preserve track history.
## Troubleshooting
- **Port conflicts:** Edit `docker-compose.yml` to change the host port (e.g., `"18080:8080"` to use 18080 on the host).
- **Permission errors:** Ensure the Unraid share has write permissions for the container's UID.
- **Hook API not responding:** Check `docker logs manual_slop` for the startup output. The hook server should log "HookServer started on :8999".
```
- [ ] **Step 7.3: Commit**
```bash
git -C C:\projects\manual_slop add docs/guide_docker_deployment.md
git -C C:\projects\manual_slop commit -m "docs(docker): add Unraid deployment guide"
git -C C:\projects\manual_slop log -1 --format='%H' | ForEach-Object { git -C C:\projects\manual_slop notes add -m "Unraid-specific setup guide: prerequisites, build, run via compose, web access URLs, agent access via hook API, volumes, updating, backup, troubleshooting." $_ }
```
---
## Task 8: Phase Completion Verification
- [ ] **Step 8.1: Verify all files exist and are syntactically valid**
```powershell
# Python syntax
python -c "import ast; ast.parse(open('sloppy.py').read())"
# YAML syntax (if PyYAML available)
python -c "import yaml; yaml.safe_load(open('docker-compose.yml').read())"
# Dockerfile syntax (if hadolint available)
# hadolint Dockerfile || echo "hadolint not installed, skipping"
# Markdown lint (skip if not configured)
```
- [ ] **Step 8.2: Run the test suite in skip mode**
```powershell
uv run pytest tests/test_docker_build.py -v
```
Expected: 2 skipped.
- [ ] **Step 8.3: If Docker is available, run the tests**
```powershell
RUN_DOCKER_TEST=1 uv run pytest tests/test_docker_build.py -v
```
- [ ] **Step 8.4: Create the checkpoint commit**
```bash
git -C C:\projects\manual_slop commit --allow-empty -m "conductor(checkpoint): Docker & web frontend complete"
git -C C:\projects\manual_slop log -1 --format='%H' | ForEach-Object { git -C C:\projects\manual_slop notes add -m "Track complete. Dockerfile, docker-compose.yml, build/run scripts, --web-host arg, Unraid deployment guide, opt-in Docker build test. imgui-bundle web backend integration pending Hello ImGui runner config tuning." $_ }
```
---
## Self-Review
- **Spec coverage:** All design sections have tasks. ✓
- **Placeholder scan:** All code blocks are complete. ✓
- **Type consistency:** Args named consistently (`--web-host`, `--web-port`, `--enable-test-hooks`). ✓
- **Risk acknowledged:** The web backend integration is experimental and may need iteration after testing. The checkpoint note flags this. ✓