Skip to main content

max / makenotwork

Describe the dashboard settings sub-nav Shape 2 step 4 (6b24f2df). user_settings.html is one printed value now, and src/quasi/settings_tabs.rs holds the six sections. This is the strip that did not look like one: a nav of anchor links rather than .tabs[role=tablist], which is why a grep for the tablist role never found it. Behaviourally it is the folder semantic exactly, and the four lines of tab-user-settings.js existed to move the is-selected class a described strip moves for free. It is the first whole file Shape 2 deletes. tab-user-settings.js goes, with its script tag, the setSettingsSectionBtn wrapper in actions-tabs.js, and six data-action sites (147 to 141). Two of the six sections are described screens, so the library strip's branch is back after two steps without it: SSH Keys and Forums name the region their answer replaces and the strip must not override it. Both constants said settings-body, the single pane six sections shared, and there is no single pane now. Each moved to the frame that is its own, and both are public so the strip draws the frame from the same constant the screen answers into. Their two seals read the described strip instead of the template they used to read, the same way the forums test already read the library strip. dispatch.ts's refresh-profile verb followed settings-body to settings-profile. One reader-visible change. The nav was a column beside the content on a wide viewport and a wrapped row of links below the breakpoint; described, it is the renderer's tab strip on every viewport, which is the narrow look everywhere. That is the Shape 2 ruling applied rather than bent, the same as the overflow menu in step 1: the strip is described and how it is drawn is the renderer's. RegionKind::TabGroup's doc prose says "with tabs above", which reads like a constraint and is not one; that is a renderer's habit written into a doc comment and it should be corrected there. HIGH_WATER 124 to 117. Only one of those seven is this step's; the other six had already gone and nobody lowered the seal behind them, which is what a ratchet that only refuses to grow will always allow. Said so on the constant.
Co-Authored-By
Claude Opus 5 (1M context) <noreply@anthropic.com>
Author: Max Johnson <me@maxj.phd> · 2026-08-19 15:48 UTC
Signed with PGP, not checked
Commit: 2877f60911d90f655f6d7581af1a1bd07fae8a51
Parent: 614154f
11 files changed, +353 insertions, -89 deletions
@@ -157,11 +157,6 @@
157 157 mediaDeleteFile(id, this.dataset.filename);
158 158 };
159 159
160 - // --- user_settings.html ---
161 - window.setSettingsSectionBtn = function () {
162 - setSettingsSection(this);
163 - };
164 -
165 160 // --- item_details.html ---
166 161 window.onSearchTagsInput = function () {
167 162 searchTags(this.value);
@@ -36,7 +36,14 @@
36 36 /// the typed core module's 12 bridged globals became visible, and htmx's one
37 37 /// vendored `window.htmx =` stopped counting. 113 + 12 - 1 = 124. The surface
38 38 /// did not change that day; what this test can see did.
39 - const HIGH_WATER: usize = 124;
39 + ///
40 + /// 117 on 2026-08-19, ratcheted while describing the settings sub-nav
41 + /// (`6b24f2df` step 4). Only one of those seven is that step's own -- the
42 + /// `setSettingsSectionBtn` wrapper, deleted with `tab-user-settings.js`. The
43 + /// other six had already gone and nobody lowered the seal behind them, which is
44 + /// what a ratchet that only refuses to grow will always let happen. Measure
45 + /// before trusting this number as a progress figure.
46 + const HIGH_WATER: usize = 117;
40 47
41 48 /// Every file whose global assignments count: the legacy scripts and the typed
42 49 /// modules that replaced them.
@@ -53,8 +53,12 @@
53 53 /// The address the settings section answers.
54 54 pub const SETTINGS_PATH: &str = "/dashboard/tabs/forums";
55 55
56 - /// The region the settings nav targets. The same pane `ssh_keys` replaces.
57 - const SETTINGS_REGION: &str = "settings-body";
56 + /// The region the settings strip draws for this section.
57 + ///
58 + /// Was `settings-body`, the single pane the hand-written sub-nav swapped into.
59 + /// `6b24f2df` step 4 gave each section a frame; `quasi::settings_tabs` draws
60 + /// this one from this constant.
61 + pub const SETTINGS_REGION: &str = "settings-forums";
58 62
59 63 /// How long to wait on Multithreaded before giving up, matching both Askama
60 64 /// handlers.
@@ -290,9 +294,23 @@
290 294 "{library}"
291 295 );
292 296
293 - let settings = include_str!("../../templates/partials/tabs/user_settings.html");
294 - assert!(settings.contains(&format!("hx-get=\"{SETTINGS_PATH}\"")));
295 - assert!(settings.contains(&format!("hx-target=\"#{SETTINGS_REGION}\"")));
297 + // Read off the described strip since `6b24f2df` step 4, the same way the
298 + // library half above is. The hand-written nav this used to read is gone.
299 + let settings = crate::quasi::settings_tabs::html(
300 + &crate::config::QuasiScreens::default(),
301 + "",
302 + true,
303 + true,
304 + true,
305 + );
306 + assert!(
307 + settings.contains(&format!("hx-get=\"{SETTINGS_PATH}\"")),
308 + "{settings}"
309 + );
310 + assert!(
311 + settings.contains(&format!("id=\"{SETTINGS_REGION}\"")),
312 + "{settings}"
313 + );
296 314 }
297 315
298 316 #[test]
@@ -37,6 +37,7 @@
37 37 pub mod library_contacts;
38 38 pub mod library_tabs;
39 39 pub mod project_tabs;
40 + pub mod settings_tabs;
40 41 pub mod ssh_keys;
41 42 pub mod user_analytics;
42 43 pub mod widgets;
@@ -55,12 +55,14 @@
55 55 /// The address this screen answers, and the one the Askama route gives up.
56 56 pub const PATH: &str = "/dashboard/tabs/ssh-keys";
57 57
58 - /// The region the answer replaces: the settings pane the tab nav targets.
58 + /// The region the answer replaces: this section's own frame in the settings
59 + /// strip.
59 60 ///
60 - /// The nav's own `hx-target` says the same thing. Naming it here is what lets
61 - /// the router say what it changed rather than leaving the client to infer it
62 - /// from which link was clicked, and the two agreeing is checked below.
63 - const REGION: &str = "settings-body";
61 + /// Was `settings-body`, the single pane six sections shared while the sub-nav
62 + /// was hand-written. `6b24f2df` step 4 described that nav, so each section has a
63 + /// frame of its own and this names the one that is ours. `quasi::settings_tabs`
64 + /// draws the frame from this constant, and its tests assert the two agree.
65 + pub const REGION: &str = "settings-ssh-keys";
64 66
65 67 /// The address removing a key calls, relative to this screen's own nest.
66 68 const REMOVE_KEY: &str = "/keys/{id}";
@@ -425,16 +427,25 @@
425 427 }
426 428
427 429 #[test]
428 - fn the_region_matches_what_the_tab_nav_targets() {
430 + fn the_region_matches_what_the_strip_draws() {
429 431 // The router says what it changed, through HX-Retarget. If this and the
430 - // template's hx-target ever disagree the tab swaps into nothing, and
431 - // that failure is invisible to every other test.
432 - let nav = include_str!("../../templates/partials/tabs/user_settings.html");
433 - assert!(
434 - nav.contains(&format!("hx-target=\"#{REGION}\"")),
435 - "the settings nav targets #{REGION}"
432 + // frame the settings strip draws ever disagree the section swaps into
433 + // nothing, and that failure is invisible to every other test.
434 + //
435 + // Read off the described strip since `6b24f2df` step 4. It was the
436 + // hand-written nav's `hx-target`, which no longer exists.
437 + let nav = crate::quasi::settings_tabs::html(
438 + &crate::config::QuasiScreens::default(),
439 + "",
440 + true,
441 + true,
442 + true,
436 443 );
437 - assert!(nav.contains("hx-get=\"/dashboard/tabs/ssh-keys\""));
444 + assert!(
445 + nav.contains(&format!("id=\"{REGION}\"")),
446 + "the settings strip draws #{REGION}:\n{nav}"
447 + );
448 + assert!(nav.contains(&format!("hx-get=\"{PATH}\"")), "{nav}");
438 449 }
439 450
440 451 #[test]
@@ -202,17 +202,13 @@
202 202 #[derive(Template)]
203 203 #[template(path = "partials/tabs/user_settings.html")]
204 204 pub struct UserSettingsTabTemplate {
205 - pub user: User,
206 - pub custom_links: Vec<CustomLinkWithId>,
207 - pub feed_url: String,
208 - pub can_create_projects: bool,
209 - pub custom_domain: Option<CustomDomainInfo>,
210 - pub has_media: bool,
211 - pub git_enabled: bool,
212 - pub has_mt_memberships: bool,
213 - /// Built-in theme choices for the profile theme picker (Tier 0). Shared with
214 - /// the included `user_profile.html` partial.
215 - pub theme_options: Vec<crate::theming::ThemeOption>,
205 + /// The sub-nav and its sections, described rather than written out here.
206 + ///
207 + /// `6b24f2df` step 4. Built by `crate::quasi::settings_tabs`, which needs
208 + /// the shown section's markup and the three membership tests. The eight
209 + /// fields that were here fed the included `user_profile.html`, which the
210 + /// handler now renders itself as `UserProfileTabTemplate` and hands over.
211 + pub sections: String,
216 212 }
217 213
218 214 /// One row of the "mail you will receive regardless" list.