DevLoop UI/UX overhaul — Phases 1–4#1
Merged
Conversation
…ersistent permissions Adds the foundation contract between the bash engine and any consumer (future TUI, monitors, scripts): - emit_event helper writes one NDJSON line per pipeline boundary to two sinks: project-wide .devloop/events.ndjson and per-session mirror. Wired into _session_init / _session_phase_start/end / _session_finish so every existing call site automatically gets events. - Approval gates: approve_plan / approve_diff wrap _approval_gate with a resolver chain — DEVLOOP_AUTO=1, pre-written decision file, gum confirm, /dev/tty read, no-tty reject. Emits approval.request / approval.decision events; persists decisions to <session>/approvals/<gate>.json for non-interactive (CI/TUI) flows. - cmd_run pauses at plan + diff gates between architect/worker/reviewer. --auto / -y flag bypasses; DEVLOOP_PLAN_GATE=off / DEVLOOP_DIFF_GATE=off disable individually. - Persistent permissions allowlist at .devloop/permissions.yaml. New tiers in devloop-permission.sh: 1.5 YAML deny (overrides built-in safe list) and 2.5 YAML allow (extends built-in safe list before escalation). Shell-glob patterns anchored at both ends. - docs/events.md documents the engine/TUI contract: every event kind, payload fields, ordering guarantees, bypass switches. - tests/smoke-phase1.sh extracts and unit-tests the helpers in isolation (26 assertions, all pass). Note: devloop.sh diff in this commit includes accumulated bash changes across phases 1-3 (helpers, dispatchers, cmd_run flow). Splitting them hunk-by-hunk would lose context cohesion. Phase 2 and 3 commits add the Go TUI module and the new run/chat views respectively. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
New cmd/devloop-tui module — a single static Go binary that observes
the devloop pipeline via the Phase 1 NDJSON event stream and presents
a live session dashboard. Distributed via `make tui-install` to
~/.devloop/bin/devloop-tui.
Stack: Charmbracelet's Bubble Tea + Bubbles + Lipgloss, plus fsnotify.
Package layout:
- internal/stream: typed Event/Session/PhaseState; ParseEvent;
fsnotify-based ndjson_tail.Tailer; session_scan.Scan. Handles
create-after-tail, truncation, and rotation. Race-clean.
- internal/components: pure pipeline_grid renderer ([architect][worker]
[reviewer][fix] status badges) and a Bubbles/list-backed task_picker
with fuzzy filter (/-key, j/k navigation).
- internal/views/dashboard: split-layout (picker left, active-session
detail right) with live updates from the tail stream. Test-mode
bypasses the goroutine via DashboardOptions{NoStream: true}.
- internal/app: thin router model (AppModel). Phase 2 has one view;
the router exists so Phase 3 can plug in chat/run views without
touching main.go.
- main.go: dispatches `dashboard` (default) with --version / --help.
- Makefile (subdir + root): `make tui` / `make tui-install` /
`make tui-dev` / `make tui-test`.
bash integration (in devloop.sh, committed in Phase 1):
- _find_tui helper, cmd_dashboard, dashboard|dash subcommand.
- main() auto-launches the TUI when invoked with no args + TTY +
binary present. Gated by DEVLOOP_DEFAULT_VIEW (default dashboard).
Falls back to cmd_help when any gate fails (CI, scripts, no binary).
go.work at repo root so gopls treats cmd/devloop-tui as in-workspace.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds two new TUI views and the bash machinery that connects them: - internal/views/run.go: single-session focus view. Subscribes to the NDJSON tail filtered by Session ID, renders the pipeline_grid live, and pops an approval modal when an approval.request event arrives. Keys a/y approve, r/n reject, e edit — writes the decision JSON to <session>/approvals/<gate>.json so the bash gate unblocks via the new polling step. approval.decision event is the authoritative dismiss signal (also handles gum/tty resolutions from elsewhere). - internal/views/chat.go: slash-command REPL. Commands /plan /run /fix /status /diff /skip /rollback /inbox /mode /help /quit; free text routes to /run. Background subprocess streaming via os/exec + a goroutine-fed line channel + a re-armed tea.Cmd chain (no tea.ExecProcess; the TUI stays interactive). Per-command IDs tag scrollback output so concurrent commands interleave cleanly. bash side (committed in Phase 1's devloop.sh diff): - _approval_read_decision helper deduplicates JSON parsing between step 2 (pre-written) and the new step 2.5 (TUI poll). - _approval_gate adds DEVLOOP_APPROVAL_WAIT polling so the TUI's decision-file write is honored without falling through to gum/tty. - cmd_chat and a TUI fast-path in cmd_status execute devloop-tui when TTY + binary; cmd_status falls back to text when DEVLOOP_STATUS_VIEW=text or output is piped. - cmd_run diff-gate edit-on-reject: rc=2 opens $EDITOR on a feedback file, drives cmd_fix with DEVLOOP_FIX_EXTRA_INSTRUCTIONS, re-captures the diff, and re-runs the gate up to DEVLOOP_DIFF_MAX_EDITS (default 2) rounds. App router (in Phase 2's app.go diff) was extended with ViewRun and ViewChat ViewIDs, RunTaskID / ChatMode options, and SwitchViewMsg for lazy view construction. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Rewrites cmd_configure / _setup_wizard with gum choose/input/confirm helpers (_cfg_choose, _cfg_input, _cfg_confirm) that detect gum at call time and fall back to plain read prompts when absent. Schema of devloop.config.sh is unchanged. Adds --non-interactive / --yes / -y flag for headless reruns. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds cmd_resume to resume interrupted pipelines from the last completed phase, with --list, --dry-run, and --help flags. The pure helper _compute_resume_from parses per-session events.ndjson to determine the next stage (worker → reviewer → fix-N loop). Emits a new session.resume event before re-entering the pipeline. Extends smoke-phase1.sh with 5 Phase 4C assertions and documents the new event kind in docs/events.md. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds _render_status_header that prints [arch ✓] [work ⠙] [review ·] [fix ·] above pipeline output during cmd_run and cmd_resume, re-rendered at every phase boundary via cursor-up + clear-line ANSI. No-op when stdout isn't a TTY or DEVLOOP_STATUS_HEADER=off, so piped/CI runs stay clean. New helpers: _render_status_header, _reset_status_header, and _read_session_states (reconstructs phase states from events.ndjson for resume). Wired into cmd_run (15 render sites) and cmd_resume (12 sites); DEVLOOP_STATUS_HEADER documented in cmd_help env-var listing. Extends smoke-phase1.sh from 31 to 42 assertions and switches the test's source-slice extraction to dynamic function-boundary detection so future function inserts can't break the slice ranges again. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR adds the structured event stream and a new opt-in Go Bubble Tea TUI companion for DevLoop, along with approval/permission UX improvements and smoke/unit coverage around those workflows.
Changes:
- Adds NDJSON event documentation, approval/permission configuration, and Phase 1 smoke coverage.
- Introduces
cmd/devloop-tuiwith dashboard, run/status, chat views, stream parsing/tailing, and UI components. - Adds Go workspace/module setup, Makefile targets, tests, and build artifact ignores.
Reviewed changes
Copilot reviewed 28 out of 30 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
tests/smoke-phase1.sh |
Adds Bash smoke coverage for event emission, gates, resume, and status header helpers. |
Makefile |
Adds top-level TUI build/test/install delegates. |
go.work |
Adds workspace entry for the TUI module. |
docs/events.md |
Documents the DevLoop NDJSON event contract. |
cmd/devloop-tui/Makefile |
Adds TUI build/install/dev/test targets. |
cmd/devloop-tui/main.go |
Adds TUI CLI entrypoint and subcommands. |
cmd/devloop-tui/internal/views/run.go |
Adds single-session run view and approval modal handling. |
cmd/devloop-tui/internal/views/run_test.go |
Adds run view unit tests. |
cmd/devloop-tui/internal/views/dashboard.go |
Adds session dashboard view and live event handling. |
cmd/devloop-tui/internal/views/dashboard_test.go |
Adds dashboard view unit tests. |
cmd/devloop-tui/internal/views/chat.go |
Adds slash-command chat REPL and subprocess dispatch. |
cmd/devloop-tui/internal/views/chat_test.go |
Adds chat REPL unit tests. |
cmd/devloop-tui/internal/stream/session_scan.go |
Adds session directory scanning and state parsing. |
cmd/devloop-tui/internal/stream/session_scan_test.go |
Adds session scanner tests. |
cmd/devloop-tui/internal/stream/ndjson_tail.go |
Adds fsnotify-based NDJSON tailer. |
cmd/devloop-tui/internal/stream/ndjson_tail_test.go |
Adds tailer behavior tests. |
cmd/devloop-tui/internal/stream/events.go |
Adds event schema parsing types. |
cmd/devloop-tui/internal/stream/events_test.go |
Adds event parser tests. |
cmd/devloop-tui/internal/components/task_picker.go |
Adds fuzzy task picker component. |
cmd/devloop-tui/internal/components/task_picker_test.go |
Adds picker component tests. |
cmd/devloop-tui/internal/components/pipeline_grid.go |
Adds pipeline phase grid renderer. |
cmd/devloop-tui/internal/components/pipeline_grid_test.go |
Adds pipeline grid tests. |
cmd/devloop-tui/internal/app/app.go |
Adds root TUI app/router model. |
cmd/devloop-tui/internal/app/app_test.go |
Adds app routing tests. |
cmd/devloop-tui/go.mod |
Defines TUI module dependencies. |
cmd/devloop-tui/go.sum |
Adds dependency checksums. |
.gitignore |
Ignores TUI build artifacts. |
.devloop/permissions.yaml |
Adds persistent permission allow/deny policy template. |
.claude/hooks/devloop-permission.sh |
Adds YAML-based permission allow/deny matching. |
Comments suppressed due to low confidence (1)
cmd/devloop-tui/internal/views/dashboard.go:109
- These assignments happen inside
Init, butInithas a value receiver and Bubble Tea does not store mutations made there. The firstwaitForEventcommand can receive one event, butm.eventsCh/m.errsChremain nil in subsequentUpdatecalls, so the listener is not re-armed after the first event andcancelis also lost. Move this state initialization into construction or return a startup message that stores the channels inUpdate.
eventsCh, errsCh, err := tailer.Run(ctx)
if err == nil {
m.eventsCh = eventsCh
m.errsCh = errsCh
cmds = append(cmds, waitForEvent(eventsCh), waitForErr(errsCh))
| } | ||
|
|
||
| if !m.opts.NoStream { | ||
| tailPath := filepath.Join(m.projectRoot, ".devloop", "pipeline.log") |
Comment on lines
+119
to
+123
| eventsCh, errsCh, err := tailer.Run(ctx) | ||
| if err == nil { | ||
| m.eventsCh = eventsCh | ||
| m.errsCh = errsCh | ||
| cmds = append(cmds, runWaitForEvent(eventsCh), runWaitForErr(errsCh)) |
Comment on lines
+687
to
+688
| _ = cmd.Wait() | ||
| close(lines) |
Comment on lines
+152
to
+153
| rc.cancel() | ||
| m = m.appendLine(lineInfo, fmt.Sprintf("[#%d] cancelled", m.lastCmdID), 0) |
Comment on lines
+541
to
+546
| eventsFile := filepath.Join(m.projectRoot, ".devloop", "events.ndjson") | ||
| f, ferr := os.OpenFile(eventsFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644) | ||
| if ferr == nil { | ||
| _, _ = fmt.Fprintf(f, "%s\n", b) | ||
| _ = f.Close() | ||
| } |
|
|
||
| set -uo pipefail | ||
|
|
||
| SCRIPT="${1:-/Volumes/SATECHI_WD_BLACK_2/dev/devloop/devloop.sh}" |
shaifulshabuj
added a commit
that referenced
this pull request
May 16, 2026
Ships the Phase 1–4 UI/UX overhaul merged in PR #1: - Go/Bubble Tea TUI (dashboard, chat, status) - structured event stream + plan/diff approval gates - devloop resume + permissions editor + gum wizard - always-visible pipeline status header See CHANGELOG.md for the full entry.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
End-to-end UI/UX overhaul of devloop, delivered as four phases:
.devloop/events.ndjson), plan + diff approval gates between pipeline stages, persistent permissions allowlist (.devloop/permissions.yaml).cmd/devloop-tui/(dashboard, status, chat views) consuming the event stream via fsnotify. Single static binary; opt-in./plan,/run,/diff,/rollback, …), live run view, diff edit-on-reject.cmd_configurewizard,cmd_permissionsYAML editor,devloop resume [TASK-ID](replays events.ndjson to skip completed phases), always-visible pipeline status header incmd_run/cmd_resume.What's new for users
devloop(no args) → live Bubble Tea dashboarddevloop chat→ slash REPL with mode toggle (ask/code/auto)devloop resume [TASK-ID]→ pick up an interrupted pipeline;--listand--dry-runflagsdevloop runpauses at plan + diff gates (skip with--auto/-y)devloop configureis now a gum wizard with--non-interactivedevloop permissionsfor editing the persistent allowlist[arch ✓] [work ⠙] [review ·] [fix ·]status header above pipeline outputArchitecture
devloop.sh); emits NDJSON events at every phase boundary..devloop/sessions/<TASK>/approvals/, which makes the same gate work in TUI, gum, /dev/tty, and CI (DEVLOOP_AUTO=1or pre-written decision file).docs/events.md.readprompt; no TTY → no-op headers.Test plan
bash tests/smoke-phase1.sh→ 42/42 passing (covers emit_event, approval gates, decision-file polling, plan/diff extraction,_compute_resume_from,_render_status_header)cd cmd/devloop-tui && go test ./... -race -count=1→ all packages green (stream, components, views, app)bash -n devloop.shcleanmake tuibuildscmd/devloop-tui/bin/devloop-tui./cmd/devloop-tui/bin/devloop-tui --version/--helpDEVLOOP_AUTO=1 devloop run "noop"end-to-end produces complete events.ndjson timelinedevloopopens live dashboard,devloop chatopens REPLdevloop resumecontinues from the right phaseBackwards compatibility
devloop.config.shschema unchanged.tmux-based live view (cmd_view) preserved as fallback.Commits (9)
3b0975fPhase 1 — structured event stream + approval gates + persistent permissions2a6a09fPhase 2 — Go/Bubble Tea companion TUI binary62b76b8Phase 3 — run view, chat REPL, diff edit-on-reject1eae69dPhase 4B — permissions yaml editorc2c0aa5Phase 4A — gum-driven setup wizarda82fd9ePhase 4C — devloop resume command3c7c3b9Merge worktree branch391e79aMerge Phase 4 (wizard + permissions + resume)fd6b5f4Phase 4D — always-visible pipeline status header🤖 Generated with Claude Code