Phase 6 status. This document was captured against the iced-based
crates/ripley-app(Phase 1-5) on 2026-05-22. It is the priority-ordered input list for Phase 6 M27 (view migration) — the issues drove the stack re-evaluation that produced STACK_DECISION.md, and the priority order survives the iced→Tauri rewrite even though the underlying tech changes. Resolution path: addressed in the Tauri + React + shadcn/Base UI + Tailwind v4 rewrite, not by patching iced.Items (1)-(3) (no tray, no native menu bar, no AX tree) are dissolved by the stack switch itself — they were structural ceilings in iced that Tauri does not share. Items (4)+ (design drift, theming, severity badges, etc.) become concrete M27 tasks: implement once in shadcn/Tailwind tokens, ship across all three OSes simultaneously.
Every numbered finding below has been routed to one of three outcomes:
| Outcome | Meaning |
|---|---|
| Resolved (Tauri) | The Tauri/React migration shipped the fix as a side effect of building the corresponding component. The iced call-sites identified in the finding are retired with crates/ripley-app in M28. |
| Deferred (notes) | Specific judgment moved to apps/desktop/DESIGN_NOTES.md because the Tauri implementation diverges from the iced-era recommendation. |
| Obsolete (stack) | Finding was specific to iced widgets / theme.rs and disappears entirely with the stack switch — nothing to port. |
| ID | Outcome | Where it lives now |
|---|---|---|
| CC-1 sidebar purple buttons | Obsolete (stack) | iced button::Style ceiling; Tauri uses components/ui/button.tsx over Tailwind tokens. Sidebar nav lands with the shell in M27.8 follow-up; no equivalent regression possible. |
| CC-2 empty-state CTA purple | Resolved (Tauri) | Button variants in apps/desktop/src/components/ui/button.tsx consume bg-primary/bg-accent-hover from styles/theme.css. |
| CC-3 sidebar header | Deferred (notes) | M27 routes ship without a sidebar shell; tray-driven navigation is the M28 model. Logo + version footer captured for the eventual shell pass. |
| CC-4 sidebar badges + footer | Deferred (notes) | Same as CC-3; not blocking M27 close-out. |
| CC-5 top bar / view title | Resolved (Tauri) | Per-view <section aria-label> and route titles live inside each route component; no shared chrome is owed. |
| CC-6 severity badges | Resolved (Tauri) | components/ripley/SeverityBadge.tsx is the single primitive used by Alerts, Guard log, Monitor, Deep scan. |
| CC-7 severity background tokens | Resolved (Tauri) | --color-severity-*-bg in apps/desktop/src/styles/theme.css. |
| CC-8 inconsistent empty-state | Resolved (Tauri) | components/ripley/EmptyState.tsx is the only empty-state pattern in the Tauri routes. |
| CC-9 surface-hover / surface-active | Resolved (Tauri) | bg-surface-hover Tailwind utility powered by --color-surface-hover; no #[allow(dead_code)] equivalent survives in CSS. |
| CC-10 monospace font | Resolved (Tauri) | Tailwind font-mono applied at every pkg@version / path / fix-command site in M27.2–M27.6 routes. |
| CC-11 numeric counts size | Resolved (Tauri) | KeyValueGrid formats counts with DESIGN.md typography tokens. |
| CC-12 settings form | Resolved (Tauri) | M27.6 — full editable form over read_settings/write_settings with atomic config writes. |
CC-13 sidebar background bg vs SURFACE |
Obsolete (stack) | iced theme.rs palette mismatch dissolves with the rewrite. |
| ID | Outcome | Notes |
|---|---|---|
| V1.1–V1.5 alerts row | Resolved (Tauri) | M27.2 — routes/Alerts.tsx ships severity badge + monospace pkg@version + advisory id + project_path. |
| V1.6 alerts empty CTA | Deferred (notes) | Empty state is shared (CC-8); per-view CTA copy/CTA wiring tracked in DESIGN_NOTES.md M27.7 entry. |
| V2.1–V2.3 guard log table | Resolved (Tauri) | M27.3 — routes/GuardLog.tsx is a columnar DataTable with severity badge + monospace script. |
| V2.4 guard log empty CTA | Deferred (notes) | Same shared-empty-state caveat as V1.6. |
| V3.1–V3.8 deep scan | Resolved (Tauri) | M27.4 — routes/DeepScan.tsx uses KeyValueGrid + SeverityBadge; dead-man-switch warning surfaces via a labeled row. Section chevrons (V3.3) intentionally not implemented — the Tauri DOM does not need them; tracked in DESIGN_NOTES.md. |
| V4.1–V4.4 monitor | Resolved (Tauri) | M27.4 — routes/Monitor.tsx splits severity badge + monospace process + secondary timestamp via TimestampCell. |
| V5.1–V5.5 audit | Resolved (Tauri) | M27.4 — routes/Audit.tsx standardised empty state, TrafficLightDot for category status, monospace fix-command blocks. Category score cards (V5.3) consciously omitted; tracked in DESIGN_NOTES.md. |
| V6.1–V6.5 posture | Resolved (Tauri) | M27.4 — routes/Posture.tsx mirrors Audit pattern; detected PMs render as monospace pill row. |
| V7.1–V7.4 settings | Resolved (Tauri) | M27.6 — routes/Settings.tsx ships section-grouped form (General / Monitor / Guard / Posture) with radio group + checkboxes + numeric inputs. |
| # | Component | Outcome |
|---|---|---|
| 1 | Severity badge | Resolved — SeverityBadge. |
| 2 | Severity dot | Resolved — TrafficLightDot. |
| 3 | Card | Resolved — Tailwind rounded-lg border border-border-subtle bg-surface p-4 is the convention; not abstracted further on purpose (DESIGN_NOTES.md). |
| 4 | Summary card | Resolved — KeyValueGrid covers the numeric-summary case in Deep scan; severity-tinted variants are deferred. |
| 5 | Category score card | Deferred (notes) — Audit/Posture render category headers with TrafficLightDot instead. |
| 6 | Traffic-light bar | Deferred (notes) — TrafficLightDot is the only traffic-light primitive shipped; bar variant tracked. |
| 7 | Code / command block | Resolved — <code> with bg-bg px-2 py-0.5 font-mono is the convention across routes. |
| 8 | Input / dropdown / toggle | Resolved (Tauri) — M27.6 settings form uses native form elements styled via DESIGN tokens; Base UI dropdown lands when a route needs it. |
| 9 | Top bar | Deferred (notes) — see CC-5. |
| 10 | Sidebar footer | Deferred (notes) — see CC-3/CC-4. |
| 11 | Alert row | Resolved — routes/Alerts.tsx. |
| 12 | Notification overlay | Deferred (notes) — tray + OS notifications are the M28 path; in-app overlay is not on the Phase 6 critical path. |
The deferred items above are individually captured in
apps/desktop/DESIGN_NOTES.md (M27.7 entry) with the rationale for why the
Tauri shell intentionally diverges or postpones each one. The iced
findings below are retained verbatim as the source-of-truth record of
what drove the stack switch and the M27 component set; they are not
re-validated against the Tauri code.
Audit of the Ripley dashboard (crates/ripley-app) against DESIGN.md
and UI.md, captured 2026-05-22 by driving the running app with Peekaboo
and inspecting source.
Captured screenshots: /tmp/ripley-view-1-alerts.png … /tmp/ripley-view-7-settings.png.
Verification harness: peekaboo hotkey --keys cmd,<N> --app ripley-app
switches views 1–7; peekaboo image --window-id <wid> snapshots. The Cmd+1..7
shortcuts were added in crates/ripley-app/src/app.rs for design iteration. In
the Tauri rewrite the Peekaboo loop continues to work against the DOM
accessibility tree exposed by WKWebView — same workflow, richer signal.
The dashboard renders, but visually it is roughly 15 % of what DESIGN.md
specifies. The colors in theme.rs match the spec, but most of them are
dead code; button(…) widgets inherit iced's default purple palette
(visible on every sidebar nav item and every empty-state CTA), Settings
is a println!-style dump of config.toml, severity badges/dots/pills
don't exist, summary cards are present in Deep Scan but use the wrong
backgrounds, and the empty-state pattern is inconsistent across views.
There is also a structural gap: the implemented widgets don't reuse a
shared theming layer, so every view re-declares colors/sizes inline. Any
fix that touches the look-and-feel needs to touch ~7 files unless we add a
components module first.
Severity legend used below:
- P0 — Breaks the design identity (e.g. wrong brand color across the app).
- P1 — Visible inconsistency or missing core component from DESIGN.md.
- P2 — Polish, empty-state coherence, hierarchy.
- P3 — Cleanup / nice-to-have.
theme.rs is a flat list of color constants. It does not export:
- Spacing constants (DESIGN.md spec
xs=4…3xl=48) - Typography size constants (DESIGN.md
display=28,heading=15,body=13, etc.) - Radius constants (
sm=4,md=6,lg=8,full=9999) - Severity background variants (
severity-*-bg, 20 % alpha) - Accent variants (
accent-muted,accent-active) - Surface variants used in interactive states (
surface-hover,surface-active) are present but marked#[allow(dead_code)] - Style factories — there is no
nav_button_style(),primary_button_style(),card_style(),severity_badge(), etc.
Result: every call site uses raw button(text(label)) which inherits iced's
default Theme::Dark palette (purple). The only widgets that pick up the
real palette are those built from container(...) with an explicit
.style(...) closure.
This is fixable with one PR that introduces theme/components.rs
(or theme/buttons.rs + theme/cards.rs + theme/badges.rs) and migrates
the existing call sites.
Where: crates/ripley-app/src/app.rs:289-300 (nav_button).
Problem: button(text(label).color(...)) only styles the label text.
The button background, border, hover, and pressed states fall back to
iced::widget::button::Style::default() for Theme::Dark, which is the
violet #7C8AC7. All seven sidebar items render as filled purple
rectangles in every screenshot.
DESIGN.md spec (Sidebar, components.sidebar-item):
- Default: transparent background,
text-secondarytext,bodyfont (13 px), paddingsmvertical +mdhorizontal, radiusmd. - Active:
accent-mutedbackground,accenttext, 2 px left border inaccent. - Hover:
surface-hoverbackground.
Fix: Apply a button::Style closure that returns:
button::Style {
background: if active { Some(ACCENT_MUTED.into()) } else { None },
text_color: if active { ACCENT } else { TEXT_SECONDARY },
border: Border { /* 2px left in accent when active */ },
..
}Requires ACCENT_MUTED to be added to theme.rs first (see RC).
Where:
crates/ripley-app/src/views/deep_scan.rs:74(“Run Deep Scan”)crates/ripley-app/src/views/monitor.rs:17(“Enable Monitor”)crates/ripley-app/src/views/deep_scan.rs:330,335(“Export Report”, “Copy Rotation Checklist”)
Problem: Same as CC-1 — button(text(...).color(Colors::ACCENT))
only colors the label; the bg is iced violet.
DESIGN.md spec (button-primary): background accent (#58a6ff),
text text-on-accent (white), radius md, padding sm×lg, weight 600.
Fix: Introduce theme::primary_button_style() and
theme::secondary_button_style() and replace all button(text(...))
constructions.
Where: crates/ripley-app/src/app.rs:237.
Problem: text("Ripley").size(15) followed by Rule::horizontal(1)
sits at the top of the sidebar with no padding distinct from the items,
and no logo glyph beside it. UI.md’s wireframe shows a LOGO block.
Fix: wrap header in a row![logo, text("Ripley")…] with deliberate
top/bottom padding, drop the iced Rule and use a container with
bottom-border-only styling so the separator color matches BORDER.
Where: crates/ripley-app/src/app.rs:235-265.
DESIGN.md / UI.md spec:
- Each nav item can show a right-aligned
badgecount (e.g.Alerts (8)inseverity-critical). - Bottom of sidebar: version (
v0.1.0), connection dot (green when daemon connected, muted otherwise).
Currently rendered: plain text labels, no badges, no bottom block.
Fix: add a nav_item(label, badge: Option<(usize, Color)>, active)
factory and a sidebar_footer(version, daemon_state) element.
Where: crates/ripley-app/src/app.rs:200-224 (view).
Problem: Main pane has no header row. UI.md wireframe shows:
Ripley ● Monitoring · 12 projects
Last poll: 2m ago
Each view title is currently rendered inside the body of that view (audit.rs:11, posture.rs:12, settings.rs:10, deep_scan.rs:21), which gives inconsistent vertical offsets and no place for the status strip.
Fix: Hoist the view title into the shell. app::view should render
column![top_bar(current_view, status), content] and individual view
files drop their leading text("…").size(18).
Where: rendered ad-hoc in audit.rs:43, posture.rs:64,
deep_scan.rs:199,261, monitor.rs:60-66.
Problem: Each view inlines its own format!("[{}]", ...) or
format!("{}", severity) rendered as plain text(...). DESIGN.md
specifies pill-shaped badges with rounded-full background fill at
20 % opacity, uppercase, +0.5 px tracking, weight 600.
Fix: Add severity_badge(level: Severity) -> Element that returns a
container(text("CRIT").size(11)…) with rounded Border::full and the
20 % bg color. Use it everywhere severity is shown.
Where: crates/ripley-app/src/theme.rs.
Problem: DESIGN.md defines five severity-*-bg tokens (20 % alpha
variants) used for badge fills, summary card backgrounds, and alert
row highlights. None exist in theme.rs.
Fix: add SEVERITY_CRITICAL_BG, SEVERITY_HIGH_BG, … as
Color::from_rgba(r, g, b, 0.125) matching #…20.
| View | Empty state |
|---|---|
| Alerts | centered "No findings / Your dependencies look clean." (no CTA) |
| Guard log | centered "No guard log entries yet." (no CTA) |
| Deep Scan | centered title + body + CTA button (Run Deep Scan) |
| Monitor | centered title + body + CTA button (Enable Monitor) |
| Audit | top-left aligned title + "Run ripley audit to generate…" (no centering, no CTA) |
| Posture | top-left aligned title + "Run ripley harden to generate…" (no centering, no CTA) |
| Settings | no empty state — it always shows |
DESIGN.md (Do's): "Every view has a next action. No dead-end screens."
Fix: Standardize via views::empty_state(icon, title, body, cta)
helper. Alerts/Guard/Audit/Posture should all get a primary CTA
(Scan Now / View Recent Activity / Run Audit / Run Harden).
Where: theme.rs:16-26 — both constants are #[allow(dead_code)].
Problem: Hover and pressed states are invisible across the entire app. Sidebar buttons, list rows, and CTAs all lack the subtle layer shift DESIGN.md specifies as the only depth mechanism.
Fix: When migrating to component styles (CC-1, CC-2), wire up hover
states via button::Status::Hovered / Pressed branches.
DESIGN.md rule: package names, versions, file paths, script content
must be code (system monospace).
Where this is violated:
views/alerts.rs:30-31— package@version rendered with default sansviews/guard_log.rs:25— package + action rendered as sansviews/deep_scan.rs:208,268—finding.path.display()rendered as sansviews/audit.rs:55,posture.rs:84— fix commands rendered as sans
Fix: Define MONO: Font = Font::MONOSPACE; in theme.rs (or use
iced::Font::with_name("SF Mono") with a fallback) and apply via
.font(theme::MONO) on the relevant text(...) builders.
Where: views/deep_scan.rs:106 — text(count.to_string()).size(18).
DESIGN.md (Summary Cards, typography.display): numbers should be 28 px bold.
Fix: size 28, weight bold (iced Font { weight: Bold, .. }).
Where: crates/ripley-app/src/views/settings.rs:7-71.
Problem: The view dumps the config as 9 text(format!(...)) lines.
UI.md specifies dropdowns, toggles, multi-value lists with [✕] rows,
"+ Add root" buttons, and radio groups for guard mode. None of this is
implemented.
Fix: This is a substantial body of work and should likely be its
own milestone. For now: at minimum group with section headers and add
a [● Live · edit config.toml] reference so users know it's read-only.
Where: crates/ripley-app/src/app.rs:276-284.
Problem: Sidebar background is Colors::SURFACE (#161b22). DESIGN.md
(components.sidebar) specifies bg (#0d1117) with a border-right.
The current treatment makes the sidebar feel lifted rather than
recessed.
Fix: Swap to BG and rely on the right border for separation.
| ID | P | Finding |
|---|---|---|
| V1.1 | P0 | List rows lack severity dot, severity badge, and [Fix] action — the row is a single text(format!("[{}] {}@{}", sev, pkg, ver)). Compare UI.md wireframe (line 138). |
| V1.2 | P1 | Package@version is rendered in default sans, must be monospace (CC-10). |
| V1.3 | P1 | No advisory ID line (UI.md spec line 152). |
| V1.4 | P1 | No project path / timestamp / action button on each row. |
| V1.5 | P2 | Row containers have padding(12) but no border-bottom and no hover state (CC-9). |
| V1.6 | P2 | Empty state has no CTA (CC-8). |
| ID | P | Finding |
|---|---|---|
| V2.1 | P1 | Renders as freeform text instead of the columnar table in UI.md lines 192-200. No TIME / PACKAGE / SCRIPT / RISK / ACTION columns. |
| V2.2 | P1 | Risk level is rendered as format!("Risk: {} · Script: {}", …) — should be a severity badge + monospace script name. |
| V2.3 | P2 | No expandable row interaction to show matched rules / script excerpt (DESIGN.md Tables, "Expandable rows"). |
| V2.4 | P2 | Empty state has no CTA (CC-8). |
| ID | P | Finding |
|---|---|---|
| V3.1 | P1 | Summary cards render the count with size(18) not the spec's 28 (CC-11). |
| V3.2 | P1 | Summary card backgrounds are all Colors::SURFACE — DESIGN.md says the bg should be the severity background color matching the metric (clean green or critical red, never neutral). |
| V3.3 | P1 | Section header arrows are ">" / "v" text — should be 16 px chevron glyphs (DESIGN.md iconography). |
| V3.4 | P1 | "Run Deep Scan" button is iced purple (CC-2). |
| V3.5 | P1 | Dead-man-switch panel uses SURFACE background but DESIGN.md treatment for critical containers would call for severity-critical-bg (the translucent red) so it visually screams. |
| V3.6 | P2 | Severity label rendered via format!("{}", finding.severity) instead of badge component (CC-6). |
| V3.7 | P2 | Action buttons row at the bottom (Export Report, Copy Rotation Checklist) is left-aligned but has no [secondary] styling; both should be button-secondary not button-primary per UI.md conventions. |
| V3.8 | P3 | "DEAD MAN SWITCH DETECTED" is text(...) in SEVERITY_CRITICAL; should also be a badge/banner with severity-critical-bg fill. |
| ID | P | Finding |
|---|---|---|
| V4.1 | P1 | "Enable Monitor" CTA in iced purple (CC-2). |
| V4.2 | P1 | Alert rows show [12:04:05Z] [HIGH] process (PID 123) as a single text run. Should split into severity badge + monospace process name + secondary timestamp. |
| V4.3 | P2 | format_timestamp strips date — fine for live view, but it should reuse a shared format_short_time(u64) so the same format appears in Alerts/Guard/Monitor. |
| V4.4 | P2 | Empty state lacks an icon glyph. |
| ID | P | Finding |
|---|---|---|
| V5.1 | P1 | Empty state is top-left aligned plain text — break from the centered pattern of Deep Scan/Monitor (CC-8). |
| V5.2 | P1 | Category traffic-light indicator is the unicode glyph ●◐○ followed by text — DESIGN.md (Traffic-Light Bars) specifies an 8 px filled bar with track. |
| V5.3 | P1 | No category score cards on top — UI/DESIGN spec calls for a row of category-score-cards before the per-category list. |
| V5.4 | P2 | Fix command line uses arrow → then plain sans — should use the command-block component (monospace + subtle bg + copy icon). |
| V5.5 | P2 | Summary line at bottom ("3 green, 1 yellow, 0 red") renders without color — should use severity-* colors inline. |
| ID | P | Finding |
|---|---|---|
| V6.1 | P1 | Same empty-state inconsistency as V5.1. |
| V6.2 | P1 | Same traffic-light glyph treatment as V5.2. |
| V6.3 | P2 | "Detected: {pms_text}" line shows package managers as comma-joined plain text — should be a row of monospace pills (e.g. npm v10.2.1 · brew v4.2). |
| V6.4 | P2 | Same fix-command treatment as V5.4. |
| V6.5 | P2 | The traffic-light dot color in the category header (line 60) is a bare text("●") not a real dot graphic — fine for now, but DESIGN.md calls for the 8 px filled circle as a primitive. |
| ID | P | Finding |
|---|---|---|
| V7.1 | P0 | The entire view is read-only text(format!(...)) output. UI.md specifies a full editable form with dropdowns, toggles, list editors. (See CC-12.) |
| V7.2 | P1 | "Monitor" subheading shares the same 14 px sans as the rest — DESIGN.md headings are 15 px semibold with bottom-rule. |
| V7.3 | P1 | No section grouping or visual separation between General / Monitoring / Guard / Monitor sections. |
| V7.4 | P2 | Boolean values printed as yes / no — should be toggle widgets per DESIGN.md (Toggles). |
Per DESIGN.md components section, these are referenced by views but
have no implementation:
- Severity badge (pill, uppercase, full radius) — CC-6.
- Severity dot (8 px filled circle) — partially faked with
●glyph. - Card (surface bg, border, lg radius, lg padding) —
deep_scan.rs:113-121inlines a partial version; not shared. - Summary card (display number + secondary label, severity bg).
- Category score card (label + traffic-light bar + finding count).
- Traffic-light bar (8 px filled bar with track).
- Code block / command block (mono font, bg surface, optional copy icon).
- Input field, dropdown, toggle (all needed for Settings).
- Top bar (current view + monitoring status + last-poll timestamp).
- Sidebar footer (version + daemon status dot).
- Alert row (severity dot + badge + pkg@ver mono + advisory + path + time + action).
- Notification overlay (in-app variant of the OS notification).
Each block is roughly one PR. Bullets in priority order.
- Add missing color tokens to
theme.rs:ACCENT_MUTED,ACCENT_ACTIVE,TEXT_ON_ACCENT,SEVERITY_*_BG(5 entries),SURFACE_ELEVATED. Remove the#[allow(dead_code)]markers fromSURFACE_HOVERandSURFACE_ACTIVE. - Add
theme::spacingandtheme::radiusconstants matching DESIGN.md. - Add
theme::font::{display, heading, body, body_sm, badge, code, code_sm}helpers that return sizedtextbuilders. - Add
theme::buttons::{primary, secondary, danger, nav}style closures.
- Add top bar with current view title + monitoring status + last poll.
- Rebuild sidebar with logo header, badge support, footer (version + status dot).
- Move view titles out of view bodies.
- Implement
severity_badge,severity_dot,traffic_light_baras reusable elements. - Replace every inline
format!("[{}]", sev)with the badge. - Replace every
text("●")/text("◐")with the dot primitive.
- Single
empty_state(title, body, cta)helper. - Convert Alerts, Guard, Audit, Posture to use it with appropriate CTAs.
Significant work — closest to the UI.md wireframes; should be its own PR.
Largest single piece of work; depends on iced toggle + radio implementations.
Tracking-only for now.
While iterating:
# rebuild + relaunch
pkill -x ripley-app; cargo build -p ripley-app && open ./target/debug/ripley-app
# find current window id (changes per launch)
swift -e 'import CoreGraphics
let w = CGWindowListCopyWindowInfo([.optionAll], 0) as! [[String: Any]]
for x in w {
let n = x["kCGWindowOwnerName" as String] as? String ?? ""
let b = x["kCGWindowBounds" as String] as? [String: Any] ?? [:]
if n.contains("ripley") && (b["Width"] as? Double ?? 0) > 500 {
print(x["kCGWindowNumber" as String] as? Int ?? 0)
}
}'
# capture each view
for i in 1 2 3 4 5 6 7; do
peekaboo hotkey --keys "cmd,$i" --app ripley-app
sleep 0.4
peekaboo image --window-id <WID> --path /tmp/ripley-view-$i.png
doneCmd+1..7 keyboard shortcuts that enable this loop live in
crates/ripley-app/src/app.rs in the subscription method.