The .modal CSS display:flex override the hidden attribute, so the empty job
modal was always visible on load and closeModal() could not hide it - the
'non-dismissable empty error'. Add .modal[hidden] { display:none } so the
hidden attribute wins.
The blanket errorhandler(Exception) also converted normal HTTP 404s (e.g.
/favicon.ico, /apple-touch-icon-*.png requested by the browser) into 500s,
flooding the log. Return HTTPException instances unchanged so only genuine
500s are logged as errors.
The controller ran silently and surfaced errors as opaque/empty bodies,
making it impossible to diagnose failures from either the UI or the server.
- Configure Python logging (timestamps, levels) and log job lifecycle events
(created / dispatched / done / failed) plus startup and dispatch warnings.
- Add a catch-all error handler so unhandled exceptions return a JSON error
with the exception type+message instead of an empty response.
- Remove the frontend submit bug that hand-set multipart Content-Type without
a boundary (and built an opts object it never used); post the FormData
directly so the browser sets the correct content type + boundary.
Replace the retired CLI usage docs with the current flow: install the
setuptools package (editable) on each host, then run cross-py-agent on the
workers and cross-py-controller on the UI host with a CROSS_PY_WORKERS list.
The nested cross_py_builder package only contains the worker agent. Rename
it to agent/ to match its role and distinguish it from the ctrl package and
the PyPI distribution name. Internal imports are relative, so build_agent.py
and zeroconf_server.py are unaffected except for the cosmetic APP_NAME banner.
The agent's /upload and /checkout_git were synchronous, blocking until a
build finished, so the controller could not relay live per-step progress.
Add ?async=1 support: the agent saves/clones the source, starts the build in
a background thread (holding build_lock), and returns 202 + {"id": ...}
immediately. The controller dispatches async, then polls /progress/<job_id>,
appending each step/log line to the job log and SSE stream, and treats the
build as done when progress clears (404) and the worker reports ready.
- Add ctrl/ package: Flask app (waitress), SQLite job store, background
scheduler that probes configured workers, dispatches via /upload and
/checkout_git, fetches artifacts, and serves a single-page UI
- Supporting API: /api/jobs (create/list/detail/cancel), /api/workers,
/api/capabilities, artifact download, and SSE log stream
- Workers keep the existing HTTP API; add /progress/<job_id> endpoint and
per-step build tracking for live status
- Retire agent_manager.py and the cross-py-builder CLI; controller UI is
the primary frontend
- Record design in DESIGN.md; add waitress dependency
Note: /progress is exposed on the worker but the synchronous /upload and
/checkout endpoints block until a build completes, so per-step worker
progress is not yet relayed live in the UI. Live streaming needs an
async-start worker endpoint as a follow-up.
- Add a non-blocking build lock so only one job runs at a time;
concurrent upload/checkout requests get a 409 response
- Add /healthz returning 200 when idle and 503 while a build is running
- Ignore .DS_Store artifacts
- Validate job IDs on /download and /delete; reject invalid identifiers
- Prevent zip-slip during /upload extraction via safe_extract
- Basename uploaded filenames in /update to block path traversal
- Route git-checkout jobs into pybuild-data so they can be downloaded/deleted
- Clean up job dirs on extract, checkout, and build failures
- Ignore Python bytecode and egg-info artifacts