max / makenotwork
- Co-Authored-By
- Claude Opus 5 (1M context) <noreply@anthropic.com>
- Claude-Session
- https://claude.ai/code/session_01P8ostB2UmZJGj5WjSHRSot
4 files changed,
+280 insertions,
-51 deletions
| @@ -48,14 +48,14 @@ | |||
| 48 | 48 | const EMPTY: &str = "git-repos-empty"; | |
| 49 | 49 | ||
| 50 | 50 | /// Everything the screen draws, resolved before it is drawn. | |
| 51 | - | struct Loaded { | |
| 51 | + | pub(crate) struct Loaded { | |
| 52 | 52 | owner: String, | |
| 53 | 53 | is_owner: bool, | |
| 54 | 54 | repos: Vec<Repo>, | |
| 55 | 55 | } | |
| 56 | 56 | ||
| 57 | 57 | /// One repository, as the listing shows it. | |
| 58 | - | struct Repo { | |
| 58 | + | pub(crate) struct Repo { | |
| 59 | 59 | name: String, | |
| 60 | 60 | description: String, | |
| 61 | 61 | /// `None` unless the reader owns the account and the repository is not | |
| @@ -87,6 +87,14 @@ | |||
| 87 | 87 | Ok(page_screen(&loaded).into()) | |
| 88 | 88 | } | |
| 89 | 89 | ||
| 90 | + | /// The one read this page makes, for the mount that serves it from a residual. | |
| 91 | + | pub(crate) fn reading( | |
| 92 | + | viewer: &super::Viewer, | |
| 93 | + | carried: &super::Carried, | |
| 94 | + | ) -> Result<Loaded, RouteError> { | |
| 95 | + | load(viewer, carried.capture("owner")?) | |
| 96 | + | } | |
| 97 | + | ||
| 90 | 98 | /// Resolve the account and the repositories this reader may see. | |
| 91 | 99 | fn load(viewer: &super::Viewer, owner: &str) -> Result<Loaded, RouteError> { | |
| 92 | 100 | let missing = || RouteError::not_found("no such account"); | |
| @@ -129,7 +137,7 @@ | |||
| 129 | 137 | ||
| 130 | 138 | declare! { | |
| 131 | 139 | /// The whole document: the title, the measure, the body. | |
| 132 | - | shape page_screen(loaded: &Loaded) -> Screen; | |
| 140 | + | pub(crate) shape page_screen(loaded: &Loaded) -> Screen; | |
| 133 | 141 | ||
| 134 | 142 | let heading = "{loaded.owner}'s Repositories"; | |
| 135 | 143 | ||
| @@ -137,11 +145,20 @@ | |||
| 137 | 145 | measured MEASURE; | |
| 138 | 146 | documented Document::default().classed(crate::shell::body_class(MEASURE, &[])); | |
| 139 | 147 | ||
| 140 | - | region PAGE_REGION as Pane { | |
| 141 | - | page heading.clone(); | |
| 142 | - | include empty(loaded.is_owner, &loaded.owner) when loaded.repos.is_empty(); | |
| 143 | - | include listing(loaded) unless loaded.repos.is_empty(); | |
| 144 | - | } | |
| 148 | + | include page_region(loaded); | |
| 149 | + | } | |
| 150 | + | } | |
| 151 | + | ||
| 152 | + | declare! { | |
| 153 | + | /// The page's one region, split out so it can be staged. | |
| 154 | + | #[staged] | |
| 155 | + | pub(crate) shape page_region(loaded: &Loaded) -> Slot; | |
| 156 | + | ||
| 157 | + | region PAGE_REGION as Pane { | |
| 158 | + | page "{loaded.owner}'s Repositories"; | |
| 159 | + | include empty(loaded.is_owner, &loaded.owner) when loaded.repos.is_empty(); | |
| 160 | + | include owner_listing(loaded) when loaded.is_owner and not loaded.repos.is_empty(); | |
| 161 | + | include listing(loaded) unless loaded.is_owner or loaded.repos.is_empty(); | |
| 145 | 162 | } | |
| 146 | 163 | } | |
| 147 | 164 | ||
| @@ -152,24 +169,34 @@ | |||
| 152 | 169 | /// The shipped template made the same split and it is worth keeping: `git | |
| 153 | 170 | /// remote add` is not advice a stranger can take. | |
| 154 | 171 | /// | |
| 155 | - | /// `own_prose` rather than `rich`, and the interpolation is the reason to | |
| 156 | - | /// say why: `owner` is a `Username`, which `validate_username` restricts to | |
| 157 | - | /// letters, digits and underscores, so it cannot carry markup into a source | |
| 158 | - | /// the renderer no longer hardens. A value that could would want `rich` | |
| 159 | - | /// instead, whatever else is in the string. | |
| 172 | + | /// # The prose is written here rather than passed to `own_prose` | |
| 173 | + | /// | |
| 174 | + | /// `own_prose` takes the markdown **source** as one parameter, so a caller | |
| 175 | + | /// that formats the string first hands a staged shape one sentinel where | |
| 176 | + | /// the whole document should be: the derivation renders `<p>ZQH...HQZ</p>` | |
| 177 | + | /// and the filler writes the request's markdown into that paragraph | |
| 178 | + | /// unparsed. See [`super::own_prose`], where the rule is. | |
| 179 | + | /// | |
| 180 | + | /// Said here, the account's name is a hole **inside** the source, so the | |
| 181 | + | /// derivation parses the fence with a sentinel in it and the residual holds | |
| 182 | + | /// docengine's `<pre><code>` around a gap. Same markup as before, and the | |
| 183 | + | /// two lines that are `own_prose`'s body are inlined with it. | |
| 184 | + | #[staged] | |
| 160 | 185 | shape empty(is_owner: bool, owner: &str) -> Slot; | |
| 161 | 186 | ||
| 162 | 187 | region EMPTY as Group { | |
| 163 | 188 | empty "No repositories yet."; | |
| 164 | - | include super::own_prose( | |
| 165 | - | "Push a new repository:\n\n```\ngit remote add origin \ | |
| 166 | - | https://makenot.work/git/{owner}/my-repo.git\ngit push -u origin main\n```" | |
| 167 | - | ) when is_owner; | |
| 189 | + | rich "Push a new repository:\n\n```\ngit remote add origin \ | |
| 190 | + | https://makenot.work/git/{owner}/my-repo.git\ngit push -u origin main\n```" | |
| 191 | + | when is_owner | |
| 192 | + | { | |
| 193 | + | trust quasi_router::Trust::Trusted; | |
| 194 | + | } | |
| 168 | 195 | } | |
| 169 | 196 | } | |
| 170 | 197 | ||
| 171 | 198 | declare! { | |
| 172 | - | /// The repositories, as a table. | |
| 199 | + | /// The repositories, as a table, in the shape this reader is owed. | |
| 173 | 200 | /// | |
| 174 | 201 | /// The template drew a `<ul>` of two-line entries. As a table the | |
| 175 | 202 | /// description says which column carries the identity and which can be | |
| @@ -177,11 +204,64 @@ | |||
| 177 | 204 | /// however it wraps. | |
| 178 | 205 | /// | |
| 179 | 206 | /// Nobody but the owner is shown a visibility column, because for everybody | |
| 180 | - | /// else every row in it would say the same word. The visibility cell names | |
| 181 | - | /// its column rather than counting to it, so the guard above is the only | |
| 182 | - | /// place ownership is decided: built by position, this row and that column | |
| 183 | - | /// list were two conditionals that had to agree, and nothing checked that | |
| 184 | - | /// they did. | |
| 207 | + | /// else every row in it would say the same word. | |
| 208 | + | /// | |
| 209 | + | /// # One question, asked once, and the seam is what insisted | |
| 210 | + | /// | |
| 211 | + | /// This was one table with a guarded `Visibility` column and a guarded | |
| 212 | + | /// visibility cell, both reading ownership. That is two conditionals that | |
| 213 | + | /// have to agree, and the module used to note that nothing checked they | |
| 214 | + | /// did. Something does now: **a derivation renders combinations the screen | |
| 215 | + | /// cannot produce.** It varies one guard at a time to read each span, so it | |
| 216 | + | /// renders the column absent with the cell present, and a cell naming a | |
| 217 | + | /// column that is not there is a cell silently lost -- which the renderer | |
| 218 | + | /// panics on rather than dropping. | |
| 219 | + | /// | |
| 220 | + | /// So the rule the seam imposes is that a screen's guards are independent, | |
| 221 | + | /// and two that must agree are one question written twice. Here the | |
| 222 | + | /// question is ownership and it is asked in [`page_region`], which picks | |
| 223 | + | /// the table rather than patching one. The two column lists repeat two | |
| 224 | + | /// lines and buy back a coupling nothing was holding. | |
| 225 | + | #[staged] | |
| 226 | + | shape owner_listing(loaded: &Loaded) -> Node; | |
| 227 | + | ||
| 228 | + | table { | |
| 229 | + | column "Repository" { | |
| 230 | + | width Content; | |
| 231 | + | priority Essential; | |
| 232 | + | } | |
| 233 | + | column "Description" { | |
| 234 | + | width Fill; | |
| 235 | + | } | |
| 236 | + | column "Visibility" { | |
| 237 | + | width Content; | |
| 238 | + | } | |
| 239 | + | ||
| 240 | + | for repo in loaded.repos.iter() { | |
| 241 | + | cells { | |
| 242 | + | cell at "Repository" repo.name.clone(); | |
| 243 | + | cell at "Description" repo.description.clone(); | |
| 244 | + | // Absent for a public repository, which is the owner's common | |
| 245 | + | // case and the reason the column is theirs alone. The guard is | |
| 246 | + | // on the cell rather than on the token because a token is a | |
| 247 | + | // setting on the cell, and a staged shape cannot guard one: a | |
| 248 | + | // setting renders in its container's opening tag, so the branch | |
| 249 | + | // it makes is not where the declaration wrote it. | |
| 250 | + | cell at "Visibility" "" when repo.visibility.is_some() { | |
| 251 | + | token Tag::badge(repo.visibility_label()); | |
| 252 | + | } | |
| 253 | + | ||
| 254 | + | activate to get "/git/{loaded.owner}/{repo.name}" navigating; | |
| 255 | + | } | |
| 256 | + | } | |
| 257 | + | } | |
| 258 | + | } | |
| 259 | + | ||
| 260 | + | declare! { | |
| 261 | + | /// The same listing for everybody else, which is the public set with no | |
| 262 | + | /// visibility to report. See [`owner_listing`] for why this is a second | |
| 263 | + | /// shape rather than a guard. | |
| 264 | + | #[staged] | |
| 185 | 265 | shape listing(loaded: &Loaded) -> Node; | |
| 186 | 266 | ||
| 187 | 267 | table { | |
| @@ -192,17 +272,11 @@ | |||
| 192 | 272 | column "Description" { | |
| 193 | 273 | width Fill; | |
| 194 | 274 | } | |
| 195 | - | column "Visibility" when loaded.is_owner { | |
| 196 | - | width Content; | |
| 197 | - | } | |
| 198 | 275 | ||
| 199 | 276 | for repo in loaded.repos.iter() { | |
| 200 | 277 | cells { | |
| 201 | 278 | cell at "Repository" repo.name.clone(); | |
| 202 | 279 | cell at "Description" repo.description.clone(); | |
| 203 | - | cell at "Visibility" "" when repo.visibility.is_some() { | |
| 204 | - | token Tag::badge(repo.visibility_label()); | |
| 205 | - | } | |
| 206 | 280 | ||
| 207 | 281 | activate to get "/git/{loaded.owner}/{repo.name}" navigating; | |
| 208 | 282 | } | |
| @@ -220,23 +294,30 @@ | |||
| 220 | 294 | ))) | |
| 221 | 295 | } | |
| 222 | 296 | ||
| 297 | + | /// An account as the tests draw it, with `count` repositories. | |
| 298 | + | /// | |
| 299 | + | /// Module-level rather than inside `mod tests` because `quasi::residuals` needs | |
| 300 | + | /// one too, and `Loaded` is this module's own type. Test-only. | |
| 301 | + | #[cfg(test)] | |
| 302 | + | pub(crate) fn sample(count: usize, is_owner: bool) -> Loaded { | |
| 303 | + | Loaded { | |
| 304 | + | owner: "ada".into(), | |
| 305 | + | is_owner, | |
| 306 | + | repos: (0..count) | |
| 307 | + | .map(|n| Repo { | |
| 308 | + | name: format!("repo{n}"), | |
| 309 | + | description: format!("Number {n}"), | |
| 310 | + | visibility: (is_owner && n == 0).then(|| "private".to_string()), | |
| 311 | + | }) | |
| 312 | + | .collect(), | |
| 313 | + | } | |
| 314 | + | } | |
| 315 | + | ||
| 223 | 316 | #[cfg(test)] | |
| 224 | 317 | mod tests { | |
| 225 | 318 | use super::*; | |
| 226 | 319 | ||
| 227 | - | fn loaded(count: usize, is_owner: bool) -> Loaded { | |
| 228 | - | Loaded { | |
| 229 | - | owner: "ada".into(), | |
| 230 | - | is_owner, | |
| 231 | - | repos: (0..count) | |
| 232 | - | .map(|n| Repo { | |
| 233 | - | name: format!("repo{n}"), | |
| 234 | - | description: format!("Number {n}"), | |
| 235 | - | visibility: (is_owner && n == 0).then(|| "private".to_string()), | |
| 236 | - | }) | |
| 237 | - | .collect(), | |
| 238 | - | } | |
| 239 | - | } | |
| 320 | + | use super::sample as loaded; | |
| 240 | 321 | ||
| 241 | 322 | fn html(loaded: &Loaded) -> String { | |
| 242 | 323 | use quasi_axum::Serves as _; |
| @@ -771,15 +771,29 @@ | |||
| 771 | 771 | ), | |
| 772 | 772 | )), | |
| 773 | 773 | ), | |
| 774 | + | // The first screen on the seam behind a rate limiter, and the layer | |
| 775 | + | // goes on here exactly as it does for the adapted mounts beside it: | |
| 776 | + | // `served_document_mount` answers an `axum::Router` too. | |
| 774 | 777 | ( | |
| 775 | 778 | git_repos::PATH, | |
| 776 | - | public_document_mount(app, git_repos::PATH, git_repos::screen, git_repos::renderer) | |
| 777 | - | .layer(tower_governor::GovernorLayer::new( | |
| 778 | - | crate::helpers::rate_limiter_ms( | |
| 779 | - | crate::constants::GIT_BROWSE_RATE_LIMIT_MS, | |
| 780 | - | crate::constants::GIT_BROWSE_RATE_LIMIT_BURST, | |
| 781 | - | ), | |
| 782 | - | )), | |
| 779 | + | served_document_mount( | |
| 780 | + | app, | |
| 781 | + | git_repos::PATH, | |
| 782 | + | git_repos::renderer, | |
| 783 | + | |viewer, carried| { | |
| 784 | + | let loaded = git_repos::reading(viewer, carried)?; | |
| 785 | + | Ok(Served { | |
| 786 | + | screen: git_repos::page_screen(&loaded), | |
| 787 | + | markup: git_repos::page_region_serve(&residuals::GIT_REPOS, &loaded).into(), | |
| 788 | + | }) | |
| 789 | + | }, | |
| 790 | + | ) | |
| 791 | + | .layer(tower_governor::GovernorLayer::new( | |
| 792 | + | crate::helpers::rate_limiter_ms( | |
| 793 | + | crate::constants::GIT_BROWSE_RATE_LIMIT_MS, | |
| 794 | + | crate::constants::GIT_BROWSE_RATE_LIMIT_BURST, | |
| 795 | + | ), | |
| 796 | + | )), | |
| 783 | 797 | ), | |
| 784 | 798 | ] | |
| 785 | 799 | } | |
| @@ -1113,6 +1127,31 @@ | |||
| 1113 | 1127 | /// be a literal in this repository, or interpolated from a value that | |
| 1114 | 1128 | /// cannot carry markup. A creator's description reaching a screen through | |
| 1115 | 1129 | /// the database is `Node::rich` however well-behaved it has been. | |
| 1130 | + | /// # Constant, and deliberately not staged | |
| 1131 | + | /// | |
| 1132 | + | /// An `include` of this with a literal folds to the `#[constant]` shim and | |
| 1133 | + | /// is derived once, which is what `/policy` uses six times over. | |
| 1134 | + | /// | |
| 1135 | + | /// `#[staged]` was tried and is wrong. The parameter is the **markdown | |
| 1136 | + | /// source**, so staging replaces the whole document with one sentinel: the | |
| 1137 | + | /// derivation renders `<p>ZQH...HQZ</p>` and the filler writes the request's | |
| 1138 | + | /// markdown into that paragraph unparsed. `git_repos` is where it showed -- | |
| 1139 | + | /// a fenced code block came back as a literal ``` inside a `<p>`. | |
| 1140 | + | /// | |
| 1141 | + | /// # A staged screen that wants a value inside prose writes the `rich` | |
| 1142 | + | /// | |
| 1143 | + | /// Not because prose cannot be staged, but because the hole has to be in | |
| 1144 | + | /// the **source** rather than around it. `rich "... [name]({base}) ..."` | |
| 1145 | + | /// written inside the staged shape puts a sentinel in the markdown, so | |
| 1146 | + | /// docengine parses it and the residual holds the markup with the value's | |
| 1147 | + | /// own gap in it; the same sentence formatted first and handed here does | |
| 1148 | + | /// not. `git_repos` and `forum_memberships` are the two sites, and each | |
| 1149 | + | /// inlines this shape's two-line body with a note saying so. | |
| 1150 | + | /// | |
| 1151 | + | /// What a staged `rich` then requires is that the value cannot change how | |
| 1152 | + | /// the source parses -- which is the rule this shape already carries for a | |
| 1153 | + | /// different reason. A value that could alter the parse could carry markup, | |
| 1154 | + | /// and such a value wants `Node::rich`, untrusted and not on the seam. | |
| 1116 | 1155 | #[must_use] | |
| 1117 | 1156 | #[constant] | |
| 1118 | 1157 | pub shape own_prose(source: impl Into<String>) -> Node; |
| @@ -78,6 +78,9 @@ | |||
| 78 | 78 | ("EXPORT_PORTAL", |plan| { | |
| 79 | 79 | Node::Region(super::export_portal::page_region_staged(plan)) | |
| 80 | 80 | }), | |
| 81 | + | ("GIT_REPOS", |plan| { | |
| 82 | + | Node::Region(super::git_repos::page_region_staged(plan)) | |
| 83 | + | }), | |
| 81 | 84 | ] | |
| 82 | 85 | } | |
| 83 | 86 | ||
| @@ -520,6 +523,52 @@ | |||
| 520 | 523 | assert_eq!(loops(EXPORT_PORTAL.ops()), 0, "the cards are unrolled"); | |
| 521 | 524 | } | |
| 522 | 525 | ||
| 526 | + | /// The repository listing's residual fills to what the renderer builds. | |
| 527 | + | /// | |
| 528 | + | /// `/git/{owner}` is the first residual holding a **token**: a repository's | |
| 529 | + | /// visibility is a `Tag`, which has no sentinel of its own, so the tag is | |
| 530 | + | /// built with one in it and the residual carries the tag's markup as a | |
| 531 | + | /// literal around a hole (quasi-declare's structured slots). | |
| 532 | + | /// | |
| 533 | + | /// Four shapes, and the two axes are independent. Owner and visitor differ | |
| 534 | + | /// by a whole column as well as by the badges, and empty and populated by | |
| 535 | + | /// which of the two branches is placed at all. | |
| 536 | + | #[test] | |
| 537 | + | fn the_git_repos_residual_fills_to_what_the_renderer_builds() { | |
| 538 | + | use quasi_axum::Serves as _; | |
| 539 | + | ||
| 540 | + | for count in [0, 1, 3] { | |
| 541 | + | for is_owner in [true, false] { | |
| 542 | + | let loaded = crate::quasi::git_repos::sample(count, is_owner); | |
| 543 | + | let filled = crate::quasi::git_repos::page_region_serve(&GIT_REPOS, &loaded); | |
| 544 | + | let rendered = Webview::new() | |
| 545 | + | .fragment(&Node::Region(crate::quasi::git_repos::page_region(&loaded))); | |
| 546 | + | ||
| 547 | + | assert_eq!(filled, rendered, "count={count} is_owner={is_owner}"); | |
| 548 | + | } | |
| 549 | + | } | |
| 550 | + | } | |
| 551 | + | ||
| 552 | + | /// The badge's markup is compiled and only its word is a hole. | |
| 553 | + | /// | |
| 554 | + | /// What says the structured slot landed. A `Tag` staged as a value fails to | |
| 555 | + | /// compile; a `Tag` staged through leaves `class="tag"` in the residual's | |
| 556 | + | /// literals with the visibility word written in per row. Finding the markup | |
| 557 | + | /// here is finding that the second thing happened. | |
| 558 | + | #[test] | |
| 559 | + | fn the_git_repos_residual_compiles_its_badges() { | |
| 560 | + | let compiled = format!("{:?}", GIT_REPOS.ops()); | |
| 561 | + | ||
| 562 | + | assert!( | |
| 563 | + | compiled.contains("badge"), | |
| 564 | + | "the badge markup is not compiled", | |
| 565 | + | ); | |
| 566 | + | assert!( | |
| 567 | + | !compiled.contains("private"), | |
| 568 | + | "a visibility word was baked into a literal", | |
| 569 | + | ); | |
| 570 | + | } | |
| 571 | + | ||
| 523 | 572 | /// Every screen the roster names is checked above. | |
| 524 | 573 | /// | |
| 525 | 574 | /// The gap this closes is the one a table opens: a screen added to | |
| @@ -534,8 +583,8 @@ | |||
| 534 | 583 | #[test] | |
| 535 | 584 | fn every_screen_on_the_seam_is_checked() { | |
| 536 | 585 | /// Screens with their own filling test: `/use-cases`, `/fan-plus`, | |
| 537 | - | /// `/c/{username}/{slug}`, `/dashboard/export`. | |
| 538 | - | const HOLED: usize = 4; | |
| 586 | + | /// `/c/{username}/{slug}`, `/dashboard/export`, `/git/{owner}`. | |
| 587 | + | const HOLED: usize = 5; | |
| 539 | 588 | ||
| 540 | 589 | assert_eq!( | |
| 541 | 590 | roster().len(), |
| @@ -151,4 +151,64 @@ | |||
| 151 | 151 | ])), | |
| 152 | 152 | ::quasi_router::stage::Op::Lit(::std::borrow::Cow::Borrowed("<div class=\"anchored\" id=\"export-cards-anchored\" data-menu=\"anchored\" hidden></div></section><h2 class=\"heading\">About Your Data</h2><p class=\"text\">Your data belongs to you. These exports contain everything we store about your account and content. If you're planning to delete your account, we recommend downloading your data first.</p><div class=\"anchored\" id=\"export-anchored\" data-menu=\"anchored\" hidden></div></div>")), | |
| 153 | 153 | ]); | |
| 154 | + | ||
| 155 | + | pub static GIT_REPOS: ::quasi_router::stage::Residual = | |
| 156 | + | ::quasi_router::stage::Residual::compiled(&[ | |
| 157 | + | ::quasi_router::stage::Op::Lit(::std::borrow::Cow::Borrowed("<div id=\"git-repos\" class=\"region pane\"><h1 class=\"heading\">")), | |
| 158 | + | ::quasi_router::stage::Op::Hole { scope: 0, id: 0 }, | |
| 159 | + | ::quasi_router::stage::Op::Lit(::std::borrow::Cow::Borrowed("'s Repositories</h1>")), | |
| 160 | + | ::quasi_router::stage::Op::Branch(::std::borrow::Cow::Borrowed(&[ | |
| 161 | + | ::quasi_router::stage::Op::Lit(::std::borrow::Cow::Borrowed("<section id=\"git-repos-empty\" class=\"region group\"><div class=\"placeholder\" data-state=\"empty\" role=\"status\" aria-live=\"polite\"><p class=\"placeholder-text\">No repositories yet.</p></div>")), | |
| 162 | + | ::quasi_router::stage::Op::Branch(::std::borrow::Cow::Borrowed(&[ | |
| 163 | + | ::quasi_router::stage::Op::Lit(::std::borrow::Cow::Borrowed("<div class=\"rich\" data-disable-scripting><p>Push a new repository:</p>\n<pre><code>git remote add origin https://makenot.work/git/")), | |
| 164 | + | ::quasi_router::stage::Op::Hole { scope: 1, id: 0 }, | |
| 165 | + | ::quasi_router::stage::Op::Lit(::std::borrow::Cow::Borrowed("/my-repo.git\ngit push -u origin main\n</code></pre>\n</div>")), | |
| 166 | + | ||
| 167 | + | ])), | |
| 168 | + | ::quasi_router::stage::Op::Lit(::std::borrow::Cow::Borrowed("<div class=\"anchored\" id=\"git-repos-empty-anchored\" data-menu=\"anchored\" hidden></div></section>")), | |
| 169 | + | ||
| 170 | + | ])), | |
| 171 | + | ::quasi_router::stage::Op::Branch(::std::borrow::Cow::Borrowed(&[ | |
| 172 | + | ::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-Repository cell-content cell-keeps\">Repository</span><span role=\"columnheader\" class=\"table-heading col-Description cell-fill cell-drops-next\">Description</span><span role=\"columnheader\" class=\"table-heading col-Visibility cell-content cell-drops-next\">Visibility</span></div>")), | |
| 173 | + | ::quasi_router::stage::Op::Loop(::std::borrow::Cow::Borrowed(&[ | |
| 174 | + | ::quasi_router::stage::Op::Lit(::std::borrow::Cow::Borrowed("<div role=\"row\" class=\"table-row\" data-row href=\"/git/")), | |
| 175 | + | ::quasi_router::stage::Op::Hole { scope: 2, id: 3 }, | |
| 176 | + | ::quasi_router::stage::Op::Lit(::std::borrow::Cow::Borrowed("/")), | |
| 177 | + | ::quasi_router::stage::Op::Hole { scope: 2, id: 4 }, | |
| 178 | + | ::quasi_router::stage::Op::Lit(::std::borrow::Cow::Borrowed("\"><div class=\"cell col-Repository cell-content cell-keeps cell-value\">")), | |
| 179 | + | ::quasi_router::stage::Op::Hole { scope: 2, id: 0 }, | |
| 180 | + | ::quasi_router::stage::Op::Lit(::std::borrow::Cow::Borrowed("</div><div class=\"cell col-Description cell-fill cell-drops-next cell-value\">")), | |
| 181 | + | ::quasi_router::stage::Op::Hole { scope: 2, id: 1 }, | |
| 182 | + | ::quasi_router::stage::Op::Lit(::std::borrow::Cow::Borrowed("</div><div class=\"cell col-Visibility cell-content cell-drops-next\">")), | |
| 183 | + | ::quasi_router::stage::Op::Branch(::std::borrow::Cow::Borrowed(&[ | |
| 184 | + | ::quasi_router::stage::Op::Lit(::std::borrow::Cow::Borrowed("<span class=\"cell-tokens\"><span class=\"badge\">")), | |
| 185 | + | ::quasi_router::stage::Op::Hole { scope: 2, id: 2 }, | |
| 186 | + | ::quasi_router::stage::Op::Lit(::std::borrow::Cow::Borrowed("</span></span>")), | |
| 187 | + | ||
| 188 | + | ])), | |
| 189 | + | ::quasi_router::stage::Op::Lit(::std::borrow::Cow::Borrowed("</div></div>")), | |
| 190 | + | ||
| 191 | + | ])), | |
| 192 | + | ::quasi_router::stage::Op::Lit(::std::borrow::Cow::Borrowed("</div>")), | |
| 193 | + | ||
| 194 | + | ])), | |
| 195 | + | ::quasi_router::stage::Op::Branch(::std::borrow::Cow::Borrowed(&[ | |
| 196 | + | ::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-Repository cell-content cell-keeps\">Repository</span><span role=\"columnheader\" class=\"table-heading col-Description cell-fill cell-drops-next\">Description</span></div>")), | |
| 197 | + | ::quasi_router::stage::Op::Loop(::std::borrow::Cow::Borrowed(&[ | |
| 198 | + | ::quasi_router::stage::Op::Lit(::std::borrow::Cow::Borrowed("<div role=\"row\" class=\"table-row\" data-row href=\"/git/")), | |
| 199 | + | ::quasi_router::stage::Op::Hole { scope: 3, id: 2 }, | |
| 200 | + | ::quasi_router::stage::Op::Lit(::std::borrow::Cow::Borrowed("/")), | |
| 201 | + | ::quasi_router::stage::Op::Hole { scope: 3, id: 3 }, | |
| 202 | + | ::quasi_router::stage::Op::Lit(::std::borrow::Cow::Borrowed("\"><div class=\"cell col-Repository cell-content cell-keeps cell-value\">")), | |
| 203 | + | ::quasi_router::stage::Op::Hole { scope: 3, id: 0 }, | |
| 204 | + | ::quasi_router::stage::Op::Lit(::std::borrow::Cow::Borrowed("</div><div class=\"cell col-Description cell-fill cell-drops-next cell-value\">")), | |
| 205 | + | ::quasi_router::stage::Op::Hole { scope: 3, id: 1 }, | |
| 206 | + | ::quasi_router::stage::Op::Lit(::std::borrow::Cow::Borrowed("</div></div>")), | |
| 207 | + | ||
| 208 | + | ])), | |
| 209 | + | ::quasi_router::stage::Op::Lit(::std::borrow::Cow::Borrowed("</div>")), | |
| 210 | + | ||
| 211 | + | ])), | |
| 212 | + | ::quasi_router::stage::Op::Lit(::std::borrow::Cow::Borrowed("<div class=\"anchored\" id=\"git-repos-anchored\" data-menu=\"anchored\" hidden></div></div>")), | |
| 213 | + | ]); | |
| 154 | 214 |