Skip to content

DevLoop UI/UX overhaul — Phases 1–4#1

Merged
shaifulshabuj merged 9 commits into
mainfrom
phase4-implementation
May 16, 2026
Merged

DevLoop UI/UX overhaul — Phases 1–4#1
shaifulshabuj merged 9 commits into
mainfrom
phase4-implementation

Conversation

@shaifulshabuj
Copy link
Copy Markdown
Owner

Summary

End-to-end UI/UX overhaul of devloop, delivered as four phases:

  • Phase 1 — Structured NDJSON event stream (.devloop/events.ndjson), plan + diff approval gates between pipeline stages, persistent permissions allowlist (.devloop/permissions.yaml).
  • Phase 2 — Go/Bubble Tea companion TUI at cmd/devloop-tui/ (dashboard, status, chat views) consuming the event stream via fsnotify. Single static binary; opt-in.
  • Phase 3 — Slash-command chat REPL (/plan, /run, /diff, /rollback, …), live run view, diff edit-on-reject.
  • Phase 4 — gum-driven cmd_configure wizard, cmd_permissions YAML editor, devloop resume [TASK-ID] (replays events.ndjson to skip completed phases), always-visible pipeline status header in cmd_run / cmd_resume.

What's new for users

  • devloop (no args) → live Bubble Tea dashboard
  • devloop chat → slash REPL with mode toggle (ask/code/auto)
  • devloop resume [TASK-ID] → pick up an interrupted pipeline; --list and --dry-run flags
  • devloop run pauses at plan + diff gates (skip with --auto / -y)
  • devloop configure is now a gum wizard with --non-interactive
  • devloop permissions for editing the persistent allowlist
  • [arch ✓] [work ⠙] [review ·] [fix ·] status header above pipeline output

Architecture

  • Engine stays bash (devloop.sh); emits NDJSON events at every phase boundary.
  • TUI consumes via fsnotify — never parses bash output. Approval gates settle via JSON files in .devloop/sessions/<TASK>/approvals/, which makes the same gate work in TUI, gum, /dev/tty, and CI (DEVLOOP_AUTO=1 or pre-written decision file).
  • Full schema in docs/events.md.
  • Everything degrades gracefully: no Go binary → bash works; no gum → read prompt; 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.sh clean
  • make tui builds cmd/devloop-tui/bin/devloop-tui
  • ./cmd/devloop-tui/bin/devloop-tui --version / --help
  • Manual: DEVLOOP_AUTO=1 devloop run "noop" end-to-end produces complete events.ndjson timeline
  • Manual: devloop opens live dashboard, devloop chat opens REPL
  • Manual: interrupt a run, then devloop resume continues from the right phase

Backwards compatibility

  • devloop.config.sh schema unchanged.
  • TUI is opt-in; bash one-shot commands keep working for scripting/CI.
  • Existing tmux-based live view (cmd_view) preserved as fallback.

Commits (9)

  1. 3b0975f Phase 1 — structured event stream + approval gates + persistent permissions
  2. 2a6a09f Phase 2 — Go/Bubble Tea companion TUI binary
  3. 62b76b8 Phase 3 — run view, chat REPL, diff edit-on-reject
  4. 1eae69d Phase 4B — permissions yaml editor
  5. c2c0aa5 Phase 4A — gum-driven setup wizard
  6. a82fd9e Phase 4C — devloop resume command
  7. 3c7c3b9 Merge worktree branch
  8. 391e79a Merge Phase 4 (wizard + permissions + resume)
  9. fd6b5f4 Phase 4D — always-visible pipeline status header

🤖 Generated with Claude Code

shaifulshabuj and others added 9 commits May 16, 2026 09:56
…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>
Copilot AI review requested due to automatic review settings May 16, 2026 13:27
@shaifulshabuj shaifulshabuj merged commit cd8e46a into main May 16, 2026
4 checks passed
@shaifulshabuj shaifulshabuj deleted the phase4-implementation branch May 16, 2026 13:31
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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-tui with 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, but Init has a value receiver and Bubble Tea does not store mutations made there. The first waitForEvent command can receive one event, but m.eventsCh/m.errsCh remain nil in subsequent Update calls, so the listener is not re-armed after the first event and cancel is also lost. Move this state initialization into construction or return a startup message that stores the channels in Update.
		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()
}
Comment thread tests/smoke-phase1.sh

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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants