diff --git a/DESIGN.md b/DESIGN.md index a7ad676..a6ac13c 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -118,8 +118,8 @@ do not go lighter) · `accent #A8650C` (deepened for AA) · `agent #0C7E75`. 5. **Allocation/category bars** use neutral/low-chroma tonal steps, never amber as a category. 6. **Danger stays loud red, early.** Even though the app is otherwise near-colorless, unlimited approvals, unknown contracts, fresh-address sends, and over-cap states surface in `error`. -7. **Caution banners** = neutral surface + a 2px amber left keyline + amber icon/text. Not a filled - warm block. +7. **Caution** = an amber alert icon + the risk text, inline. No left keyline or banner box — the + amber icon carries the signal and the risk word carries the emphasis. 8. **Budget/utilization bars** = thin (4px), neutral track, neutral/cyan fill at rest; amber only ≥90%, red at ≥100%. Never a saturated amber slab. @@ -232,8 +232,8 @@ data components must define **empty / loading / error**. Defaults: - **Seed reveal** — blurred by default, **hold-to-reveal**, auto-hides after a few seconds, a "make sure nobody is watching" caution, **never auto-copied** (and Copy is visually demoted below Hold-to-reveal). The index numbers stay legible so the grid reads as "present but hidden." -- **Network warning** on Receive — the one caution moment; neutral surface + amber keyline, the risk - word emphasized, not the network chip. +- **Network warning** on Receive — the one caution moment; an amber alert icon + the risk text inline + (no keyline or banner box), the risk word emphasized, not the network chip. - **Kill switch / revocation** — Pause / Revoke / Rotate always one deliberate action away on any agent; a master "Pause all agents" belongs in Settings (agent governance), styled deliberate. diff --git a/crates/deckard-app/src/receive.rs b/crates/deckard-app/src/receive.rs index 4074aba..ac3e66e 100644 --- a/crates/deckard-app/src/receive.rs +++ b/crates/deckard-app/src/receive.rs @@ -85,20 +85,13 @@ impl Shell { .text_color(fg) .child(address), ) - // Network warning — the one caution moment (DESIGN §236): a - // neutral surface with a 2px amber LEFT keyline + amber icon/text. - // Not a filled warm block; the risk word carries the emphasis. + // Network warning — the one caution moment: an amber alert icon + the risk + // text, inline. No banner box or keyline; the icon carries the signal. .child( h_flex() .w_full() .items_start() .gap_2() - .px_3() - .py_2p5() - .rounded_lg() - .bg(surface) - .border_l_2() - .border_color(amber) .child( Icon::new(IconName::TriangleAlert) .text_color(amber) @@ -106,6 +99,8 @@ impl Shell { ) .child( div() + .flex_1() + .min_w_0() .text_xs() .text_color(fg) .child("Only send Ethereum-network assets to this address. Funds sent on the wrong network may be lost."), diff --git a/crates/deckard-app/src/settings_view.rs b/crates/deckard-app/src/settings_view.rs index 4f87c5f..a5f4bbd 100644 --- a/crates/deckard-app/src/settings_view.rs +++ b/crates/deckard-app/src/settings_view.rs @@ -38,6 +38,8 @@ impl Shell { .justify_between() .child( v_flex() + .flex_1() + .min_w_0() .gap_0p5() .child( div() diff --git a/crates/deckard-app/src/shell.rs b/crates/deckard-app/src/shell.rs index 1540c5a..8ea2150 100644 --- a/crates/deckard-app/src/shell.rs +++ b/crates/deckard-app/src/shell.rs @@ -13,6 +13,7 @@ use gpui::{ use gpui_component::{ h_flex, input::{InputEvent, InputState}, + scroll::ScrollableElement, v_flex, ActiveTheme, TitleBar, }; @@ -1427,28 +1428,57 @@ impl Render for Shell { // (sidebar | [breadcrumb / content / status strip]) + command palette. self.prepare_shield_inputs(window, cx); let title_bar = self.render_title_bar(cx); + // Each scrollable surface inlines its OWN `.overflow_y_scrollbar()` (don't factor into + // a helper): gpui-component keys the scroll offset by call site, so per-arm calls give + // each surface an independent offset. Receive/Shield are short centered cards — no wrapper. let content = match (self.selection, self.surface) { - (_, Surface::Settings) => self.render_settings(window, cx).into_any_element(), + (_, Surface::Settings) => div() + .id("scroll-settings") + .size_full() + .overflow_y_scrollbar() + .child(self.render_settings(window, cx)) + .into_any_element(), (_, Surface::Receive) => self.render_receive(cx).into_any_element(), (_, Surface::Shield) => self.render_shield(cx).into_any_element(), - (Selection::Wallet, Surface::Home) => { - self.render_wallet_home(cx).into_any_element() - } - (Selection::Project, Surface::Home) => { - self.render_project_home(cx).into_any_element() - } - (Selection::Agent, Surface::Home) => self.render_agent_home(cx).into_any_element(), + (Selection::Wallet, Surface::Home) => div() + .id("scroll-wallet") + .size_full() + .overflow_y_scrollbar() + .child(self.render_wallet_home(cx)) + .into_any_element(), + (Selection::Project, Surface::Home) => div() + .id("scroll-project") + .size_full() + .overflow_y_scrollbar() + .child(self.render_project_home(cx)) + .into_any_element(), + (Selection::Agent, Surface::Home) => div() + .id("scroll-agent") + .size_full() + .overflow_y_scrollbar() + .child(self.render_agent_home(cx)) + .into_any_element(), }; v_flex() .size_full() .child(title_bar) .child( h_flex().size_full().child(self.render_sidebar(cx)).child( + // Fill the full pane height (like the sidebar's `.h_full()`): `h_flex` + // centers its children vertically, so without this the content column + // collapses to its intrinsic height and floats mid-pane — the + // breadcrumb, content, and bottom status strip then bunch up and overlap + // whenever a view is shorter than the viewport. v_flex() .flex_1() + .h_full() .min_w_0() + .min_h_0() .child(self.render_breadcrumb(cx)) - .child(div().flex_1().min_h_0().child(content)) + // The slot is a `v_flex`, not a plain `div` (gpui defaults to + // `display: block`): the centered Receive/Shield roots use `flex_1` + + // `justify_center`, which only fill + center inside a flex parent. + .child(v_flex().flex_1().min_h_0().child(content)) .child(self.render_status_strip(cx)), ), ) diff --git a/crates/deckard-app/src/shell_chrome.rs b/crates/deckard-app/src/shell_chrome.rs index 1348aac..460c33a 100644 --- a/crates/deckard-app/src/shell_chrome.rs +++ b/crates/deckard-app/src/shell_chrome.rs @@ -298,8 +298,15 @@ impl Shell { .gap_2() .child(div().size(px(16.0)).rounded(px(4.0)).bg(id_square)) .child(div().text_sm().text_color(fg).child("Personal")) - .child(div().text_sm().text_color(muted).child("›")) - .child(div().text_sm().text_color(fg).child(self.view_label())), + // Skip the trailing "› " when it would just repeat the project name + // (Project Home's label is "Personal" → avoid "Personal › Personal"). + .when( + !(self.surface == Surface::Home && self.selection == Selection::Project), + |el| { + el.child(div().text_sm().text_color(muted).child("›")) + .child(div().text_sm().text_color(fg).child(self.view_label())) + }, + ), ) .child( h_flex() diff --git a/crates/deckard-app/src/shield_view.rs b/crates/deckard-app/src/shield_view.rs index 0440fbb..dcdf5a1 100644 --- a/crates/deckard-app/src/shield_view.rs +++ b/crates/deckard-app/src/shield_view.rs @@ -117,10 +117,21 @@ impl Shell { ), ) .child( - div() - .text_xs() - .text_color(muted) - .child("Your own 0zk address auto-fills in a later release."), + // Only call the recipient "your own 0zk address" when it actually matches the + // wallet's auto-filled address — a user-typed/edited recipient gets neutral copy + // so the line never misrepresents where the deposit is going. + div().text_xs().text_color(muted).child({ + let recipient = recipient_raw.trim(); + let is_own_address = + self.railgun_address.as_deref().map(str::trim) == Some(recipient); + if recipient.is_empty() { + "Enter the 0zk address that will receive the private balance." + } else if is_own_address { + "Pre-filled with your own 0zk address — edit it to shield to a different recipient." + } else { + "Shielding to the 0zk address above — double-check it before you continue." + } + }), ) .into_any_element(), ) @@ -289,14 +300,12 @@ impl Shell { ) } - /// The three honesty lines, in DESIGN's caution frame (neutral surface + a 2px amber - /// left keyline). Calm, not a filled warm block. + /// The three honesty lines in a calm neutral surface (no keyline). fn shield_honesty(&self, cx: &mut Context) -> impl IntoElement { let theme = cx.theme(); let fg = theme.foreground; let muted = theme.muted_foreground; let surface = theme.secondary; - let amber = theme::amber(theme.is_dark()); v_flex() .w_full() @@ -305,8 +314,6 @@ impl Shell { .py_2p5() .rounded_lg() .bg(surface) - .border_l_2() - .border_color(amber) .child( div() .text_xs() @@ -444,6 +451,8 @@ impl Shell { ) .child( v_flex() + .flex_1() + .min_w_0() .gap_1() .child( div()