Skip to main content

max / makenotwork

server: describe three tier-1 dashboard panels payout_summary, user_projects and user_support, the first batch off the S4 tab inventory measured today (wiki mnw-server-conversion-plan). Each deletes its template; the Askama half goes where it can. Not every tab converts the same way, and the test is the ETag. payout_summary becomes a mounted screen because its route does nothing but render. user_projects cannot: dashboard_tab_projects answers a conditional GET keyed on the user's cache generation, and quasi::mount has no way to say "304 if the generation has not moved". So it follows project_content instead, which stayed on its Askama handler for the same reason and never wrote down why: the handler and route survive, the template dies, and the panel is fill() inline plus fragment() addressed. user_support takes that shape for the other reason, being one of the four panels the page renders inline. A fill panel keeps screen: None in user_tabs::TABS, since Some(..) means a quasi route owns the address and the strip should leave the region alone. user_projects asserts quasi::PATHS lacks its address so the two cannot drift. Closes three of 736f45a5's 122 spinner-spelling sites: user_support's htmx-indicator, hx-indicator and id="support-spinner" are all Action::awaiting() now. Three things the descriptions say that the templates did not: a failed balance is Readiness::Failed rather than a hand-copied second card; an empty project list and its guide are alternatives rather than an empty role="list" above a box; and a disabled value="" option is not a choice, so the category select offers six rather than seven. Also deletes types::PayoutSummary, six orphaned CSS rules, and the four user-projects-* classes that had no rule anywhere to begin with. 179 lib tests green, fmt and clippy clean. The integration suite belongs on astra and was not run here.
Co-Authored-By
Claude Opus 5 (1M context) <noreply@anthropic.com>
Author: Max Johnson <me@maxj.phd> · 2026-08-26 21:55 UTC
Signed with PGP, not checked
Commit: 16d8cac4129460d0271a3e41eee048cbbead31f4
Parent: 0c7e23c
18 files changed, +895 insertions, -300 deletions
@@ -8527,23 +8527,6 @@
8527 8527 }
8528 8528 .stripe-actions-disconnect { margin-left: auto; }
8529 8529
8530 - .payout-stats-grid {
8531 - display: grid;
8532 - grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
8533 - gap: var(--gap-section);
8534 - margin-top: var(--gap-section);
8535 - padding-top: var(--gap-section);
8536 - border-top: 1px solid var(--border);
8537 - }
8538 -
8539 - .payouts-disabled-notice {
8540 - margin-top: var(--gap-section);
8541 - padding: var(--gap-section);
8542 - background: var(--warning);
8543 - color: var(--primary-light);
8544 - font-size: var(--text-note);
8545 - }
8546 -
8547 8530 .fee-breakdown {
8548 8531 display: grid;
8549 8532 grid-template-columns: 1fr 1fr;
@@ -10892,22 +10875,6 @@
10892 10875 USER DASHBOARD TABS (support / sessions / buyer contacts)
10893 10876 =========================================== */
10894 10877
10895 - /* Support tab (templates/partials/tabs/user_support.html). */
10896 - .support-tab .support-intro-meta {
10897 - margin-bottom: var(--gap-pane);
10898 - font-size: var(--text-note);
10899 - opacity: 0.8;
10900 - }
10901 - .support-tab .support-form { max-width: 600px; }
10902 - .support-tab .response-times {
10903 - margin-top: var(--gap-page);
10904 - font-size: var(--text-note);
10905 - opacity: 0.7;
10906 - }
10907 - .support-tab .response-times ul {
10908 - margin: var(--gap-bound) 0 0 var(--gap-pane);
10909 - }
10910 -
10911 10878 /* Sessions tab (templates/partials/tabs/user_sessions.html). */
10912 10879 .sessions-tab .sessions-table { width: 100%; }
10913 10880 .sessions-tab .device-cell {
@@ -47,6 +47,7 @@
47 47 pub mod library_contacts;
48 48 pub mod library_tabs;
49 49 pub mod media_picker;
50 + pub mod payout_summary;
50 51 pub mod project_content;
51 52 pub mod project_tabs;
52 53 pub mod rich_field;
@@ -54,6 +55,8 @@
54 55 pub mod ssh_keys;
55 56 pub mod upload_field;
56 57 pub mod user_analytics;
58 + pub mod user_projects;
59 + pub mod user_support;
57 60 pub mod user_tabs;
58 61 pub mod version_delete_act;
59 62 pub mod widgets;
@@ -222,6 +225,7 @@
222 225 library_contacts::PATH,
223 226 buyer_contacts::PATH,
224 227 user_analytics::PATH,
228 + payout_summary::PATH,
225 229 forum_memberships::LIBRARY_PATH,
226 230 forum_memberships::SETTINGS_PATH,
227 231 ];
@@ -238,6 +242,7 @@
238 242 library_contacts::SCREEN,
239 243 buyer_contacts::SCREEN,
240 244 user_analytics::SCREEN,
245 + payout_summary::SCREEN,
241 246 forum_memberships::LIBRARY_SCREEN,
242 247 forum_memberships::SETTINGS_SCREEN,
243 248 ];
@@ -283,6 +288,10 @@
283 288 user_analytics::PATH,
284 289 mount(app, user_analytics::screen, &[], user_analytics::renderer),
285 290 ),
291 + (
292 + payout_summary::PATH,
293 + mount(app, payout_summary::screen, &[], payout_summary::renderer),
294 + ),
286 295 (
287 296 forum_memberships::SETTINGS_PATH,
288 297 mount(
@@ -104,9 +104,12 @@
104 104 const TABS: &[Tab] = &[
105 105 Tab {
106 106 label: "Projects",
107 - panel: "user-projects",
107 + panel: super::user_projects::REGION,
108 108 route: "projects",
109 109 gate: Gate::Creator,
110 + // `None` although the panel is described: `screen` means "a quasi route
111 + // answers this address", and Projects is a fill on the Askama handler
112 + // that keeps the ETag. See `super::user_projects`.
110 113 screen: None,
111 114 },
112 115 Tab {
@@ -132,9 +135,11 @@
132 135 },
133 136 Tab {
134 137 label: "Support",
135 - panel: "user-support",
138 + panel: super::user_support::REGION,
136 139 route: "support",
137 140 gate: Gate::Always,
141 + // Described, but as a fill on the Askama handler rather than a quasi
142 + // route, so the strip still fetches it. See `super::user_support`.
138 143 screen: None,
139 144 },
140 145 ];
@@ -225,11 +225,9 @@
225 225 UserSettingsTabTemplate,
226 226 UserAccountTabTemplate,
227 227 UserPaymentsTabTemplate,
228 - UserProjectsTabTemplate,
229 228 UserCreatorTabTemplate,
230 229 ProjectOverviewTabTemplate,
231 230 ProjectAnalyticsTabTemplate,
232 - PayoutSummaryPartialTemplate,
233 231 ProjectSettingsTabTemplate,
234 232 ProjectCodeTabTemplate,
235 233 ProjectSubscriptionsTabTemplate,
@@ -258,7 +256,6 @@
258 256 // Media library
259 257 UserMediaTabTemplate,
260 258 // Support
261 - UserSupportTabTemplate,
262 259 // Collections
263 260 CollectionTemplate,
264 261 // Library tabs
@@ -8,9 +8,9 @@
8 8 use crate::types::{
9 9 AdminAppealRow, AdminHeldUploadRow, AdminReportRow, AdminUserRow, AdminWaitlistRow,
10 10 BlogPostDashboardRow, ChartBar, Collection, ContentItem, DiscoverItem, DiscoverProject,
11 - InviteCodeDisplay, Item, ItemSection, LicenseKeyRow, PayoutSummary, Project, ProjectCard,
12 - ProjectMemberRow, PromoCodeRow, SaleRow, SidebarView, StatCard, SubscriptionTier, SyncAppRow,
13 - TipReceived, Transaction, User, UserSubscription, Version, WaitlistEntry, WaveStats,
11 + InviteCodeDisplay, Item, ItemSection, LicenseKeyRow, Project, ProjectCard, ProjectMemberRow,
12 + PromoCodeRow, SaleRow, SidebarView, StatCard, SubscriptionTier, SyncAppRow, TipReceived,
13 + Transaction, User, UserSubscription, Version, WaitlistEntry, WaveStats,
14 14 };
15 15
16 16 use super::CsrfTokenOption;
@@ -340,14 +340,6 @@
340 340 pub can_create_projects: bool,
341 341 }
342 342
343 - /// Dashboard tab: user's projects list with create button.
344 - #[derive(Template)]
345 - #[template(path = "partials/tabs/user_projects.html")]
346 - pub struct UserProjectsTabTemplate {
347 - pub projects: Vec<ProjectCard>,
348 - pub can_create_projects: bool,
349 - }
350 -
351 343 /// Creator tab in the user dashboard showing invite/waitlist status.
352 344 #[derive(Template)]
353 345 #[template(path = "partials/tabs/user_creator.html")]
@@ -597,13 +589,6 @@
597 589 pub storage_display: String,
598 590 }
599 591
600 - /// Support tab in the user dashboard, submit a support ticket.
601 - #[derive(Template)]
602 - #[template(path = "partials/tabs/user_support.html")]
603 - pub struct UserSupportTabTemplate {
604 - pub email: String,
605 - }
606 -
607 592 // Library Tab Partials
608 593
609 594 /// Library purchases tab.
@@ -642,15 +627,6 @@
642 627 pub revenue: String,
643 628 }
644 629
645 - /// Stripe payout-summary card, HTMX-loaded into the Payments tab so the tab
646 - /// render never blocks on the Stripe balance round-trip (ultra-fuzz Run 11 Perf SER-2).
647 - #[derive(Template)]
648 - #[template(path = "partials/tabs/payout_summary.html")]
649 - pub struct PayoutSummaryPartialTemplate {
650 - pub payout_summary: Option<PayoutSummary>,
651 - pub stripe_payouts_enabled: bool,
652 - }
653 -
654 630 /// SSH keys list partial for HTMX updates.
655 631 #[derive(Template)]
656 632 #[template(path = "partials/ssh_keys_list.html")]
@@ -1,13 +1,6 @@
1 1 //! View types for money flows: payouts, transactions, tips, subscriptions,
2 2 //! license keys, promo codes, and sales.
3 3
4 - /// Payout summary derived from the Stripe Balance API.
5 - #[derive(Clone)]
6 - pub struct PayoutSummary {
7 - pub available: String,
8 - pub pending: String,
9 - }
10 -
11 4 /// A single bar in a revenue bar chart.
12 5 #[derive(Clone)]
13 6 pub struct ChartBar {
@@ -66,6 +66,11 @@
66 66 quasi::buyer_contacts::PATH,
67 67 "contacts-section",
68 68 ),
69 + (
70 + "payout_summary",
71 + quasi::payout_summary::PATH,
72 + quasi::payout_summary::REGION,
73 + ),
69 74 (
70 75 "user_analytics",
71 76 quasi::user_analytics::PATH,