Skip to main content

max / audiofiles

Declare the licence section, and stop handing a container back The section took the settings body and returned it, which is the one shape a declaration is refused. So it becomes what goingson's settings panes already are: a Vec<Node> the screen splices, and the splice is the caller's `extend`. The read is hoisted into `Standing`, whose `key` and `machine` are total because a `given` evaluates both arms' holes whether or not either is placed. The heading stays in the description rather than moving into the read: which of the two words is on screen is the one thing this section says out loud. No production earned. The heading and the key line are value dispatches, the machine id and its Copy act are a loop over an Option, and Deactivate is a guarded act with a tone and a confirmation.
Author: Max Johnson <me@maxj.phd> · 2026-09-04 20:37 UTC
Signed with PGP, not checked
Commit: 448ac5f4c0046c72e4bb663229d906ed83b3b676
Parent: a3fa684
2 files changed, +50 insertions, -33 deletions
@@ -47,8 +47,8 @@
47 47 //! [`Act::confirm`]: quasi_router::Act::confirm
48 48 //! [`Licence::machine`]: super::Licence::machine
49 49
50 - use quasi_router::layout::Tone;
51 - use quasi_router::{Act, Action, Node, Request, Response, RouteError, Router, Slot};
50 + use quasi_declare::declare;
51 + use quasi_router::{Request, Response, RouteError, Router};
52 52
53 53 use super::Panels;
54 54
@@ -92,43 +92,60 @@
92 92 Ok(super::settings::screen(state)?.into())
93 93 }
94 94
95 - /// The whole section, added to the settings body.
96 - pub(super) fn section(body: Slot, state: &Panels<'_>) -> Slot {
97 - let held = state.licence.masked();
95 + /// The licence, as the section draws it.
96 + ///
97 + /// `key` and `machine` are total: a `given` evaluates both its arms' holes
98 + /// whether or not either is placed (R9), so the reader answers the absent case
99 + /// with nothing rather than with a panic.
100 + pub(super) struct Standing {
101 + /// Whether a key is held here at all.
102 + held: bool,
103 + /// The masked key, or nothing when none is held.
104 + key: String,
105 + /// This machine's id as much of it as is worth reading, when the host has
106 + /// one. The whole value is what Copy carries; see [`shorten`].
107 + machine: Option<String>,
108 + }
98 109
99 - // The heading is the state: "audiofiles Pro" when a key is held, "License"
100 - // when none is. The shipped section did this and it is the one place the
101 - // app says out loud that a key changes what it is.
102 - let mut body = body.with(Node::section(if held.is_some() {
103 - "audiofiles Pro"
104 - } else {
105 - "License"
106 - }));
110 + /// What the section draws, read off the app.
111 + pub(super) fn read(state: &Panels<'_>) -> Standing {
112 + let key = state.licence.masked();
113 + Standing {
114 + held: key.is_some(),
115 + key: key.unwrap_or_default(),
116 + machine: state.licence.machine().map(|machine| shorten(&machine)),
117 + }
118 + }
107 119
108 - body = match &held {
109 - Some(masked) => body.with(Node::text(format!("Key: {masked}"))),
110 - None => body.with(Node::text(
111 - "No license key. audiofiles is fully functional without one.",
112 - )),
120 + declare! {
121 + /// The whole section, spliced into the settings body.
122 + ///
123 + /// The heading is the state: "audiofiles Pro" when a key is held, "License"
124 + /// when none is. The shipped section did this and it is the one place the
125 + /// app says out loud that a key changes what it is.
126 + pub(super) shape section(licence: &Standing) -> Vec<Node>;
127 +
128 + let heading = given licence.held {
129 + true -> "audiofiles Pro",
130 + otherwise -> "License",
131 + };
132 + let says = given licence.held {
133 + true -> "Key: {licence.key}",
134 + otherwise -> "No license key. audiofiles is fully functional without one.",
113 135 };
114 136
115 - if let Some(machine) = state.licence.machine() {
116 - body = body
117 - .with(Node::text(format!("Machine: {}", shorten(&machine))))
118 - .with(Node::Act(Act::new(
119 - "Copy machine id",
120 - Action::post("/settings/licence/machine/copy"),
121 - )));
137 + section heading;
138 + text says;
139 +
140 + for machine in licence.machine.iter() {
141 + text "Machine: {machine}";
142 + act "Copy machine id" to post "/settings/licence/machine/copy";
122 143 }
123 144
124 - if held.is_some() {
125 - body = body.with(Node::Act(
126 - Act::new("Deactivate", Action::post("/settings/licence/deactivate"))
127 - .tone(Tone::Danger)
128 - .confirm(GIVES_UP),
129 - ));
145 + act "Deactivate" to post "/settings/licence/deactivate" when licence.held {
146 + tone Danger;
147 + confirm GIVES_UP;
130 148 }
131 - body
132 149 }
133 150
134 151 /// A machine id as much of it as is worth reading.
@@ -195,7 +195,7 @@
195 195
196 196 body = super::storage::section(body, state);
197 197 body = super::trash::section(body, state);
198 - body = super::licence::section(body, state);
198 + body = body.extend(super::licence::section(&super::licence::read(state)));
199 199
200 200 // The classifier's door. Max ruled it a window of its own; what stays here
201 201 // is one act, so the thing is findable from Settings.