fix(gui): needs-attention grows to fit with 'show N more' instead of a hidden inner scroll; receipt wording follows the policy

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MtYEaR6M6Ybdhm6X98BWNu
This commit is contained in:
swaitsandClaude Opus 5.5 committed 2026-10-12 18:39:48 -06:00
1 parent 2a00f4ef33
commit 3f20858df3
5 files changed
+56 -15

No files matched your search

+4 -4
View File
@@ -51,9 +51,9 @@ pub fn find(b: &Books) -> Vec<Attention> {
severity: Severity::Problem,
title: format!("{} without receipts", fmt::count(missing.len(), "expense", "expenses")),
detail: format!(
"{} of spending over the {} threshold has no receipt attached.",
"{} of spending has no receipt attached ({}).",
fmt::dollars(total),
fmt::dollars(b.receipt_threshold)
b.receipt_rule()
),
target: Some(Route::Transactions),
prompt: format!(
@@ -67,11 +67,11 @@ pub fn find(b: &Books) -> Vec<Attention> {
severity: Severity::Problem,
title: format!("No receipt: {}", e.description),
detail: format!(
"#{} · {} · {} is over the {} receipt threshold.",
"#{} · {} · {}: {}.",
e.id,
fmt::date(e.date),
fmt::dollars(e.amount),
fmt::dollars(b.receipt_threshold)
b.receipt_rule()
),
target: Some(Route::Entry(e.id)),
prompt: format!(
+13
View File
@@ -316,6 +316,19 @@ impl Books {
Ok(books)
}
/// Why an entry needs a receipt, in words: "every entry needs a
/// receipt" or "over the $25.00 receipt threshold".
pub fn receipt_rule(&self) -> String {
if self.receipt_threshold.is_zero() {
"every entry needs a receipt".to_string()
} else {
format!(
"over the {} receipt threshold",
crate::fmt::dollars(self.receipt_threshold)
)
}
}
pub fn seq(&self) -> u64 {
self.journal.len() as u64
}
+1 -4
View File
@@ -243,10 +243,7 @@ pub fn detail(ui: &mut Ui, b: &Books, e: &EntryInfo, shell: &mut Shell<'_>) {
);
if all_for_entry.is_empty() {
let msg = if e.missing_receipt {
format!(
"None attached, and this entry is over the {} receipt threshold.",
fmt::dollars(b.receipt_threshold)
)
format!("None attached, and {}.", b.receipt_rule())
} else {
"None attached.".to_string()
};
+34 -6
View File
@@ -283,11 +283,17 @@ fn attention(ui: &mut Ui, b: &Books, shell: &mut Shell<'_>) {
);
return;
}
egui::ScrollArea::vertical()
.id_salt("attention")
.max_height(260.0)
.show(ui, |ui| {
for (i, a) in b.attention.iter().enumerate() {
// No inner scrolling: the card grows to fit. Past a handful of
// items, the rest sit behind a button that says how many.
const FIRST: usize = 6;
let id = ui.id().with("attention-expanded");
let mut expanded: bool = ui.ctx().data(|d| d.get_temp(id)).unwrap_or(false);
let shown = if expanded {
b.attention.len()
} else {
b.attention.len().min(FIRST)
};
for (i, a) in b.attention.iter().take(shown).enumerate() {
if i > 0 {
ui.add_space(4.0);
widgets::rule(ui);
@@ -317,7 +323,29 @@ fn attention(ui: &mut Ui, b: &Books, shell: &mut Shell<'_>) {
});
});
}
});
let hidden = b.attention.len() - shown;
if hidden > 0 || expanded && b.attention.len() > FIRST {
ui.add_space(8.0);
let label = if hidden > 0 {
format!("{} Show {} more", icon::CARET_DOWN, hidden)
} else {
format!("{} Show fewer", icon::CARET_UP)
};
if ui
.add(
egui::Button::new(
RichText::new(label)
.font(theme::semibold(12.5))
.color(p.accent),
)
.frame(false),
)
.clicked()
{
expanded = !expanded;
ui.ctx().data_mut(|d| d.insert_temp(id, expanded));
}
}
},
);
}
+4 -1
View File
@@ -77,7 +77,10 @@ fn screenshots() {
let books = data::load_once(&source).unwrap();
let mut harness = Harness::builder()
.with_size(egui::vec2(
1440.0,
std::env::var("SHOT_WIDTH")
.ok()
.and_then(|w| w.parse().ok())
.unwrap_or(1440.0),
std::env::var("SHOT_HEIGHT")
.ok()
.and_then(|h| h.parse().ok())