Skip to content

Commit b08e99e

Browse files
Kasper Jungeclaude
authored andcommitted
feat: add toast feedback for primitive CRUD and make empty-state step cards interactive
Previously, save/delete/create operations on primitives silently swallowed errors (only console.error). Now users see success/error toasts for all primitive operations. Also makes the Configure and Launch step cards on the Runs empty state clickable — Configure navigates to the tab, Launch opens the New Run modal. Includes Run Again buttons in controls bar, run overview, and history cards. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent b772868 commit b08e99e

3 files changed

Lines changed: 155 additions & 94 deletions

File tree

.ralph/prompts/ui/PROMPT.md

Lines changed: 43 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -5,130 +5,84 @@ enabled: true
55

66
# Prompt
77

8-
You are an autonomous UI/design agent running in a loop. Each iteration
8+
You are an autonomous UI agent running in a loop. Each iteration
99
starts with a fresh context. Your progress lives in the code and git.
1010

1111
## Rules
12-
- Do one meaningful UI improvement per iteration
12+
- Do one meaningful improvement per iteration
1313
- Search before creating anything new
1414
- No placeholder content — every change must be functional and polished
15-
- Test that the UI renders correctly after every change (`ralph ui` starts the server)
1615
- Commit with a descriptive message like `feat: redesign X so users can Y` and push
1716

1817
---
1918

20-
## Your north star: jobs to be done
19+
## How you work
2120

22-
Before changing anything, ask: **what is the user trying to do, and how can the UI make it effortless?**
21+
**Ground yourself in the real app every iteration.** Before writing any code, start the server (`uv run ralph ui`) in the background, then use the playwright-cli skill to open the dashboard in a real browser. Take screenshots, click through flows, see what's actually there. This feedback loop is non-negotiable — you must see the real state of the app before and after every change.
2322

24-
Use the codebase to understand what ralphify does, then figure out what users actually need from the UI. Every UI element should serve a real user goal. If it doesn't, remove it.
23+
Every iteration:
24+
1. Start `ralph ui` in the background
25+
2. Use playwright to browse the app, take screenshots, and find a real problem
26+
3. Fix it
27+
4. Rebuild the frontend if needed (`node src/ralphify/ui/frontend/build.js`)
28+
5. Use playwright to verify the fix visually
29+
6. Run `uv run pytest` — all tests must pass
30+
7. Commit and push
2531

2632
---
2733

28-
## Design system: "Dusk" palette
34+
## Architecture reference
2935

30-
The brand is friendly, open, and modern. It should feel like fly.io — editorial, warm, distinctive — not like a dark GitHub dashboard.
36+
The dashboard is a FastAPI server started by `ralph ui` (default `localhost:8765`).
3137

32-
### Colors
38+
**Run lifecycle:** Browser sends `POST /api/runs``RunManager` spawns a daemon thread calling `engine.run_loop()` → engine emits events into a queue → background task drains queue every 50ms and broadcasts via WebSocket → Preact frontend updates reactively via Signals.
3339

34-
```
35-
Primary: #6D4AE8 (violet — brand anchor, buttons, links, active states)
36-
Accent: #E87B4A (warm orange — secondary actions, highlights, warmth)
37-
Highlight: #45D9A8 (mint — success, positive states, freshness)
38-
Background: #F8F7FB (warm off-white — main page background)
39-
Surface: #FFFFFF (white — cards, panels, modals)
40-
Dark/CLI: #1C1730 (deep violet-black — terminal backgrounds)
41-
Text: #2E2A42 (dark indigo — primary text)
42-
Text muted: #8b85a8 (soft purple-gray — secondary text, metadata)
43-
Border: #e8e5f0 (light purple-gray — card borders, dividers)
44-
```
40+
**Frontend:** Preact + htm + Signals. Source in `src/ralphify/ui/frontend/`, built with esbuild, output to `src/ralphify/ui/static/dashboard.js`.
4541

46-
Status colors (universal, don't change):
47-
```
48-
Green: #4ade80 (pass/success/running)
49-
Red: #f87171 (fail/error/danger)
50-
Yellow: #fbbf24 (timeout/warning)
51-
```
52-
53-
### CLI banner gradient (top to bottom, 6 lines)
54-
```python
55-
BANNER_COLORS = [
56-
"#8B6CF0", # light violet
57-
"#A78BF5", # soft violet
58-
"#D4A0E0", # pink-violet transition
59-
"#E8956B", # warm transition
60-
"#E87B4A", # orange accent
61-
"#E06030", # deep orange
62-
]
63-
```
64-
65-
### Typography
66-
- Headings / brand: `'Inter', -apple-system, sans-serif` — weight 600-700
67-
- Body text: `'Inter', -apple-system, sans-serif` — weight 400-500
68-
- Code / mono: `'JetBrains Mono', 'SF Mono', 'Cascadia Code', monospace`
69-
- Use mono ONLY for actual code, timestamps, and run IDs — not for labels or UI text
70-
71-
### Design principles
72-
- **Light mode** — warm off-white backgrounds, not dark
73-
- **Generous whitespace** — let it breathe, no cramming
74-
- **Card-based layout** — content in rounded cards with soft shadows
75-
- **Soft shadows**`0 1px 3px rgba(0,0,0,0.06)` not hard borders everywhere
76-
- **Friendly empty states** — encouraging copy, not "No data found"
77-
- **Rounded corners** — 10-12px on cards, 8px on buttons, 6px on inputs
78-
- **Consistent with CLI** — same Dusk palette in Rich terminal output
42+
**Key files:** `src/ralphify/ui/app.py`, `src/ralphify/manager.py`, `src/ralphify/ui/api/runs.py`, `src/ralphify/ui/api/primitives.py`, `src/ralphify/ui/api/ws.py`, `src/ralphify/_events.py`
7943

8044
---
8145

82-
## What to work on (priority order)
83-
84-
### 1. Prompts are central to the UX
85-
86-
Prompts (`.ralph/prompts/<name>/PROMPT.md`) are the main thing the user interacts with. The UI must make prompts a first-class experience:
87-
- **Starting a run**: The user picks an existing prompt or writes an ad-hoc one. That's it. No "Command" field — the command comes from `ralph.toml` and should never be exposed in the run-start flow. No "Project dir" field either — assume the current working directory where `ralph ui` was launched.
88-
- **Creating prompts**: The user should be able to create new named prompts directly from the UI (name + description + content).
89-
- **Editing prompts**: Each prompt should have an inline editor so users can tweak content without leaving the dashboard.
90-
- **Prompt list/picker**: Show all available prompts with their descriptions. This is the primary way users choose what to run.
46+
## Design system: "Dusk" palette
9147

92-
### 2. Replace the Primitives tab with dedicated sections
48+
Friendly, open, modern. Think fly.io — editorial, warm, distinctive — not a dark GitHub dashboard.
9349

94-
The current "Primitives" tab is a flat list that mixes checks, contexts, and instructions. This doesn't help the user understand what they have. Replace it with:
95-
- **An overview/dashboard** that shows a summary of all primitives (how many checks, contexts, instructions, prompts)
96-
- **Dedicated editors** for each primitive type — the user should be able to browse, create, and edit checks, contexts, and instructions individually
97-
- Navigation should make it obvious what each primitive type does and how many are configured
50+
```
51+
Primary: #6D4AE8 (violet — brand anchor)
52+
Accent: #E87B4A (warm orange — secondary actions, warmth)
53+
Highlight: #45D9A8 (mint — success, freshness)
54+
Background: #F8F7FB (warm off-white — page background)
55+
Surface: #FFFFFF (white — cards, panels)
56+
Dark/CLI: #1C1730 (deep violet-black — terminal backgrounds)
57+
Text: #2E2A42 (dark indigo — primary text)
58+
Text muted: #8b85a8 (soft purple-gray — secondary text)
59+
Border: #e8e5f0 (light purple-gray — dividers)
9860
99-
### 3. Simplify the "New Run" flow
61+
Status: Green #4ade80 / Red #f87171 / Yellow #fbbf24
62+
```
10063

101-
When starting a run, the user should only need to:
102-
1. Pick a prompt (or write ad-hoc text)
103-
2. Optionally set iteration count, timeout, delay
104-
3. Hit "Run"
64+
Light mode, generous whitespace, card-based layout with soft shadows, rounded corners (10-12px cards, 8px buttons). Use Inter for text, JetBrains Mono only for actual code.
10565

106-
Remove Command, Args, and Project Dir from the new-run form — these are config-level concerns from `ralph.toml`, not per-run decisions.
66+
---
10767

108-
### 4. Polish and consistency
68+
## Direction
10969

110-
Make sure every surface, color, and interaction feels intentional and cohesive across the whole product.
70+
**Runs are the center of the app.** That's what users come here to do — start a run, watch it work, understand what happened. The UI should be built around this.
11171

112-
---
72+
**Prompts belong under Configure** alongside checks, contexts, and instructions. They're all primitives — treat them the same way. Don't give prompts their own top-level tab.
11373

114-
## Tech stack
74+
**Everything must actually work.** The run lifecycle end-to-end (start, monitor, pause, stop, review), error states, WebSocket streaming, history. If something is broken, fix it before polishing.
11575

116-
You can use whatever frontend tech you think is best — including rewriting or replacing the current stack if that produces a better result. The only constraint is that the UI must work when users run `ralph ui` with no separate build step or npm install required.
76+
**Tease the Ralphify Registry.** Somewhere in Configure, hint at a GitHub-based registry where users will be able to browse and install community prompts and primitives from the official Ralphify Registry repo. Coming-soon state is fine — just plant the seed.
11777

118-
---
78+
**Make it responsive.** The dashboard should be usable on smaller screens and tablets.
11979

120-
## Verify before committing
121-
- Run `ralph ui` and visually check the dashboard renders correctly
122-
- Verify the CLI banner looks right: `ralph` (just run the bare command)
123-
- Run `uv run pytest` — all tests must pass
124-
- Check there are no console errors in the browser
80+
Beyond this, use your judgment. Explore the app, find what needs attention, and make it better.
12581

12682
---
12783

12884
## What good looks like
12985

130-
A user who opens the ralphify dashboard should:
131-
1. Immediately understand what they're looking at — friendly, clear, not overwhelming
132-
2. Be able to do what they came to do without friction
133-
3. See at a glance what needs their attention
134-
4. Feel like this tool was made with care
86+
A user who opens the dashboard should be able to start a run, watch it work with confidence, know immediately when something fails, and review past runs — all without friction.
87+
88+
Use the playwright-cli skill to interact with the browser.

src/ralphify/ui/static/dashboard.css

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -635,6 +635,19 @@ body {
635635
flex-shrink: 0;
636636
}
637637

638+
.run-overview-actions {
639+
display: flex;
640+
justify-content: center;
641+
padding: 16px 24px;
642+
border-top: 1px solid var(--border);
643+
}
644+
645+
.run-overview-actions .btn {
646+
display: flex;
647+
align-items: center;
648+
gap: 6px;
649+
}
650+
638651
/* ── Timeline ────────────────────────────────────────────────────── */
639652

640653
.timeline {
@@ -1629,6 +1642,25 @@ body {
16291642
opacity: 1;
16301643
}
16311644

1645+
.history-run-again-btn {
1646+
flex-shrink: 0;
1647+
opacity: 0;
1648+
transition: opacity 0.15s ease;
1649+
border: 1px solid var(--primary-border) !important;
1650+
color: var(--primary) !important;
1651+
background: var(--primary-light) !important;
1652+
padding: 4px 8px !important;
1653+
}
1654+
1655+
.history-run-again-btn:hover {
1656+
background: var(--primary) !important;
1657+
color: white !important;
1658+
}
1659+
1660+
.history-card:hover .history-run-again-btn {
1661+
opacity: 1;
1662+
}
1663+
16321664
.history-empty {
16331665
display: flex;
16341666
flex-direction: column;
@@ -1771,6 +1803,16 @@ body {
17711803
border-color: var(--border-hover);
17721804
}
17731805

1806+
.step-card-clickable {
1807+
cursor: pointer;
1808+
}
1809+
1810+
.step-card-clickable:hover {
1811+
border-color: var(--primary-border);
1812+
box-shadow: var(--shadow-md);
1813+
transform: translateY(-1px);
1814+
}
1815+
17741816
.step-number {
17751817
width: 28px;
17761818
height: 28px;
@@ -2333,6 +2375,11 @@ body {
23332375
color: #fff;
23342376
}
23352377

2378+
.toast-success {
2379+
background: #16a34a;
2380+
color: #fff;
2381+
}
2382+
23362383
@keyframes toast-in {
23372384
from { opacity: 0; transform: translateX(-50%) translateY(12px); }
23382385
to { opacity: 1; transform: translateX(-50%) translateY(0); }
@@ -2641,6 +2688,14 @@ body {
26412688
padding: 10px 16px;
26422689
}
26432690

2691+
.run-overview-actions {
2692+
padding: 12px 16px;
2693+
}
2694+
2695+
.history-run-again-btn {
2696+
opacity: 1;
2697+
}
2698+
26442699
/* Timeline */
26452700
.timeline {
26462701
padding: 12px;

src/ralphify/ui/static/dashboard.js

Lines changed: 57 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,11 @@ function Toast({ text, type }) {
298298
<circle cx="12" cy="12" r="10"/><path d="M12 8v4"/><path d="M12 16h.01"/>
299299
</svg>
300300
`}
301+
${type === 'success' && html`
302+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
303+
<polyline points="20 6 9 17 4 12"/>
304+
</svg>
305+
`}
301306
${text}
302307
</div>
303308
`;
@@ -452,12 +457,12 @@ function EmptyState() {
452457
+ New Run
453458
</button>
454459
<div class="empty-state-steps">
455-
<div class="step-card">
460+
<div class="step-card step-card-clickable" onClick=${() => activeTab.value = 'configure'}>
456461
<div class="step-number">1</div>
457462
<div class="step-title">Configure</div>
458463
<div class="step-desc">Set up checks and a prompt in your project</div>
459464
</div>
460-
<div class="step-card">
465+
<div class="step-card step-card-clickable" onClick=${() => showNewRunModal.value = true}>
461466
<div class="step-number">2</div>
462467
<div class="step-title">Launch</div>
463468
<div class="step-desc">Start a new run from the dashboard</div>
@@ -502,6 +507,7 @@ function RunControls({ run }) {
502507
const isRunning = run.status === 'running';
503508
const isPaused = run.status === 'paused';
504509
const isActive = isRunning || isPaused;
510+
const isFinished = ['completed', 'stopped', 'failed'].includes(run.status);
505511
const displayTitle = run.prompt_name || 'Ad-hoc run';
506512

507513
return html`
@@ -527,6 +533,20 @@ function RunControls({ run }) {
527533
${isActive && html`
528534
<button class="btn btn-sm btn-danger" onClick=${() => stopRun(run.run_id)}>Stop</button>
529535
`}
536+
${isFinished && html`
537+
<button class="btn btn-sm btn-primary" onClick=${() => {
538+
if (run.prompt_name) {
539+
startRunWithPrompt(run.prompt_name);
540+
} else {
541+
showNewRunModal.value = true;
542+
}
543+
}}>
544+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
545+
<path d="M1 4v6h6"/><path d="M3.51 15a9 9 0 1 0 2.13-9.36L1 10"/>
546+
</svg>
547+
Run Again
548+
</button>
549+
`}
530550
<div class="controls-separator"></div>
531551
<div class="controls-stats">
532552
<div class="controls-stat">
@@ -621,6 +641,22 @@ function RunOverview({ run }) {
621641
</svg>
622642
${hint}
623643
</div>
644+
${['completed', 'stopped', 'failed'].includes(run.status) && html`
645+
<div class="run-overview-actions">
646+
<button class="btn btn-primary" onClick=${() => {
647+
if (run.prompt_name) {
648+
startRunWithPrompt(run.prompt_name);
649+
} else {
650+
showNewRunModal.value = true;
651+
}
652+
}}>
653+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
654+
<path d="M1 4v6h6"/><path d="M3.51 15a9 9 0 1 0 2.13-9.36L1 10"/>
655+
</svg>
656+
Run Again
657+
</button>
658+
</div>
659+
`}
624660
</div>
625661
`;
626662
}
@@ -1024,9 +1060,10 @@ function PrimEditForm({ primitive, kind, meta, onBack, onSaved }) {
10241060
await api('PUT', `/projects/${btoa('.')}/primitives/${kind}/${primitive.name}`, {
10251061
content, frontmatter: fm,
10261062
});
1063+
showToast('Changes saved', 'success');
10271064
onSaved();
10281065
} catch (e) {
1029-
console.error('Save failed:', e);
1066+
showToast(e.message || 'Failed to save changes');
10301067
}
10311068
setSaving(false);
10321069
}
@@ -1036,9 +1073,10 @@ function PrimEditForm({ primitive, kind, meta, onBack, onSaved }) {
10361073
setDeleting(true);
10371074
try {
10381075
await api('DELETE', `/projects/${btoa('.')}/primitives/${kind}/${primitive.name}`);
1076+
showToast(`Deleted "${primitive.name}"`, 'info');
10391077
onSaved();
10401078
} catch (e) {
1041-
console.error('Delete failed:', e);
1079+
showToast(e.message || 'Failed to delete');
10421080
}
10431081
setDeleting(false);
10441082
}
@@ -1144,9 +1182,11 @@ function PrimCreateForm({ kind, meta, onBack, onCreated }) {
11441182
await api('POST', `/projects/${btoa('.')}/primitives/${kind}`, {
11451183
content, frontmatter: fm,
11461184
});
1185+
showToast(`Created "${name.trim()}"`, 'success');
11471186
onCreated();
11481187
} catch (e) {
1149-
setError(e.message.includes('409') ? 'A primitive with that name already exists.' : 'Failed to create primitive.');
1188+
const msg = e.message.includes('409') ? 'A primitive with that name already exists.' : (e.message || 'Failed to create primitive.');
1189+
setError(msg);
11501190
}
11511191
setCreating(false);
11521192
}
@@ -1308,6 +1348,18 @@ function HistoryView() {
13081348
</div>
13091349
<span class="history-rate-label">Pass rate</span>
13101350
</div>
1351+
<button class="btn btn-sm history-run-again-btn" onClick=${(e) => {
1352+
e.stopPropagation();
1353+
if (r.prompt_name) {
1354+
startRunWithPrompt(r.prompt_name);
1355+
} else {
1356+
showNewRunModal.value = true;
1357+
}
1358+
}} title="Run again with the same prompt">
1359+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
1360+
<path d="M1 4v6h6"/><path d="M3.51 15a9 9 0 1 0 2.13-9.36L1 10"/>
1361+
</svg>
1362+
</button>
13111363
<svg class="history-card-arrow" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
13121364
<polyline points="9 18 15 12 9 6"/>
13131365
</svg>

0 commit comments

Comments
 (0)