max / makenotwork
- Co-Authored-By
- Claude Opus 5 (1M context) <noreply@anthropic.com>
- Claude-Session
- https://claude.ai/code/session_01MptwXZ8k65v19rFmdGAyki
5 files changed,
+81 insertions,
-172 deletions
| @@ -35,7 +35,7 @@ | |||
| 35 | 35 | ||
| 36 | 36 | use makeover_layout as layout; | |
| 37 | 37 | use quasi_declare::declare; | |
| 38 | - | use quasi_router::{Action, Document, Node, RegionKind}; | |
| 38 | + | use quasi_router::{Action, Document, RegionKind}; | |
| 39 | 39 | use quasi_webview::Webview; | |
| 40 | 40 | ||
| 41 | 41 | use crate::git::signing::SignatureStatus; | |
| @@ -488,19 +488,6 @@ | |||
| 488 | 488 | } | |
| 489 | 489 | } | |
| 490 | 490 | ||
| 491 | - | /// The one-letter status, as the tag it always was. | |
| 492 | - | /// | |
| 493 | - | /// A supplier and not a member: `token` is a **setting** on a row and on a | |
| 494 | - | /// cell, so the name cannot also be a node member. The fourth instance of that | |
| 495 | - | /// forcing, and quasicoherent `e2030032` is where it is being decided. | |
| 496 | - | fn status_badge(file: &DiffFile) -> Node { | |
| 497 | - | Node::token( | |
| 498 | - | quasi_router::screen::Tag::badge(file.status.label()) | |
| 499 | - | .tone(status_tone(file)) | |
| 500 | - | .hinted(file.status.name()), | |
| 501 | - | ) | |
| 502 | - | } | |
| 503 | - | ||
| 504 | 491 | /// What the file is called, and what it was called before. | |
| 505 | 492 | fn diff_label(file: &DiffFile) -> String { | |
| 506 | 493 | match &file.old_path { | |
| @@ -521,7 +508,10 @@ | |||
| 521 | 508 | region "diff-{slug(&file.path)}" as Group { | |
| 522 | 509 | region "diff-header-{slug(&file.path)}" as Group { | |
| 523 | 510 | across Wrap { | |
| 524 | - | beside Essential include status_badge(file); | |
| 511 | + | beside Essential badge file.status.label() { | |
| 512 | + | tone status_tone(file); | |
| 513 | + | hinted file.status.name(); | |
| 514 | + | } | |
| 525 | 515 | beside Essential link diff_label(file) | |
| 526 | 516 | to get "{view.base()}/tree/{view.current_ref}/{file.path}" navigating; | |
| 527 | 517 | beside Secondary toned "+{file.additions}" layout::Tone::Success |
| @@ -451,6 +451,59 @@ | |||
| 451 | 451 | forum_memberships::SETTINGS_SCREEN, | |
| 452 | 452 | ]; | |
| 453 | 453 | ||
| 454 | + | /// One window an analytics panel offers. | |
| 455 | + | /// | |
| 456 | + | /// Named members rather than a tuple, for `policy`'s reason: a description names | |
| 457 | + | /// what it draws, and `.1` is not a name. | |
| 458 | + | pub(super) struct Range { | |
| 459 | + | /// What the address carries. | |
| 460 | + | pub value: &'static str, | |
| 461 | + | /// What the heading calls it. | |
| 462 | + | pub label: &'static str, | |
| 463 | + | } | |
| 464 | + | ||
| 465 | + | /// The four windows, and what each is called. | |
| 466 | + | /// | |
| 467 | + | /// Shared by `project_analytics` and `user_analytics`, which draw the same | |
| 468 | + | /// selector against different addresses. It was duplicated between them, along | |
| 469 | + | /// with `Range`, `range_heading`, `is_shown` and a `chip` supplier each; the | |
| 470 | + | /// supplier went when `chip` became a node member (quasicoherent `e2030032`) | |
| 471 | + | /// and the rest is here. | |
| 472 | + | pub(super) const RANGES: &[Range] = &[ | |
| 473 | + | Range { | |
| 474 | + | value: "7d", | |
| 475 | + | label: "Last 7 days", | |
| 476 | + | }, | |
| 477 | + | Range { | |
| 478 | + | value: "30d", | |
| 479 | + | label: "Last 30 days", | |
| 480 | + | }, | |
| 481 | + | Range { | |
| 482 | + | value: "90d", | |
| 483 | + | label: "Last 90 days", | |
| 484 | + | }, | |
| 485 | + | Range { | |
| 486 | + | value: "all", | |
| 487 | + | label: "All time", | |
| 488 | + | }, | |
| 489 | + | ]; | |
| 490 | + | ||
| 491 | + | /// What the current range is called. | |
| 492 | + | pub(super) fn range_heading(range: &str) -> &'static str { | |
| 493 | + | RANGES | |
| 494 | + | .iter() | |
| 495 | + | .find(|window| window.value == range) | |
| 496 | + | .map_or("All time", |window| window.label) | |
| 497 | + | } | |
| 498 | + | ||
| 499 | + | /// Whether this window is the one being shown. | |
| 500 | + | /// | |
| 501 | + | /// A supplier because a comparison is an expression and the form admits none in | |
| 502 | + | /// an argument. It hands back a `bool`, which is the smallest type that works. | |
| 503 | + | pub(super) fn is_shown(window: &Range, range: &str) -> bool { | |
| 504 | + | window.value == range | |
| 505 | + | } | |
| 506 | + | ||
| 454 | 507 | /// Every described screen, mounted. | |
| 455 | 508 | /// | |
| 456 | 509 | /// Unconditional since `64b33b26`. Each entry used to be gated on |
| @@ -65,7 +65,7 @@ | |||
| 65 | 65 | ||
| 66 | 66 | use makeover_layout as layout; | |
| 67 | 67 | use quasi_declare::declare; | |
| 68 | - | use quasi_router::{Action, Document, Feed, FeedKind, Node, RegionKind, Tag}; | |
| 68 | + | use quasi_router::{Action, Document, Feed, FeedKind, RegionKind, Tag}; | |
| 69 | 69 | use quasi_webview::Webview; | |
| 70 | 70 | ||
| 71 | 71 | use crate::templates::CarouselFrame; | |
| @@ -329,16 +329,6 @@ | |||
| 329 | 329 | } | |
| 330 | 330 | } | |
| 331 | 331 | ||
| 332 | - | /// The badge a subscriber wears on a tier they already hold. | |
| 333 | - | /// | |
| 334 | - | /// A supplier and not a member: `token` is a **setting** on a row and on a | |
| 335 | - | /// cell, so the name cannot also be a node member. The third instance of that | |
| 336 | - | /// forcing after both analytics panels' `chip`, and quasicoherent `e2030032` | |
| 337 | - | /// is where it is being decided. | |
| 338 | - | fn subscribed_badge() -> Node { | |
| 339 | - | Node::token(Tag::badge("Subscribed")) | |
| 340 | - | } | |
| 341 | - | ||
| 342 | 332 | declare! { | |
| 343 | 333 | /// One membership tier, as a group. | |
| 344 | 334 | shape tier_group(store: &Store<'_>, tier: &SubscriptionTier) -> Node; | |
| @@ -348,7 +338,7 @@ | |||
| 348 | 338 | text tier.price.clone(); | |
| 349 | 339 | text tier.description.clone() unless tier.description.is_empty(); | |
| 350 | 340 | ||
| 351 | - | include subscribed_badge() when store.has_subscription; | |
| 341 | + | badge "Subscribed" when store.has_subscription; | |
| 352 | 342 | ||
| 353 | 343 | // The hidden `_csrf` goes the way the tip form's did: this arrives as | |
| 354 | 344 | // an htmx post and `frontend/src/core/htmx-glue.ts` attaches the token |
| @@ -43,8 +43,8 @@ | |||
| 43 | 43 | ||
| 44 | 44 | use makeover_layout as layout; | |
| 45 | 45 | use quasi_declare::declare; | |
| 46 | - | use quasi_router::screen::{Figure, Tag}; | |
| 47 | - | use quasi_router::{Action, Node, RegionKind, Slot}; | |
| 46 | + | use quasi_router::screen::Figure; | |
| 47 | + | use quasi_router::{Node, RegionKind, Slot}; | |
| 48 | 48 | use quasi_webview::Webview; | |
| 49 | 49 | ||
| 50 | 50 | use crate::types::{ChartBar, ContentItem, StatCard}; | |
| @@ -54,36 +54,6 @@ | |||
| 54 | 54 | ||
| 55 | 55 | /// The bespoke region the chart is drawn into. | |
| 56 | 56 | const CHART_SLOT: &str = "project-revenue-chart"; | |
| 57 | - | ||
| 58 | - | /// One window the panel offers. | |
| 59 | - | /// | |
| 60 | - | /// Named members rather than a tuple, for `policy`'s reason: a description names | |
| 61 | - | /// what it draws, and `.1` is not a name. | |
| 62 | - | struct Range { | |
| 63 | - | value: &'static str, | |
| 64 | - | label: &'static str, | |
| 65 | - | } | |
| 66 | - | ||
| 67 | - | /// The four windows the panel offers, and what each is called. | |
| 68 | - | const RANGES: &[Range] = &[ | |
| 69 | - | Range { | |
| 70 | - | value: "7d", | |
| 71 | - | label: "Last 7 days", | |
| 72 | - | }, | |
| 73 | - | Range { | |
| 74 | - | value: "30d", | |
| 75 | - | label: "Last 30 days", | |
| 76 | - | }, | |
| 77 | - | Range { | |
| 78 | - | value: "90d", | |
| 79 | - | label: "Last 90 days", | |
| 80 | - | }, | |
| 81 | - | Range { | |
| 82 | - | value: "all", | |
| 83 | - | label: "All time", | |
| 84 | - | }, | |
| 85 | - | ]; | |
| 86 | - | ||
| 87 | 57 | /// The panel as the route answers it: the region, carrying its own id. | |
| 88 | 58 | #[must_use] | |
| 89 | 59 | pub fn fragment( | |
| @@ -129,7 +99,7 @@ | |||
| 129 | 99 | ||
| 130 | 100 | link "Docs: Analytics" to get "/docs/analytics" navigating; | |
| 131 | 101 | link "Export data" to get "/dashboard/export" navigating; | |
| 132 | - | section heading(range); | |
| 102 | + | section super::range_heading(range); | |
| 133 | 103 | ||
| 134 | 104 | for chip in range_chips(slug, range) { | |
| 135 | 105 | include chip; | |
| @@ -148,40 +118,6 @@ | |||
| 148 | 118 | when items.is_empty(); | |
| 149 | 119 | include top_items(items) unless items.is_empty(); | |
| 150 | 120 | } | |
| 151 | - | ||
| 152 | - | /// What the window is called, as the heading says it. | |
| 153 | - | fn heading(range: &str) -> &'static str { | |
| 154 | - | RANGES | |
| 155 | - | .iter() | |
| 156 | - | .find(|window| window.value == range) | |
| 157 | - | .map_or("All time", |window| window.label) | |
| 158 | - | } | |
| 159 | - | ||
| 160 | - | /// Whether this window is the one being shown. | |
| 161 | - | /// | |
| 162 | - | /// A supplier because a comparison is an expression, and the form admits none | |
| 163 | - | /// in an argument. It hands back a `bool`, which is the smallest thing that | |
| 164 | - | /// works and keeps it out of the population. | |
| 165 | - | fn is_shown(window: &Range, range: &str) -> bool { | |
| 166 | - | window.value == range | |
| 167 | - | } | |
| 168 | - | ||
| 169 | - | /// One range control. | |
| 170 | - | /// | |
| 171 | - | /// A supplier and not a member: `token` is a **setting** on a row and on a cell, | |
| 172 | - | /// and a body's idents are told from settings by the member list alone, so the | |
| 173 | - | /// name cannot mean both. `quasi_declare`'s `NODE_MEMBERS` records the ruling. | |
| 174 | - | fn chip(window: &Range, slug: &str, range: &str) -> Node { | |
| 175 | - | Node::token( | |
| 176 | - | Tag::chip( | |
| 177 | - | window.value, | |
| 178 | - | Action::get(format!("/dashboard/project/{slug}/tabs/analytics")) | |
| 179 | - | .carrying("range", window.value), | |
| 180 | - | ) | |
| 181 | - | .latched(is_shown(window, range)), | |
| 182 | - | ) | |
| 183 | - | } | |
| 184 | - | ||
| 185 | 121 | declare! { | |
| 186 | 122 | /// The four range controls. | |
| 187 | 123 | /// | |
| @@ -191,8 +127,11 @@ | |||
| 191 | 127 | /// the template composing a class name. | |
| 192 | 128 | shape range_chips(slug: &str, range: &str) -> Vec<Node>; | |
| 193 | 129 | ||
| 194 | - | for window in RANGES { | |
| 195 | - | include chip(window, slug, range); | |
| 130 | + | for window in super::RANGES { | |
| 131 | + | chip window.value | |
| 132 | + | to get "/dashboard/project/{slug}/tabs/analytics" carrying "range" window.value { | |
| 133 | + | latched super::is_shown(window, range); | |
| 134 | + | } | |
| 196 | 135 | } | |
| 197 | 136 | } | |
| 198 | 137 | ||
| @@ -301,10 +240,10 @@ | |||
| 301 | 240 | ||
| 302 | 241 | #[test] | |
| 303 | 242 | fn the_heading_names_the_window_and_falls_back_to_all_time() { | |
| 304 | - | assert_eq!(heading("7d"), "Last 7 days"); | |
| 305 | - | assert_eq!(heading("90d"), "Last 90 days"); | |
| 243 | + | assert_eq!(super::super::range_heading("7d"), "Last 7 days"); | |
| 244 | + | assert_eq!(super::super::range_heading("90d"), "Last 90 days"); | |
| 306 | 245 | // What the template's `{% else %}` did for anything unrecognised. | |
| 307 | - | assert_eq!(heading("nonsense"), "All time"); | |
| 246 | + | assert_eq!(super::super::range_heading("nonsense"), "All time"); | |
| 308 | 247 | } | |
| 309 | 248 | ||
| 310 | 249 | #[test] |
| @@ -46,8 +46,8 @@ | |||
| 46 | 46 | ||
| 47 | 47 | use makeover_layout as layout; | |
| 48 | 48 | use quasi_declare::declare; | |
| 49 | - | use quasi_router::screen::{Figure, Tag}; | |
| 50 | - | use quasi_router::{Action, Node, RegionKind, Request, Response, RouteError}; | |
| 49 | + | use quasi_router::screen::Figure; | |
| 50 | + | use quasi_router::{RegionKind, Request, Response, RouteError}; | |
| 51 | 51 | use quasi_webview::Webview; | |
| 52 | 52 | ||
| 53 | 53 | use super::Viewer; | |
| @@ -73,38 +73,6 @@ | |||
| 73 | 73 | /// The slot the chart's own markup mounts into. | |
| 74 | 74 | const CHART_SLOT: &str = "analytics-chart"; | |
| 75 | 75 | ||
| 76 | - | /// One window the selector offers. | |
| 77 | - | /// | |
| 78 | - | /// Named members rather than a tuple, for `policy`'s reason and | |
| 79 | - | /// `super::project_analytics`'s: a description names what it draws, and `.1` is | |
| 80 | - | /// not a name. | |
| 81 | - | struct Range { | |
| 82 | - | /// What the address carries. | |
| 83 | - | value: &'static str, | |
| 84 | - | /// What the heading calls it. | |
| 85 | - | label: &'static str, | |
| 86 | - | } | |
| 87 | - | ||
| 88 | - | /// The ranges offered, in the order the selector draws them. | |
| 89 | - | const RANGES: &[Range] = &[ | |
| 90 | - | Range { | |
| 91 | - | value: "7d", | |
| 92 | - | label: "Last 7 days", | |
| 93 | - | }, | |
| 94 | - | Range { | |
| 95 | - | value: "30d", | |
| 96 | - | label: "Last 30 days", | |
| 97 | - | }, | |
| 98 | - | Range { | |
| 99 | - | value: "90d", | |
| 100 | - | label: "Last 90 days", | |
| 101 | - | }, | |
| 102 | - | Range { | |
| 103 | - | value: "all", | |
| 104 | - | label: "All time", | |
| 105 | - | }, | |
| 106 | - | ]; | |
| 107 | - | ||
| 108 | 76 | /// One stat card, as the screen needs it. | |
| 109 | 77 | pub struct StatView { | |
| 110 | 78 | label: String, | |
| @@ -293,7 +261,7 @@ | |||
| 293 | 261 | shape pane(analytics: &Analytics) -> Node; | |
| 294 | 262 | ||
| 295 | 263 | region REGION as Pane { | |
| 296 | - | section range_heading(&analytics.range); | |
| 264 | + | section super::range_heading(&analytics.range); | |
| 297 | 265 | ||
| 298 | 266 | for chip in range_chips(&analytics.range) { | |
| 299 | 267 | include chip; | |
| @@ -318,45 +286,14 @@ | |||
| 318 | 286 | } | |
| 319 | 287 | } | |
| 320 | 288 | ||
| 321 | - | /// What the current range is called. | |
| 322 | - | fn range_heading(range: &str) -> &'static str { | |
| 323 | - | RANGES | |
| 324 | - | .iter() | |
| 325 | - | .find(|window| window.value == range) | |
| 326 | - | .map_or("All time", |window| window.label) | |
| 327 | - | } | |
| 328 | - | ||
| 329 | - | /// Whether this window is the one being shown. | |
| 330 | - | /// | |
| 331 | - | /// A supplier because a comparison is an expression, and the form admits none | |
| 332 | - | /// in an argument. It hands back a `bool`, which is the smallest thing that | |
| 333 | - | /// works and keeps it out of the population. | |
| 334 | - | fn is_shown(window: &Range, range: &str) -> bool { | |
| 335 | - | window.value == range | |
| 336 | - | } | |
| 337 | - | ||
| 338 | - | /// One range control. | |
| 339 | - | /// | |
| 340 | - | /// A supplier and not a member: `token` is a **setting** on a row and on a | |
| 341 | - | /// cell, so the name cannot also be a node member. `quasi_declare`'s | |
| 342 | - | /// `NODE_MEMBERS` records the ruling and `super::project_analytics::chip` is | |
| 343 | - | /// the same supplier for the project-level panel. | |
| 344 | - | fn chip(window: &Range, range: &str) -> Node { | |
| 345 | - | Node::token( | |
| 346 | - | Tag::chip( | |
| 347 | - | window.value, | |
| 348 | - | Action::get(PATH).carrying("range", window.value), | |
| 349 | - | ) | |
| 350 | - | .latched(is_shown(window, range)), | |
| 351 | - | ) | |
| 352 | - | } | |
| 353 | - | ||
| 354 | 289 | declare! { | |
| 355 | 290 | /// The range selector: one chip per range, the current one held down. | |
| 356 | 291 | shape range_chips(range: &str) -> Vec<Node>; | |
| 357 | 292 | ||
| 358 | - | for window in RANGES { | |
| 359 | - | include chip(window, range); | |
| 293 | + | for window in super::RANGES { | |
| 294 | + | chip window.value to get PATH carrying "range" window.value { | |
| 295 | + | latched super::is_shown(window, range); | |
| 296 | + | } | |
| 360 | 297 | } | |
| 361 | 298 | } | |
| 362 | 299 | ||
| @@ -514,7 +451,7 @@ | |||
| 514 | 451 | mod tests { | |
| 515 | 452 | use super::*; | |
| 516 | 453 | use quasi_axum::Serves; | |
| 517 | - | use quasi_router::Slot; | |
| 454 | + | use quasi_router::{Node, Slot}; | |
| 518 | 455 | ||
| 519 | 456 | fn analytics() -> Analytics { | |
| 520 | 457 | Analytics { | |
| @@ -591,8 +528,8 @@ | |||
| 591 | 528 | } | |
| 592 | 529 | // An unknown range falls back rather than leaving nothing selected, so | |
| 593 | 530 | // the heading and the selector cannot disagree about where the reader is. | |
| 594 | - | assert_eq!(range_heading("nonsense"), "All time"); | |
| 595 | - | assert_eq!(range_heading("7d"), "Last 7 days"); | |
| 531 | + | assert_eq!(super::super::range_heading("nonsense"), "All time"); | |
| 532 | + | assert_eq!(super::super::range_heading("7d"), "Last 7 days"); | |
| 596 | 533 | } | |
| 597 | 534 | ||
| 598 | 535 | #[test] |