Skip to main content

max / makenotwork

Put both contact panes on the residual seam Eleven of seventeen. Both are tables behind guards and neither needed anything new: the panel mount landed with the forum panes, and these are what it was shaped for. The library's pane is two tables, each with its own heading and its own empty state, plus a third state where neither is placed and one line says so. Every combination is filled and compared, because a residual that had lost one table's branch would still pass on the shapes where that table is absent. `export_act::act` gains `#[constant]`, which is what lets the buyer pane's Export control be derived once rather than built per request: the call's arguments are literals, so a staged `include` reaches the shim.
Co-Authored-By
Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session
https://claude.ai/code/session_01P8ostB2UmZJGj5WjSHRSot
Author: Max Johnson <me@maxj.phd> · 2026-09-08 02:15 UTC
Commit: 1fa59d074643bc5317ee8e906f270de5943ebcb7
Parent: 4621d89
6 files changed, +258 insertions, -43 deletions
@@ -53,10 +53,13 @@
53 53 ///
54 54 /// The Payments tab leaves an empty div here and fills it on `revealed`, so
55 55 /// this region is the whole of what the section is, not a pane it shares.
56 - const REGION: &str = "contacts-section";
56 + /// `pub` so the seam's mount can name it: a panel served from a residual
57 + /// answers no `Response`, so the region it retargets is stated at the mount
58 + /// rather than by the router. `library_contacts::REGION` is `pub` already.
59 + pub const REGION: &str = "contacts-section";
57 60
58 61 /// One buyer who chose to share their email, as the screen needs it.
59 - pub struct BuyerView {
62 + pub(crate) struct BuyerView {
60 63 username: String,
61 64 email: String,
62 65 purchases: String,
@@ -64,8 +67,9 @@
64 67 last_purchase: String,
65 68 }
66 69
67 - /// The section.
68 - pub fn screen(viewer: &Viewer, _request: Request) -> Result<Response, RouteError> {
70 + /// The one read this section makes, for the mount that serves it from a
71 + /// residual.
72 + pub(crate) fn reading(viewer: &Viewer) -> Result<Vec<BuyerView>, RouteError> {
69 73 let contacts = viewer
70 74 .block_on(db::transactions::get_seller_contacts(
71 75 &viewer.app.db,
@@ -74,7 +78,7 @@
74 78 .map_err(|_| RouteError::internal("your contacts could not be read"))?;
75 79
76 80 let currency = viewer.reader()?.settlement_currency;
77 - let buyers: Vec<BuyerView> = contacts
81 + Ok(contacts
78 82 .into_iter()
79 83 .map(|contact| BuyerView {
80 84 username: contact.username,
@@ -83,14 +87,18 @@
83 87 spent: crate::formatting::format_revenue(contact.total_spent_cents, currency),
84 88 last_purchase: contact.last_purchase_at.format("%b %-d, %Y").to_string(),
85 89 })
86 - .collect();
90 + .collect())
91 + }
87 92
88 - Ok(Response::fragment(REGION, pane(&buyers)))
93 + /// The section.
94 + pub fn screen(viewer: &Viewer, _request: Request) -> Result<Response, RouteError> {
95 + Ok(Response::fragment(REGION, pane(&reading(viewer)?)))
89 96 }
90 97
91 98 declare! {
92 99 /// Everything inside the section.
93 - shape pane(buyers: &[BuyerView]) -> Node;
100 + #[staged]
101 + pub(crate) shape pane(buyers: &[BuyerView]) -> Node;
94 102
95 103 region REGION as Pane {
96 104 section "Shared Contacts ({buyers.len()})";
@@ -123,6 +131,7 @@
123 131 /// bytes are not, and `layout::Awaiting` takes a measurement rather than a
124 132 /// stand-in for one.
125 133 #[must_use]
134 + #[constant]
126 135 shape export() -> Node;
127 136
128 137 // Through `export_act` rather than spelled again here. This screen had the
@@ -141,6 +150,7 @@
141 150 /// and every buyer contributes the same five cells, so there is no seam for
142 151 /// a heading and a cell to drift across. Nothing is paged either, so no
143 152 /// `more`: this is a whole set the handler already counted.
153 + #[staged]
144 154 shape table(buyers: &[BuyerView]) -> Node;
145 155
146 156 table {
@@ -187,6 +197,21 @@
187 197 Webview::new().with_shell(viewer.shell())
188 198 }
189 199
200 + /// One buyer as the tests draw it.
201 + ///
202 + /// Module-level rather than inside `mod tests` because `quasi::residuals` needs
203 + /// one too, and `BuyerView` is this module's own type. Test-only.
204 + #[cfg(test)]
205 + pub(crate) fn sample(username: &str) -> BuyerView {
206 + BuyerView {
207 + username: username.into(),
208 + email: format!("{username}@example.com"),
209 + purchases: "3".into(),
210 + spent: "$42.00".into(),
211 + last_purchase: "Aug 10, 2026".into(),
212 + }
213 + }
214 +
190 215 #[cfg(test)]
191 216 mod tests {
192 217 use quasi_axum::Serves;
@@ -194,15 +219,7 @@
194 219
195 220 use super::*;
196 221
197 - fn buyer(username: &str) -> BuyerView {
198 - BuyerView {
199 - username: username.into(),
200 - email: format!("{username}@example.com"),
201 - purchases: "3".into(),
202 - spent: "$42.00".into(),
203 - last_purchase: "Aug 10, 2026".into(),
204 - }
205 - }
222 + use super::sample as buyer;
206 223
207 224 fn render(node: &Node) -> String {
208 225 Webview::new().fragment(node)
@@ -54,6 +54,7 @@
54 54 /// call site and the byte count never is, and `layout::Awaiting` takes a
55 55 /// measurement rather than a stand-in for one.
56 56 #[must_use]
57 + #[constant]
57 58 pub shape act(route: &str, filename: &str) -> Node;
58 59
59 60 include control("Export CSV", route, filename);
@@ -51,7 +51,7 @@
51 51 pub const WRITES: &[(Method, &str, super::Screen)] = &[(Method::Delete, REVOKE, revoke)];
52 52
53 53 /// One buyer who chose to share their email, as the screen needs it.
54 - pub struct BuyerView {
54 + pub(crate) struct BuyerView {
55 55 username: String,
56 56 email: String,
57 57 purchases: String,
@@ -60,14 +60,18 @@
60 60 }
61 61
62 62 /// One creator this reader has shared their own email with.
63 - pub struct SharedView {
63 + pub(crate) struct SharedView {
64 64 seller_id: String,
65 65 username: String,
66 66 name: String,
67 67 }
68 68
69 69 /// The tab.
70 - pub fn screen(viewer: &Viewer, _request: Request) -> Result<Response, RouteError> {
70 + /// The one read this tab makes, for the mount that serves it from a residual.
71 + ///
72 + /// Both lists together, because the pane is one region and reading them apart
73 + /// would be two round trips where the screen has one.
74 + pub(crate) fn reading(viewer: &Viewer) -> Result<(Vec<BuyerView>, Vec<SharedView>), RouteError> {
71 75 let user_id = viewer.reader()?.id;
72 76
73 77 let shared = viewer
@@ -119,6 +123,11 @@
119 123 })
120 124 .collect();
121 125
126 + Ok((buyers, shared))
127 + }
128 +
129 + pub fn screen(viewer: &Viewer, _request: Request) -> Result<Response, RouteError> {
130 + let (buyers, shared) = reading(viewer)?;
122 131 Ok(Response::fragment(REGION, pane(&buyers, &shared)))
123 132 }
124 133
@@ -160,7 +169,8 @@
160 169 /// two conditions and returns early. Said as guards, the two halves each
161 170 /// carry their own, and the both-empty case is the one where neither is
162 171 /// placed and the empty line is.
163 - shape pane(buyers: &[BuyerView], shared: &[SharedView]) -> Node;
172 + #[staged]
173 + pub(crate) shape pane(buyers: &[BuyerView], shared: &[SharedView]) -> Node;
164 174
165 175 region REGION as Pane {
166 176 empty "No contacts yet." when buyers.is_empty() and shared.is_empty();
@@ -184,6 +194,7 @@
184 194 /// declaration and every buyer contributes the same five cells, so naming
185 195 /// each column would buy nothing a reader cannot already see. Nothing is
186 196 /// paged, so no `more`: this is a whole set the handler already counted.
197 + #[staged]
187 198 shape buyers_table(buyers: &[BuyerView]) -> Node;
188 199
189 200 table {
@@ -238,6 +249,7 @@
238 249 /// and the pairing would be less obvious than the order already makes it.
239 250 /// Both cells are drawn for every creator. Nothing is paged, so no `more`:
240 251 /// this is a whole set the handler already counted.
252 + #[staged]
241 253 shape shared_table(shared: &[SharedView]) -> Node;
242 254
243 255 table {
@@ -282,28 +294,37 @@
282 294 Webview::new().with_shell(viewer.shell())
283 295 }
284 296
297 + /// One buyer as the tests draw it.
298 + ///
299 + /// Module-level rather than inside `mod tests` because `quasi::residuals` needs
300 + /// one too, and these are this module's own types. Test-only.
301 + #[cfg(test)]
302 + pub(crate) fn sample_buyer(username: &str) -> BuyerView {
303 + BuyerView {
304 + username: username.into(),
305 + email: format!("{username}@example.com"),
306 + purchases: "3".into(),
307 + spent: "$42.00".into(),
308 + last_purchase: "Aug 10, 2026".into(),
309 + }
310 + }
311 +
312 + /// One creator this reader shares an address with, as the tests draw it.
313 + #[cfg(test)]
314 + pub(crate) fn sample_creator(id: &str, username: &str, name: &str) -> SharedView {
315 + SharedView {
316 + seller_id: id.into(),
317 + username: username.into(),
318 + name: name.into(),
319 + }
320 + }
321 +
285 322 #[cfg(test)]
286 323 mod tests {
287 324 use super::*;
288 325 use quasi_axum::Serves;
289 326
290 - fn buyer(username: &str) -> BuyerView {
291 - BuyerView {
292 - username: username.into(),
293 - email: format!("{username}@example.com"),
294 - purchases: "3".into(),
295 - spent: "$42.00".into(),
296 - last_purchase: "Aug 10, 2026".into(),
297 - }
298 - }
299 -
300 - fn creator(id: &str, username: &str, name: &str) -> SharedView {
301 - SharedView {
302 - seller_id: id.into(),
303 - username: username.into(),
304 - name: name.into(),
305 - }
306 - }
327 + use super::{sample_buyer as buyer, sample_creator as creator};
307 328
308 329 fn render(node: &quasi_router::Node) -> String {
309 330 Webview::new().fragment(node)
@@ -520,11 +520,19 @@
520 520 ),
521 521 (
522 522 library_contacts::PATH,
523 - mount(
523 + served_panel_mount(
524 524 app,
525 - library_contacts::screen,
525 + library_contacts::REGION,
526 526 library_contacts::WRITES,
527 527 library_contacts::renderer,
528 + |viewer, _| {
529 + let (buyers, shared) = library_contacts::reading(viewer)?;
530 + Ok(library_contacts::pane_serve(
531 + &residuals::LIBRARY_CONTACTS,
532 + &buyers,
533 + &shared,
534 + ))
535 + },
528 536 ),
529 537 ),
530 538 // One module, two screens: the library tab and the settings section are
@@ -548,7 +556,18 @@
548 556 ),
549 557 (
550 558 buyer_contacts::PATH,
551 - mount(app, buyer_contacts::screen, &[], buyer_contacts::renderer),
559 + served_panel_mount(
560 + app,
561 + buyer_contacts::REGION,
562 + &[],
563 + buyer_contacts::renderer,
564 + |viewer, _| {
565 + Ok(buyer_contacts::pane_serve(
566 + &residuals::BUYER_CONTACTS,
567 + &buyer_contacts::reading(viewer)?,
568 + ))
569 + },
570 + ),
552 571 ),
553 572 (
554 573 user_analytics::PATH,
@@ -89,6 +89,12 @@
89 89 ("FORUMS_SETTINGS", |plan| {
90 90 super::forum_memberships::settings_pane_staged(plan)
91 91 }),
92 + ("BUYER_CONTACTS", |plan| {
93 + super::buyer_contacts::pane_staged(plan)
94 + }),
95 + ("LIBRARY_CONTACTS", |plan| {
96 + super::library_contacts::pane_staged(plan)
97 + }),
92 98 ]
93 99 }
94 100
@@ -619,6 +625,52 @@
619 625 }
620 626 }
621 627
628 + /// The two contact panes fill to what the renderer builds.
629 + ///
630 + /// Both are tables behind guards, and the library's is two of them: buyers
631 + /// and shared-with, each with its own heading and its own empty state, and
632 + /// a third state where neither is placed and one line says so. Every
633 + /// combination is filled, because a residual that had lost one table's
634 + /// branch would still pass on the shapes where that table is absent.
635 + #[test]
636 + fn the_contact_residuals_fill_to_what_the_renderer_builds() {
637 + use quasi_axum::Serves as _;
638 +
639 + for count in [0, 1, 3] {
640 + let buyers: Vec<_> = (0..count)
641 + .map(|n| crate::quasi::buyer_contacts::sample(&format!("buyer{n}")))
642 + .collect();
643 + assert_eq!(
644 + crate::quasi::buyer_contacts::pane_serve(&BUYER_CONTACTS, &buyers),
645 + Webview::new().fragment(&crate::quasi::buyer_contacts::pane(&buyers)),
646 + "buyer contacts, {count} buyers",
647 + );
648 + }
649 +
650 + for buyers in [0, 2] {
651 + for shared in [0, 2] {
652 + let held: Vec<_> = (0..buyers)
653 + .map(|n| crate::quasi::library_contacts::sample_buyer(&format!("buyer{n}")))
654 + .collect();
655 + let with: Vec<_> = (0..shared)
656 + .map(|n| {
657 + crate::quasi::library_contacts::sample_creator(
658 + &format!("{n}"),
659 + &format!("creator{n}"),
660 + &format!("Creator {n}"),
661 + )
662 + })
663 + .collect();
664 +
665 + assert_eq!(
666 + crate::quasi::library_contacts::pane_serve(&LIBRARY_CONTACTS, &held, &with),
667 + Webview::new().fragment(&crate::quasi::library_contacts::pane(&held, &with)),
668 + "library contacts, {buyers} buyers and {shared} shared",
669 + );
670 + }
671 + }
672 + }
673 +
622 674 /// Every screen the roster names is checked above.
623 675 ///
624 676 /// The gap this closes is the one a table opens: a screen added to
@@ -634,8 +686,8 @@
634 686 fn every_screen_on_the_seam_is_checked() {
635 687 /// Screens with their own filling test: `/use-cases`, `/fan-plus`,
636 688 /// `/c/{username}/{slug}`, `/dashboard/export`, `/git/{owner}`, and the
637 - /// two forum panes.
638 - const HOLED: usize = 7;
689 + /// two forum panes, and the two contact panes.
690 + const HOLED: usize = 9;
639 691
640 692 assert_eq!(
641 693 roster().len(),
@@ -284,4 +284,109 @@
284 284 ])),
285 285 ::quasi_router::stage::Op::Lit(::std::borrow::Cow::Borrowed("<div class=\"anchored\" id=\"settings-forums-anchored\" data-menu=\"anchored\" hidden></div></div>")),
286 286 ]);
287 +
288 + pub static BUYER_CONTACTS: ::quasi_router::stage::Residual =
289 + ::quasi_router::stage::Residual::compiled(&[
290 + ::quasi_router::stage::Op::Lit(::std::borrow::Cow::Borrowed("<div id=\"contacts-section\" class=\"region pane\"><h2 class=\"heading\">Shared Contacts (")),
291 + ::quasi_router::stage::Op::Hole { scope: 0, id: 0 },
292 + ::quasi_router::stage::Op::Lit(::std::borrow::Cow::Borrowed(")</h2><p class=\"text\">Buyers who opted to share their email at checkout. They can revoke sharing from their library.</p>")),
293 + ::quasi_router::stage::Op::Branch(::std::borrow::Cow::Borrowed(&[
294 + ::quasi_router::stage::Op::Lit(::std::borrow::Cow::Borrowed("<div class=\"placeholder\" data-state=\"empty\" role=\"status\" aria-live=\"polite\"><p class=\"placeholder-text\">No shared contacts yet. When buyers opt to share their email at checkout, they will appear here.</p></div>")),
295 +
296 + ])),
297 + ::quasi_router::stage::Op::Branch(::std::borrow::Cow::Borrowed(&[
298 + ::quasi_router::stage::Op::Lit(::std::borrow::Cow::Borrowed("<button type=\"button\" class=\"button\" data-act data-saves=\"contacts.csv\" hx-post=\"/api/export/contacts\" hx-swap=\"outerMorph\" hx-disable=\"this\" data-awaiting=\"indeterminate\">Export CSV</button>")),
299 +
300 + ])),
301 + ::quasi_router::stage::Op::Branch(::std::borrow::Cow::Borrowed(&[
302 + ::quasi_router::stage::Op::Lit(::std::borrow::Cow::Borrowed("<div role=\"table\" class=\"table\"><div role=\"row\" class=\"table-head\"><span role=\"columnheader\" class=\"table-heading col-Username cell-content cell-keeps\">Username</span><span role=\"columnheader\" class=\"table-heading col-Email cell-fill cell-keeps\">Email</span><span role=\"columnheader\" class=\"table-heading col-Purchases cell-content cell-drops-next\">Purchases</span><span role=\"columnheader\" class=\"table-heading col-Total-Spent cell-content cell-drops-next\">Total Spent</span><span role=\"columnheader\" class=\"table-heading col-Last-Purchase cell-content cell-drops-first\">Last Purchase</span></div>")),
303 + ::quasi_router::stage::Op::Loop(::std::borrow::Cow::Borrowed(&[
304 + ::quasi_router::stage::Op::Lit(::std::borrow::Cow::Borrowed("<div role=\"row\" class=\"table-row\" data-row><div class=\"cell col-Username cell-content cell-keeps\"><a class=\"cell-link\" data-act href=\"/u/")),
305 + ::quasi_router::stage::Op::Hole { scope: 1, id: 1 },
306 + ::quasi_router::stage::Op::Lit(::std::borrow::Cow::Borrowed("\">")),
307 + ::quasi_router::stage::Op::Hole { scope: 1, id: 0 },
308 + ::quasi_router::stage::Op::Lit(::std::borrow::Cow::Borrowed("</a></div><div class=\"cell col-Email cell-fill cell-keeps cell-value\">")),
309 + ::quasi_router::stage::Op::Hole { scope: 1, id: 2 },
310 + ::quasi_router::stage::Op::Lit(::std::borrow::Cow::Borrowed("</div><div class=\"cell col-Purchases cell-content cell-drops-next cell-value\">")),
311 + ::quasi_router::stage::Op::Hole { scope: 1, id: 3 },
312 + ::quasi_router::stage::Op::Lit(::std::borrow::Cow::Borrowed("</div><div class=\"cell col-Total-Spent cell-content cell-drops-next cell-value\">")),
313 + ::quasi_router::stage::Op::Hole { scope: 1, id: 4 },
314 + ::quasi_router::stage::Op::Lit(::std::borrow::Cow::Borrowed("</div><div class=\"cell col-Last-Purchase cell-content cell-drops-first cell-value\">")),
315 + ::quasi_router::stage::Op::Hole { scope: 1, id: 5 },
316 + ::quasi_router::stage::Op::Lit(::std::borrow::Cow::Borrowed("</div></div>")),
317 +
318 + ])),
319 + ::quasi_router::stage::Op::Lit(::std::borrow::Cow::Borrowed("</div>")),
320 +
321 + ])),
322 + ::quasi_router::stage::Op::Lit(::std::borrow::Cow::Borrowed("<div class=\"anchored\" id=\"contacts-section-anchored\" data-menu=\"anchored\" hidden></div></div>")),
323 + ]);
324 +
325 + pub static LIBRARY_CONTACTS: ::quasi_router::stage::Residual =
326 + ::quasi_router::stage::Residual::compiled(&[
327 + ::quasi_router::stage::Op::Lit(::std::borrow::Cow::Borrowed("<div id=\"library-contacts\" class=\"region pane\">")),
328 + ::quasi_router::stage::Op::Branch(::std::borrow::Cow::Borrowed(&[
329 + ::quasi_router::stage::Op::Lit(::std::borrow::Cow::Borrowed("<div class=\"placeholder\" data-state=\"empty\" role=\"status\" aria-live=\"polite\"><p class=\"placeholder-text\">No contacts yet.</p></div>")),
330 +
331 + ])),
332 + ::quasi_router::stage::Op::Branch(::std::borrow::Cow::Borrowed(&[
333 + ::quasi_router::stage::Op::Lit(::std::borrow::Cow::Borrowed("<h2 class=\"heading\">Your Buyers (")),
334 + ::quasi_router::stage::Op::Hole { scope: 0, id: 0 },
335 + ::quasi_router::stage::Op::Lit(::std::borrow::Cow::Borrowed(")</h2>")),
336 +
337 + ])),
338 + ::quasi_router::stage::Op::Branch(::std::borrow::Cow::Borrowed(&[
339 + ::quasi_router::stage::Op::Lit(::std::borrow::Cow::Borrowed("<p class=\"text\">Buyers who opted to share their email with you at purchase time.</p>")),
340 +
341 + ])),
342 + ::quasi_router::stage::Op::Branch(::std::borrow::Cow::Borrowed(&[
343 + ::quasi_router::stage::Op::Lit(::std::borrow::Cow::Borrowed("<div role=\"table\" class=\"table\"><div role=\"row\" class=\"table-head\"><span role=\"columnheader\" class=\"table-heading col-Username cell-content cell-keeps\">Username</span><span role=\"columnheader\" class=\"table-heading col-Email cell-fill cell-keeps\">Email</span><span role=\"columnheader\" class=\"table-heading col-Purchases cell-content cell-drops-next\">Purchases</span><span role=\"columnheader\" class=\"table-heading col-Total-Spent cell-content cell-drops-next\">Total Spent</span><span role=\"columnheader\" class=\"table-heading col-Last-Purchase cell-content cell-drops-first\">Last Purchase</span></div>")),
344 + ::quasi_router::stage::Op::Loop(::std::borrow::Cow::Borrowed(&[
345 + ::quasi_router::stage::Op::Lit(::std::borrow::Cow::Borrowed("<div role=\"row\" class=\"table-row\" data-row><div class=\"cell col-Username cell-content cell-keeps\"><a class=\"cell-link\" data-act href=\"/u/")),
346 + ::quasi_router::stage::Op::Hole { scope: 1, id: 1 },
347 + ::quasi_router::stage::Op::Lit(::std::borrow::Cow::Borrowed("\">")),
348 + ::quasi_router::stage::Op::Hole { scope: 1, id: 0 },
349 + ::quasi_router::stage::Op::Lit(::std::borrow::Cow::Borrowed("</a></div><div class=\"cell col-Email cell-fill cell-keeps\"><a class=\"cell-link\" data-act href=\"mailto:")),
350 + ::quasi_router::stage::Op::Hole { scope: 1, id: 3 },
351 + ::quasi_router::stage::Op::Lit(::std::borrow::Cow::Borrowed("\" target=\"_blank\" rel=\"noopener noreferrer\">")),
352 + ::quasi_router::stage::Op::Hole { scope: 1, id: 2 },
353 + ::quasi_router::stage::Op::Lit(::std::borrow::Cow::Borrowed("</a></div><div class=\"cell col-Purchases cell-content cell-drops-next cell-value\">")),
354 + ::quasi_router::stage::Op::Hole { scope: 1, id: 4 },
355 + ::quasi_router::stage::Op::Lit(::std::borrow::Cow::Borrowed("</div><div class=\"cell col-Total-Spent cell-content cell-drops-next cell-value\">")),
356 + ::quasi_router::stage::Op::Hole { scope: 1, id: 5 },
357 + ::quasi_router::stage::Op::Lit(::std::borrow::Cow::Borrowed("</div><div class=\"cell col-Last-Purchase cell-content cell-drops-first cell-value\">")),
358 + ::quasi_router::stage::Op::Hole { scope: 1, id: 6 },
359 + ::quasi_router::stage::Op::Lit(::std::borrow::Cow::Borrowed("</div></div>")),
360 +
361 + ])),
362 + ::quasi_router::stage::Op::Lit(::std::borrow::Cow::Borrowed("</div>")),
363 +
364 + ])),
365 + ::quasi_router::stage::Op::Branch(::std::borrow::Cow::Borrowed(&[
366 + ::quasi_router::stage::Op::Lit(::std::borrow::Cow::Borrowed("<h2 class=\"heading\">Shared With</h2>")),
367 +
368 + ])),
369 + ::quasi_router::stage::Op::Branch(::std::borrow::Cow::Borrowed(&[
370 + ::quasi_router::stage::Op::Lit(::std::borrow::Cow::Borrowed("<p class=\"text\">You&#39;ve shared your email with these creators. You can revoke sharing at any time.</p>")),
371 +
372 + ])),
373 + ::quasi_router::stage::Op::Branch(::std::borrow::Cow::Borrowed(&[
374 + ::quasi_router::stage::Op::Lit(::std::borrow::Cow::Borrowed("<div role=\"table\" class=\"table\"><div role=\"row\" class=\"table-head\"><span role=\"columnheader\" class=\"table-heading col-Creator cell-fill cell-keeps\">Creator</span><span role=\"columnheader\" class=\"table-heading col- cell-content cell-keeps\"></span></div>")),
375 + ::quasi_router::stage::Op::Loop(::std::borrow::Cow::Borrowed(&[
376 + ::quasi_router::stage::Op::Lit(::std::borrow::Cow::Borrowed("<div role=\"row\" class=\"table-row\" data-row><div class=\"cell col-Creator cell-fill cell-keeps\"><a class=\"cell-link\" data-act href=\"/u/")),
377 + ::quasi_router::stage::Op::Hole { scope: 2, id: 1 },
378 + ::quasi_router::stage::Op::Lit(::std::borrow::Cow::Borrowed("\">")),
379 + ::quasi_router::stage::Op::Hole { scope: 2, id: 0 },
380 + ::quasi_router::stage::Op::Lit(::std::borrow::Cow::Borrowed("</a></div><div class=\"cell col- cell-content cell-keeps\"><span class=\"cell-actions\"><button type=\"button\" class=\"button\" data-tone=\"danger\" data-act hx-delete=\"/library/tabs/contacts/revoke/")),
381 + ::quasi_router::stage::Op::Hole { scope: 2, id: 2 },
382 + ::quasi_router::stage::Op::Lit(::std::borrow::Cow::Borrowed("\" hx-confirm=\"Revoke contact sharing with ")),
383 + ::quasi_router::stage::Op::Hole { scope: 2, id: 3 },
384 + ::quasi_router::stage::Op::Lit(::std::borrow::Cow::Borrowed("?\" hx-swap=\"outerMorph\" hx-disable=\"this\" data-awaiting=\"indeterminate\">Revoke</button></span></div></div>")),
385 +
386 + ])),
387 + ::quasi_router::stage::Op::Lit(::std::borrow::Cow::Borrowed("</div>")),
388 +
389 + ])),
390 + ::quasi_router::stage::Op::Lit(::std::borrow::Cow::Borrowed("<div class=\"anchored\" id=\"library-contacts-anchored\" data-menu=\"anchored\" hidden></div></div>")),
391 + ]);
287 392