返回 CodeWhale
workbar.rs
1 //! The eight Codewhale work-dock panels, with ordinary example rows.
2
3 use ratatui::{buffer::Buffer, layout::Rect, text::Line};
4
5 use super::Entry;
6 use crate::{
7 Paint, Role, Theme, Workbar, WorkbarAgent, WorkbarPanel, WorkbarPlacement, WorkbarRow,
8 WorkbarTab, WorkbarTone,
9 };
10
11 fn tabs() -> Vec<WorkbarTab> {
12 WorkbarPanel::ORDER
13 .into_iter()
14 .map(|panel| match panel {
15 WorkbarPanel::Tasks => WorkbarTab::new(panel).count(3),
16 WorkbarPanel::Fleet => WorkbarTab::new(panel).count(2),
17 WorkbarPanel::Jobs => WorkbarTab::new(panel).count(1),
18 WorkbarPanel::Files => WorkbarTab::new(panel).count(3),
19 WorkbarPanel::Notes => WorkbarTab::new(panel).count(2),
20 _ => WorkbarTab::new(panel),
21 })
22 .collect()
23 }
24
25 fn rows(panel: WorkbarPanel) -> Vec<WorkbarRow> {
26 let dot = "·";
27 match panel {
28 WorkbarPanel::Tasks => vec![
29 WorkbarRow::new("graph:read", "Read the existing component")
30 .mark("✓")
31 .tone(WorkbarTone::Success)
32 .detail("completed"),
33 WorkbarRow::new("graph:extract", "Extract the native work dock")
34 .mark("●")
35 .tone(WorkbarTone::Live)
36 .detail("in progress"),
37 WorkbarRow::new("graph:document", "Document the reusable API")
38 .mark("○")
39 .detail("pending"),
40 ],
41 WorkbarPanel::Fleet => vec![
42 WorkbarRow::new("worker:build", "Builder").mark("●").agent(
43 WorkbarAgent::new(
44 "builder",
45 "running",
46 "Extract the dock and keep the native layout",
47 )
48 .elapsed_seconds(754)
49 .tokens(111_900)
50 .remaining(2),
51 ),
52 WorkbarRow::new("worker:review", "Reviewer")
53 .mark("✓")
54 .agent(
55 WorkbarAgent::new(
56 "reviewer",
57 "completed",
58 "Review tab and keyboard navigation",
59 )
60 .elapsed_seconds(183),
61 ),
62 ],
63 WorkbarPanel::Jobs => vec![
64 WorkbarRow::new("shell:gallery", "Render the component gallery")
65 .mark("●")
66 .tone(WorkbarTone::Live)
67 .detail("running"),
68 WorkbarRow::new("shell:check", "Check formatting")
69 .mark("✓")
70 .tone(WorkbarTone::Success)
71 .detail("completed"),
72 ],
73 WorkbarPanel::Files => vec![
74 WorkbarRow::new("files:edited", "Edited 2")
75 .mark("▾")
76 .tone(WorkbarTone::Heading)
77 .selectable(false),
78 WorkbarRow::new("files:edit:workbar", "src/components/workbar.rs")
79 .mark("✎")
80 .tone(WorkbarTone::Success)
81 .detail("+48 -12"),
82 WorkbarRow::new("files:edit:gallery", "src/gallery/workbar.rs")
83 .mark("✎")
84 .tone(WorkbarTone::Success)
85 .detail("+22 -4"),
86 WorkbarRow::new("files:read", "Read 1")
87 .mark("▾")
88 .tone(WorkbarTone::Heading)
89 .selectable(false),
90 WorkbarRow::new("files:read:source", "work_surface/render/mod.rs")
91 .mark("·")
92 .selectable(false),
93 ],
94 WorkbarPanel::Notes => vec![
95 WorkbarRow::new("notes:1", "Keep the native dock tab order")
96 .mark("▪")
97 .tone(WorkbarTone::Live)
98 .detail("/note show 1"),
99 WorkbarRow::new("notes:2", "Make each row independently selectable")
100 .mark("▪")
101 .tone(WorkbarTone::Live)
102 .detail("/note show 2"),
103 ],
104 WorkbarPanel::Context => vec![
105 WorkbarRow::new(
106 "context:budget",
107 format!("48k of 128k {dot} 38% {dot} compacts at 90%"),
108 )
109 .mark("◔")
110 .tone(WorkbarTone::Live)
111 .detail("/context for the full source map"),
112 WorkbarRow::new(
113 "context:system",
114 format!("system + tools {dot} 8.4k {dot} 24 tools"),
115 )
116 .mark("·")
117 .selectable(false),
118 WorkbarRow::new(
119 "context:conversation",
120 format!("conversation {dot} 18 {dot} 29.2k"),
121 )
122 .mark("·")
123 .selectable(false),
124 WorkbarRow::new("context:tool-output", format!("tool output {dot} 10.4k"))
125 .mark("·")
126 .selectable(false),
127 WorkbarRow::new("context:compact", "compact now")
128 .mark("▸")
129 .tone(WorkbarTone::Live)
130 .detail("/compact"),
131 ],
132 WorkbarPanel::Git => vec![
133 WorkbarRow::new("git:branch", format!("feature/work-dock {dot} up to date"))
134 .mark("⎇")
135 .tone(WorkbarTone::Live)
136 .detail("example/component-kit"),
137 WorkbarRow::new("git:changes", "2 modified files")
138 .mark("±")
139 .tone(WorkbarTone::Live)
140 .detail("/diff"),
141 WorkbarRow::new("git:commit", "Extract reusable work dock")
142 .mark("·")
143 .detail("latest commit"),
144 ],
145 WorkbarPanel::Cost => vec![
146 WorkbarRow::new("price:session", format!("session {dot} $0.42"))
147 .mark("$")
148 .tone(WorkbarTone::Live)
149 .detail("/cost for the full ledger"),
150 WorkbarRow::new("price:agents", format!("agents {dot} $0.18"))
151 .mark("·")
152 .detail("2 of 2 agents priced")
153 .selectable(false),
154 WorkbarRow::new(
155 "price:cache",
156 format!("cache hit {dot} parent 72% {dot} agents 64%"),
157 )
158 .mark("·")
159 .detail("/cache for per-turn cache telemetry"),
160 ],
161 }
162 }
163
164 pub(crate) fn sample(panel: WorkbarPanel) -> Workbar {
165 let rows = rows(panel);
166 let selected = rows
167 .iter()
168 .find(|row| row.selectable)
169 .map(|row| row.id.clone());
170 let mut dock = Workbar::new(panel, rows).tabs(tabs()).focused(true);
171 if let Some(selected) = selected {
172 dock = dock.selected(selected);
173 }
174 if panel == WorkbarPanel::Tasks {
175 let dot = "·";
176 dock = dock
177 .goal("Make the work dock reusable")
178 .progress(format!("TODO {dot} 1/3 {dot} 2 left"));
179 }
180 dock
181 }
182
183 fn tasks(area: Rect, buf: &mut Buffer, theme: &Theme) {
184 sample(WorkbarPanel::Tasks).paint(area, buf, theme);
185 }
186 fn fleet(area: Rect, buf: &mut Buffer, theme: &Theme) {
187 sample(WorkbarPanel::Fleet).paint(area, buf, theme);
188 }
189 fn jobs(area: Rect, buf: &mut Buffer, theme: &Theme) {
190 sample(WorkbarPanel::Jobs).paint(area, buf, theme);
191 }
192 fn files(area: Rect, buf: &mut Buffer, theme: &Theme) {
193 sample(WorkbarPanel::Files).paint(area, buf, theme);
194 }
195 fn notes(area: Rect, buf: &mut Buffer, theme: &Theme) {
196 sample(WorkbarPanel::Notes).paint(area, buf, theme);
197 }
198 fn context(area: Rect, buf: &mut Buffer, theme: &Theme) {
199 sample(WorkbarPanel::Context).paint(area, buf, theme);
200 }
201 fn git(area: Rect, buf: &mut Buffer, theme: &Theme) {
202 sample(WorkbarPanel::Git).paint(area, buf, theme);
203 }
204 fn cost(area: Rect, buf: &mut Buffer, theme: &Theme) {
205 sample(WorkbarPanel::Cost).paint(area, buf, theme);
206 }
207 fn top(area: Rect, buf: &mut Buffer, theme: &Theme) {
208 sample(WorkbarPanel::Tasks)
209 .placement(WorkbarPlacement::Top)
210 .paint(area, buf, theme);
211 }
212
213 fn narrow(area: Rect, buf: &mut Buffer, theme: &Theme) {
214 let rows = (0..12)
215 .map(|index| {
216 WorkbarRow::new(format!("graph:{index}"), format!("Plan step {}", index + 1))
217 .mark(if index == 3 { "●" } else { "○" })
218 .tone(if index == 3 {
219 WorkbarTone::Live
220 } else {
221 WorkbarTone::Muted
222 })
223 })
224 .collect();
225 Workbar::new(WorkbarPanel::Tasks, rows)
226 .tabs(tabs())
227 .goal("Extract the native dock")
228 .progress(if theme.ascii() {
229 "TODO . 3/12 . 9 left"
230 } else {
231 "TODO · 3/12 · 9 left"
232 })
233 .focused(true)
234 .selected("graph:3")
235 .offset(3)
236 .paint(area, buf, theme);
237 }
238
239 fn side_with(placement: WorkbarPlacement, area: Rect, buf: &mut Buffer, theme: &Theme) {
240 let area = area.intersection(buf.area);
241 let dock = sample(WorkbarPanel::Fleet).placement(placement);
242 let regions = dock.regions(area);
243 if regions.content.height > 0 && regions.content.width > 0 {
244 buf.set_line(
245 regions.content.x,
246 regions.content.y,
247 &Line::styled("Codewhale", theme.fg(Role::Foreground)),
248 regions.content.width,
249 );
250 if regions.content.height > 2 {
251 buf.set_line(
252 regions.content.x,
253 regions.content.y + 2,
254 &Line::styled("The conversation keeps its space.", theme.fg(Role::Muted)),
255 regions.content.width,
256 );
257 }
258 }
259 if let Some(area) = regions.dock {
260 dock.placement(regions.placement).paint(area, buf, theme);
261 }
262 }
263 fn left(area: Rect, buf: &mut Buffer, theme: &Theme) {
264 side_with(WorkbarPlacement::Left, area, buf, theme);
265 }
266 fn right(area: Rect, buf: &mut Buffer, theme: &Theme) {
267 side_with(WorkbarPlacement::Right, area, buf, theme);
268 }
269
270 pub(crate) fn entries() -> Vec<Entry> {
271 [
272 (
273 "workbar-tasks",
274 104,
275 7,
276 tasks as fn(Rect, &mut Buffer, &Theme),
277 ),
278 ("workbar-fleet", 104, 5, fleet),
279 ("workbar-jobs", 104, 5, jobs),
280 ("workbar-files", 104, 8, files),
281 ("workbar-notes", 104, 5, notes),
282 ("workbar-context", 104, 8, context),
283 ("workbar-git", 104, 6, git),
284 ("workbar-cost", 104, 6, cost),
285 ("workbar-top", 104, 7, top),
286 ("workbar-narrow", 38, 8, narrow),
287 ("workbar-left", 104, 12, left),
288 ("workbar-right", 104, 12, right),
289 ]
290 .into_iter()
291 .map(|(name, width, height, draw)| Entry {
292 name,
293 width,
294 height,
295 draw,
296 })
297 .collect()
298 }
299
299 lines RUST