* feat(offline): persisted query cache + unreachable banner (web + mobile)
- Web: PersistQueryClientProvider on localStorage (24h maxAge); mobile:
AsyncStorage persister (7d). Both exclude high-churn keys (logs,
notifications, metrics) — stale tails are worse than none.
- networkMode 'offlineFirst' so cached data serves while the refetch
attempt fails.
- Web: ConnectivityBanner in PlatformShell pings public /health every
15s + listens to online/offline events — slim "last-known data" bar
so persisted state can't masquerade as live.
- Mobile: same banner in the tabs layout — /health ping plus
expo-network's local signal distinguishes "no connection" from
"instance down".
Generated with [Devin](https://devin.ai)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
* feat(mobile): cron jobs screen — schedules, run-now, execution history
- /service/cron lists the service's jobs with schedule, timezone,
last/next run, and last status; "Run now" triggers via the existing
POST /cron-jobs/:id/trigger and "History" expands recent executions
with status + output/error.
- Manage row on the service detail + Stack registration.
Verified live: created */5 job on a running service, triggered it —
execution recorded with status success and captured stdout.
Generated with [Devin](https://devin.ai)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
---------
Co-authored-by: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
GET /services/:id/terminal upgrades to a WebSocket attached to a
`docker exec` PTY in the service's running replica. JSON frames both
ways: stdin/resize out, stdout/exit/error in. Shell probe tries each
candidate (bash→sh) since `command -v` needs a shell to run; distroless
images get a clean "no shell" error instead of an OCI failure dump.
- docker.Client gains ExecAttach + ExecResize wrappers
- Frontend: xterm.js replaces the one-off console section — live PTY,
fit-addon resizes forwarded over the socket
- CLI: `containr shell <id>` bridges the local TTY (raw mode, SIGWINCH
resize forwarding); `containr exec` keeps the one-off command path
- Access mirrors service logs: project manage scope required
Generated with [Devin](https://devin.ai)
Co-authored-by: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Phase 0 cleanup verified end-to-end:
- Delete vendored APwhy runtime (cmd/apwhy, internal/apwhy) and the
unwired copy (internal/api/server.go + static bundle + cloned
internal/{storage,analytics,auth,rbac}) — ~7.5k lines of dead code.
- Keep Containr's native internal/gateway; move APwhy management
routes under /api/v1/gateway/* so they stop colliding with the
native /services paths. Fix handlers against the real schema:
UUID ids via RETURNING, integer enabled flags, plan validation,
service validate endpoint, unique-violation mapping.
- Port agents.go off GORM onto sqlc (sqlc/queries/agents.sql);
drop gorm dependencies; fix sqlc schema drift (services.environment_id).
- Replace the SELECT 1 goose baseline with the real consolidated
schema (52 tables) minus APwhy's parallel auth/RBAC objects;
idempotent via IF NOT EXISTS / OR REPLACE / guarded DO blocks so it
replays cleanly over legacy installs. Add a drop migration for the
dead objects and delete the legacy filename-tracked migrations/
loader, directory, and Dockerfile copy. Verified via cmd/migrate:
fresh install 53 tables, legacy upgrade clean to 20260917000000.
- Canvas: add invisible React Flow handles to ServiceNode so inferred
auto-connections stop emitting warning #008.
- Remove reactflow dep (superseded by @xyflow/react), test harness
cmds, and legacy 0001_init.sql backups.
Authored By: TDvorak <[email protected]>