Projects: classes as entities, and the sidebar fits short windows
check / check (push) Canceled after 0s

A project is a class with facts (description, status, kind, party,
dates, links, notes) in one project.set event behind the format
feature projects. Four tools (books.add_project, update_project,
projects, project); documents and decisions link to projects; B-37:
on-spec costs are expensed as incurred, repayment is revenue, and the
unrecovered cost is never below zero (Kani). Viewer: a Projects list
and a project page with its figures, by month and by account. The
sidebar measures its footer instead of reserving a fixed 172 px.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T3gXMVt7XJPi6ens1or9Dj
This commit is contained in:
swaitsandClaude Opus 5.5 committed 2026-10-22 03:50:52 -06:00
1 parent 0495726001
commit 321b94562a
71 files changed
+7446 -217

No files matched your search

+52 -1
View File
@@ -73,6 +73,37 @@ pub fn pill(ui: &mut Ui, text: &str, fg: Color32, bg: Color32) -> Response {
.response
}
/// One journal event on a History card: its icon, its text as a link
/// (cut with an ellipsis), then its number and time, which keep their
/// room, so a long name in the text never widens the card. Returns the
/// link's response.
pub fn history_row(ui: &mut Ui, icon: &str, text: &str, seq: u64, ts: &str) -> Response {
let p = pal(ui.ctx());
ui.horizontal(|ui| {
ui.label(RichText::new(icon).color(p.muted));
let meta = format!("event {seq} · {}", crate::fmt::timestamp(ts));
let meta_w = ui
.painter()
.layout_no_wrap(meta.clone(), theme::regular(11.0), p.faint)
.size()
.x;
let w = (ui.available_width() - meta_w - ui.spacing().item_spacing.x).max(40.0);
let link = ui
.scope(|ui| {
ui.set_max_width(w);
link_truncated(ui, RichText::new(text).font(theme::regular(12.5)))
})
.inner;
ui.label(
RichText::new(meta)
.font(theme::regular(11.0))
.color(p.faint),
);
link
})
.inner
}
/// Text that looks like plain text until you hover it, then like a link.
pub fn link(ui: &mut Ui, text: RichText) -> Response {
link_label(ui, egui::Label::new(text))
@@ -276,11 +307,31 @@ pub fn recently_copied(ui: &Ui, id: egui::Id) -> bool {
/// A value that copies itself when clicked: a pointer and a copy icon on
/// hover, a check for a moment after.
pub fn copyable(ui: &mut Ui, text: RichText, value: &str) -> Response {
copyable_label(ui, text, value, false)
}
/// [`copyable`] on one line, cut with an ellipsis where the space ends
/// (the copy icon's slot kept), the full text on hover: for text of any
/// length with no spaces to wrap at, a long name or a web address.
pub fn copyable_truncated(ui: &mut Ui, text: RichText, value: &str) -> Response {
copyable_label(ui, text, value, true)
}
fn copyable_label(ui: &mut Ui, text: RichText, value: &str, truncate: bool) -> Response {
let id = ui.id().with(("copy", value));
let p = pal(ui.ctx());
ui.horizontal(|ui| {
ui.spacing_mut().item_spacing.x = 4.0;
let r = ui.add(egui::Label::new(text).sense(Sense::click()));
let r = if truncate {
let w = (ui.available_width() - 18.0).max(24.0);
ui.scope(|ui| {
ui.set_max_width(w);
ui.add(egui::Label::new(text).truncate().sense(Sense::click()))
})
.inner
} else {
ui.add(egui::Label::new(text).sense(Sense::click()))
};
let recent = recently_copied(ui, id);
let (slot, _) = ui.allocate_exact_size(vec2(14.0, 14.0), Sense::hover());
if r.hovered() || recent {