Add the UI foundation: vim keymap, widgets and the component contract

- keymap: every key sequence in vim notation (gg, yy, <C-a>,
  <Space>s), with counts (5j), per-screen overrides and which-key hints,
  all from the same tables.
- Component: the one trait for screens and popups. They only look at
  the data through a Context and answer with Actions.
- Action: everything a screen can ask the app to do.
- widgets: big digits, buttons, a day timeline, a heatmap, a text field.
- ui/mod.rs: tabs, status line and the which-key popup; terminal.rs sets
  the terminal up (and restores it after a crash).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
skkeyeandClaude Opus 5.5 committed 2026-09-24 02:59:22 -04:00
1 parent eff7834838
commit 7c8a02275e
13 files changed
+1588

No files matched your search

+85
View File
@@ -0,0 +1,85 @@
//! Everything a screen or popup can ask the app to do.
//!
//! Screens and popups never change your data themselves: they return an
//! `Action`, and the app applies it in one place (`App::apply`), saves, and
//! reports what happened. That's the only way anything changes.
use jiff::civil::Date;
use jiff::{SignedDuration, Timestamp};
use crate::domain::Segment;
use crate::export::ExportFormat;
use crate::export::timesheet::CopyPreset;
use crate::time::DateRange;
use crate::ui::{Component, Tab};
pub enum Action {
// ----- Moving around the app
GoTo(Tab),
OpenPopup(Box<dyn Component>),
ClosePopup,
/// The help popup for the current screen.
ShowHelp,
/// Show a message in the status line.
Notify(String),
Quit,
// ----- The shift
Start,
/// Start working as if you had pressed Start at this earlier moment.
StartAt(Timestamp),
Pause,
/// Pause as if you had pressed Pause at this earlier moment.
PauseAt(Timestamp),
EndDay,
EndDayAt(Timestamp),
/// Move the start of the session in progress by this much.
ShiftRunningStart(SignedDuration),
/// Correct the session in progress: when it started, and optionally
/// when it actually stopped (you forgot to pause).
FixRunning {
since: Timestamp,
stopped: Option<Timestamp>,
},
/// Silence the break suggestion for a while.
Snooze,
// ----- Recorded work
AddSegment {
day: Date,
segment: Segment,
},
/// Replace segment `index` of `day`, filing the new one under `new_day`.
ReplaceSegment {
day: Date,
index: usize,
segment: Segment,
new_day: Date,
},
DeleteSegment {
day: Date,
index: usize,
},
Undo,
Redo,
// ----- Getting data out
Copy {
days: Vec<Date>,
preset: CopyPreset,
},
Export {
format: ExportFormat,
range: DateRange,
},
// ----- Settings, by their key in config.toml
SetSetting {
key: &'static str,
value: String,
},
ResetSetting {
key: &'static str,
},
ToggleWhichKey,
}