Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down Expand Up @@ -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.

Expand Down
13 changes: 4 additions & 9 deletions crates/deckard-app/src/receive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,27 +85,22 @@ 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)
.flex_shrink_0(),
)
.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."),
Expand Down
2 changes: 2 additions & 0 deletions crates/deckard-app/src/settings_view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ impl Shell {
.justify_between()
.child(
v_flex()
.flex_1()
.min_w_0()
.gap_0p5()
.child(
div()
Expand Down
48 changes: 39 additions & 9 deletions crates/deckard-app/src/shell.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use gpui::{
use gpui_component::{
h_flex,
input::{InputEvent, InputState},
scroll::ScrollableElement,
v_flex, ActiveTheme, TitleBar,
};

Expand Down Expand Up @@ -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)),
),
)
Expand Down
11 changes: 9 additions & 2 deletions crates/deckard-app/src/shell_chrome.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 "› <view>" 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()
Expand Down
27 changes: 18 additions & 9 deletions crates/deckard-app/src/shield_view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
)
Expand Down Expand Up @@ -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<Self>) -> 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()
Expand All @@ -305,8 +314,6 @@ impl Shell {
.py_2p5()
.rounded_lg()
.bg(surface)
.border_l_2()
.border_color(amber)
.child(
div()
.text_xs()
Expand Down Expand Up @@ -444,6 +451,8 @@ impl Shell {
)
.child(
v_flex()
.flex_1()
.min_w_0()
.gap_1()
.child(
div()
Expand Down
Loading