max / goingson
- Co-Authored-By
- Claude Opus 5 (1M context) <noreply@anthropic.com>
5 files changed,
+100 insertions,
-64 deletions
| @@ -532,19 +532,25 @@ | |||
| 532 | 532 | ||
| 533 | 533 | /// Delete the task. | |
| 534 | 534 | /// | |
| 535 | - | /// # The sixth finding | |
| 535 | + | /// # The sixth finding, closed 2026-08-09 | |
| 536 | 536 | /// | |
| 537 | - | /// **Nothing describes going somewhere else.** Every answer replaces a region | |
| 537 | + | /// **Nothing described going somewhere else.** Every answer replaced a region | |
| 538 | 538 | /// or the screen in place. Deleting the thing a screen is about is the first | |
| 539 | - | /// case where the right answer is a *different* screen — the JS closes the | |
| 540 | - | /// drawer and leaves the list underneath — and the description has no way to | |
| 541 | - | /// say "show that address now". The projects and contacts writes never met this | |
| 539 | + | /// case where the right answer is a *different* address — the JS closes the | |
| 540 | + | /// drawer and leaves the list underneath — and the description had no way to | |
| 541 | + | /// say "show that address now". The projects and contacts writes never met it, | |
| 542 | 542 | /// because their answer was always the same screen re-read. | |
| 543 | 543 | /// | |
| 544 | - | /// So this answers with a screen saying the task is gone, which is honest and | |
| 545 | - | /// is not what the app should do. Filed as quasicoherent `80afd652` rather than | |
| 546 | - | /// on makeover-layout, because it is a fact about the response and not about | |
| 547 | - | /// what is on screen. | |
| 544 | + | /// This answered with a screen saying the task was gone, addressed by the id of | |
| 545 | + | /// the thing that no longer existed. quasicoherent `80afd652` closed that: | |
| 546 | + | /// [`Response`] is a struct now, carrying an [`Outcome`](quasi_router::Outcome) | |
| 547 | + | /// that can be a redirect, and a notice beside it. | |
| 548 | + | /// | |
| 549 | + | /// The notice is the second half and was its own finding (`a92ecb1e`). A | |
| 550 | + | /// redirect alone cannot say what happened, because the list it lands on looks | |
| 551 | + | /// the same whether a task was deleted or the user simply navigated. The two | |
| 552 | + | /// were filed apart and had to land together: one enum member could not be | |
| 553 | + | /// both. | |
| 548 | 554 | fn remove(state: &AppState, params: quasi_router::Params) -> Result<Response, RouteError> { | |
| 549 | 555 | let id = task_id(¶ms)?; | |
| 550 | 556 | let deleted = state | |
| @@ -554,12 +560,11 @@ | |||
| 554 | 560 | if !deleted { | |
| 555 | 561 | return Err(RouteError::not_found("no such task")); | |
| 556 | 562 | } | |
| 557 | - | Ok(Screen::list_detail("Task", false) | |
| 558 | - | .with(Slot::new("task-band", RegionKind::Band).with(Node::page("Task deleted"))) | |
| 559 | - | .with(Slot::new("task-overview", RegionKind::Pane).with(Node::text( | |
| 560 | - | "The task is gone. Nothing here can say where to go next; see the note on this handler.", | |
| 561 | - | ))) | |
| 562 | - | .into()) | |
| 563 | + | // Was a tombstone screen until 2026-08-09: a page saying the task was gone, | |
| 564 | + | // on a screen addressed by the id of the thing that no longer exists. That | |
| 565 | + | // was finding `80afd652`, and it was the honest description of a router | |
| 566 | + | // whose every answer was content. | |
| 567 | + | Ok(Response::goto(Action::get("/tasks")).toast(makeover_layout::Tone::Success, "Task deleted")) | |
| 563 | 568 | } | |
| 564 | 569 | ||
| 565 | 570 | /// Add a subtask. |
| @@ -9,6 +9,7 @@ | |||
| 9 | 9 | ||
| 10 | 10 | use goingson_core::{NewContact, NewContactEmail, NewSocialHandle}; | |
| 11 | 11 | use quasi_http::Render as _; | |
| 12 | + | use quasi_router::Outcome; | |
| 12 | 13 | use quasi_router::{Method, Params, Response}; | |
| 13 | 14 | ||
| 14 | 15 | use super::super::router; | |
| @@ -72,14 +73,14 @@ | |||
| 72 | 73 | } | |
| 73 | 74 | ||
| 74 | 75 | fn screen_html(response: Response) -> String { | |
| 75 | - | let Response::Screen(screen) = response else { | |
| 76 | + | let Outcome::Screen(screen) = (response).outcome else { | |
| 76 | 77 | panic!("the route answers with a screen"); | |
| 77 | 78 | }; | |
| 78 | 79 | quasi_webview::Webview::new().screen(&screen) | |
| 79 | 80 | } | |
| 80 | 81 | ||
| 81 | 82 | fn fragment_html(response: Response) -> String { | |
| 82 | - | let Response::Fragment { node, .. } = response else { | |
| 83 | + | let Outcome::Fragment { node, .. } = (response).outcome else { | |
| 83 | 84 | panic!("the route answers with a fragment"); | |
| 84 | 85 | }; | |
| 85 | 86 | quasi_webview::Webview::new().fragment(&node) |
| @@ -9,6 +9,7 @@ | |||
| 9 | 9 | ||
| 10 | 10 | use goingson_core::{NewProject, ProjectStatus, ProjectType}; | |
| 11 | 11 | use quasi_http::Render as _; | |
| 12 | + | use quasi_router::Outcome; | |
| 12 | 13 | use quasi_router::{Method, Params, Response}; | |
| 13 | 14 | ||
| 14 | 15 | use super::super::{protocol, router}; | |
| @@ -87,7 +88,7 @@ | |||
| 87 | 88 | #[tokio::test] | |
| 88 | 89 | async fn an_empty_database_says_so_rather_than_rendering_nothing() { | |
| 89 | 90 | let state = state().await; | |
| 90 | - | let Response::Screen(screen) = answer(&state, "/projects", Params::new()) else { | |
| 91 | + | let Outcome::Screen(screen) = answer(&state, "/projects", Params::new()).outcome else { | |
| 91 | 92 | panic!("the index answers with a screen"); | |
| 92 | 93 | }; | |
| 93 | 94 | let html = quasi_webview::Webview::new().screen(&screen); | |
| @@ -101,7 +102,7 @@ | |||
| 101 | 102 | add(&state, "Paused", ProjectStatus::OnHold); | |
| 102 | 103 | add(&state, "Finished", ProjectStatus::Completed); | |
| 103 | 104 | ||
| 104 | - | let Response::Screen(screen) = answer(&state, "/projects", Params::new()) else { | |
| 105 | + | let Outcome::Screen(screen) = answer(&state, "/projects", Params::new()).outcome else { | |
| 105 | 106 | panic!("a screen"); | |
| 106 | 107 | }; | |
| 107 | 108 | let html = quasi_webview::Webview::new().screen(&screen); | |
| @@ -120,7 +121,7 @@ | |||
| 120 | 121 | add(&state, "Finished", ProjectStatus::Completed); | |
| 121 | 122 | ||
| 122 | 123 | let shown = answer(&state, "/projects", Params::new().with("retired", "1")); | |
| 123 | - | let Response::Screen(screen) = shown else { | |
| 124 | + | let Outcome::Screen(screen) = shown.outcome else { | |
| 124 | 125 | panic!("a screen"); | |
| 125 | 126 | }; | |
| 126 | 127 | let html = quasi_webview::Webview::new().screen(&screen); | |
| @@ -138,7 +139,7 @@ | |||
| 138 | 139 | // reflowed to change one pane. | |
| 139 | 140 | assert_eq!(response.target(), Some("projects-grid")); | |
| 140 | 141 | ||
| 141 | - | let Response::Fragment { node, .. } = response else { | |
| 142 | + | let Outcome::Fragment { node, .. } = (response).outcome else { | |
| 142 | 143 | panic!("a fragment"); | |
| 143 | 144 | }; | |
| 144 | 145 | let html = quasi_webview::Webview::new().fragment(&node); | |
| @@ -152,7 +153,7 @@ | |||
| 152 | 153 | let state = state().await; | |
| 153 | 154 | add(&state, "Mine", ProjectStatus::Active); | |
| 154 | 155 | ||
| 155 | - | let Response::Screen(screen) = answer(&state, "/projects", Params::new()) else { | |
| 156 | + | let Outcome::Screen(screen) = answer(&state, "/projects", Params::new()).outcome else { | |
| 156 | 157 | panic!("a screen"); | |
| 157 | 158 | }; | |
| 158 | 159 | let html = quasi_webview::Webview::new().screen(&screen); | |
| @@ -160,7 +161,8 @@ | |||
| 160 | 161 | ||
| 161 | 162 | // Asking for the filtered view surfaces the control even with nothing | |
| 162 | 163 | // shared, so the way back is always on screen. Same rule as `projects.js`. | |
| 163 | - | let Response::Screen(screen) = answer(&state, "/projects", Params::new().with("shared", "1")) | |
| 164 | + | let Outcome::Screen(screen) = | |
| 165 | + | answer(&state, "/projects", Params::new().with("shared", "1")).outcome | |
| 164 | 166 | else { | |
| 165 | 167 | panic!("a screen"); | |
| 166 | 168 | }; | |
| @@ -179,7 +181,8 @@ | |||
| 179 | 181 | let state = state().await; | |
| 180 | 182 | add(&state, "Mine", ProjectStatus::OnHold); | |
| 181 | 183 | ||
| 182 | - | let Response::Fragment { node, .. } = answer(&state, "/projects/list", Params::new()) else { | |
| 184 | + | let Outcome::Fragment { node, .. } = answer(&state, "/projects/list", Params::new()).outcome | |
| 185 | + | else { | |
| 183 | 186 | panic!("a fragment"); | |
| 184 | 187 | }; | |
| 185 | 188 | let html = quasi_webview::Webview::new().fragment(&node); | |
| @@ -201,8 +204,8 @@ | |||
| 201 | 204 | let state = state().await; | |
| 202 | 205 | add(&state, "Done", ProjectStatus::Archived); | |
| 203 | 206 | ||
| 204 | - | let Response::Fragment { node, .. } = | |
| 205 | - | answer(&state, "/projects/list", Params::new().with("retired", "1")) | |
| 207 | + | let Outcome::Fragment { node, .. } = | |
| 208 | + | answer(&state, "/projects/list", Params::new().with("retired", "1")).outcome | |
| 206 | 209 | else { | |
| 207 | 210 | panic!("a fragment"); | |
| 208 | 211 | }; | |
| @@ -217,7 +220,8 @@ | |||
| 217 | 220 | let state = state().await; | |
| 218 | 221 | let project = add(&state, "Mine", ProjectStatus::Active); | |
| 219 | 222 | ||
| 220 | - | let Response::Fragment { node, .. } = answer(&state, "/projects/list", Params::new()) else { | |
| 223 | + | let Outcome::Fragment { node, .. } = answer(&state, "/projects/list", Params::new()).outcome | |
| 224 | + | else { | |
| 221 | 225 | panic!("a fragment"); | |
| 222 | 226 | }; | |
| 223 | 227 | let html = quasi_webview::Webview::new().fragment(&node); | |
| @@ -249,7 +253,7 @@ | |||
| 249 | 253 | let state = state().await; | |
| 250 | 254 | add(&state, "<script>alert(1)</script>", ProjectStatus::Active); | |
| 251 | 255 | ||
| 252 | - | let Response::Screen(screen) = answer(&state, "/projects", Params::new()) else { | |
| 256 | + | let Outcome::Screen(screen) = answer(&state, "/projects", Params::new()).outcome else { | |
| 253 | 257 | panic!("a screen"); | |
| 254 | 258 | }; | |
| 255 | 259 | let html = quasi_webview::Webview::new().screen(&screen); | |
| @@ -266,14 +270,14 @@ | |||
| 266 | 270 | let state = state().await; | |
| 267 | 271 | let project = add(&state, "Mine", ProjectStatus::Active); | |
| 268 | 272 | ||
| 269 | - | let Response::Screen(screen) = answer(&state, "/projects", Params::new()) else { | |
| 273 | + | let Outcome::Screen(screen) = answer(&state, "/projects", Params::new()).outcome else { | |
| 270 | 274 | panic!("a screen"); | |
| 271 | 275 | }; | |
| 272 | 276 | let html = quasi_webview::Webview::new().screen(&screen); | |
| 273 | 277 | assert!(html.contains("hx-get=\"/projects/new\"")); | |
| 274 | 278 | ||
| 275 | - | let Response::Fragment { node, .. } = | |
| 276 | - | answer(&state, &format!("/projects/{}", project.id), Params::new()) | |
| 279 | + | let Outcome::Fragment { node, .. } = | |
| 280 | + | answer(&state, &format!("/projects/{}", project.id), Params::new()).outcome | |
| 277 | 281 | else { | |
| 278 | 282 | panic!("a fragment"); | |
| 279 | 283 | }; | |
| @@ -292,7 +296,8 @@ | |||
| 292 | 296 | #[tokio::test] | |
| 293 | 297 | async fn the_create_form_asks_what_the_modal_asks() { | |
| 294 | 298 | let state = state().await; | |
| 295 | - | let Response::Fragment { node, .. } = answer(&state, "/projects/new", Params::new()) else { | |
| 299 | + | let Outcome::Fragment { node, .. } = answer(&state, "/projects/new", Params::new()).outcome | |
| 300 | + | else { | |
| 296 | 301 | panic!("a fragment"); | |
| 297 | 302 | }; | |
| 298 | 303 | let html = quasi_webview::Webview::new().fragment(&node); | |
| @@ -313,7 +318,7 @@ | |||
| 313 | 318 | #[tokio::test] | |
| 314 | 319 | async fn creating_a_project_puts_it_in_the_grid() { | |
| 315 | 320 | let state = state().await; | |
| 316 | - | let Response::Screen(screen) = post(&state, "/projects", valid("Made here")) else { | |
| 321 | + | let Outcome::Screen(screen) = post(&state, "/projects", valid("Made here")).outcome else { | |
| 317 | 322 | panic!("a write answers with the whole screen"); | |
| 318 | 323 | }; | |
| 319 | 324 | let html = quasi_webview::Webview::new().screen(&screen); | |
| @@ -332,7 +337,7 @@ | |||
| 332 | 337 | let state = state().await; | |
| 333 | 338 | let long = "x".repeat(101); | |
| 334 | 339 | ||
| 335 | - | let Response::Fragment { node, .. } = post(&state, "/projects", valid(&long)) else { | |
| 340 | + | let Outcome::Fragment { node, .. } = post(&state, "/projects", valid(&long)).outcome else { | |
| 336 | 341 | panic!("a refusal answers with the form, not the screen"); | |
| 337 | 342 | }; | |
| 338 | 343 | let html = quasi_webview::Webview::new().fragment(&node); | |
| @@ -349,7 +354,7 @@ | |||
| 349 | 354 | #[tokio::test] | |
| 350 | 355 | async fn a_nameless_project_is_refused_rather_than_created_blank() { | |
| 351 | 356 | let state = state().await; | |
| 352 | - | let Response::Fragment { .. } = post(&state, "/projects", valid(" ")) else { | |
| 357 | + | let Outcome::Fragment { .. } = post(&state, "/projects", valid(" ")).outcome else { | |
| 353 | 358 | panic!("a refusal"); | |
| 354 | 359 | }; | |
| 355 | 360 | assert!(state.projects.list_all(DESKTOP_USER_ID).unwrap().is_empty()); | |
| @@ -362,7 +367,7 @@ | |||
| 362 | 367 | let state = state().await; | |
| 363 | 368 | let params = a_project("Mine", "Sculpture", "Active"); | |
| 364 | 369 | ||
| 365 | - | let Response::Fragment { node, .. } = post(&state, "/projects", params) else { | |
| 370 | + | let Outcome::Fragment { node, .. } = post(&state, "/projects", params).outcome else { | |
| 366 | 371 | panic!("a refusal"); | |
| 367 | 372 | }; | |
| 368 | 373 | let html = quasi_webview::Webview::new().fragment(&node); | |
| @@ -371,7 +376,7 @@ | |||
| 371 | 376 | ||
| 372 | 377 | // A status that parses but is not on offer is refused on the same grounds. | |
| 373 | 378 | let params = a_project("Mine", "SideProject", "Archived"); | |
| 374 | - | let Response::Fragment { node, .. } = post(&state, "/projects", params) else { | |
| 379 | + | let Outcome::Fragment { node, .. } = post(&state, "/projects", params).outcome else { | |
| 375 | 380 | panic!("a refusal"); | |
| 376 | 381 | }; | |
| 377 | 382 | let html = quasi_webview::Webview::new().fragment(&node); | |
| @@ -385,11 +390,13 @@ | |||
| 385 | 390 | let project = add(&state, "Going", ProjectStatus::Active); | |
| 386 | 391 | add(&state, "Staying", ProjectStatus::Active); | |
| 387 | 392 | ||
| 388 | - | let Response::Screen(screen) = post( | |
| 393 | + | let Outcome::Screen(screen) = post( | |
| 389 | 394 | &state, | |
| 390 | 395 | &format!("/projects/{}/delete", project.id), | |
| 391 | 396 | Params::new(), | |
| 392 | - | ) else { | |
| 397 | + | ) | |
| 398 | + | .outcome | |
| 399 | + | else { | |
| 393 | 400 | panic!("a write answers with the whole screen"); | |
| 394 | 401 | }; | |
| 395 | 402 | let html = quasi_webview::Webview::new().screen(&screen); | |
| @@ -425,7 +432,7 @@ | |||
| 425 | 432 | let project = add(&state, "Finished", ProjectStatus::Completed); | |
| 426 | 433 | let retired = Params::new().with("retired", "1"); | |
| 427 | 434 | ||
| 428 | - | let Response::Screen(screen) = answer(&state, "/projects", retired.clone()) else { | |
| 435 | + | let Outcome::Screen(screen) = answer(&state, "/projects", retired.clone()).outcome else { | |
| 429 | 436 | panic!("a screen"); | |
| 430 | 437 | }; | |
| 431 | 438 | let html = quasi_webview::Webview::new().screen(&screen); | |
| @@ -437,8 +444,8 @@ | |||
| 437 | 444 | assert!(html.contains(&format!("hx-get=\"/projects/new\" {vals}"))); | |
| 438 | 445 | assert!(html.contains(&format!("hx-get=\"/projects/{}\" {vals}", project.id))); | |
| 439 | 446 | ||
| 440 | - | let Response::Fragment { node, .. } = | |
| 441 | - | answer(&state, &format!("/projects/{}", project.id), retired) | |
| 447 | + | let Outcome::Fragment { node, .. } = | |
| 448 | + | answer(&state, &format!("/projects/{}", project.id), retired).outcome | |
| 442 | 449 | else { | |
| 443 | 450 | panic!("a fragment"); | |
| 444 | 451 | }; | |
| @@ -455,11 +462,13 @@ | |||
| 455 | 462 | add(&state, "Finished", ProjectStatus::Completed); | |
| 456 | 463 | let doomed = add(&state, "Going", ProjectStatus::Completed); | |
| 457 | 464 | ||
| 458 | - | let Response::Screen(screen) = post( | |
| 465 | + | let Outcome::Screen(screen) = post( | |
| 459 | 466 | &state, | |
| 460 | 467 | &format!("/projects/{}/delete", doomed.id), | |
| 461 | 468 | Params::new().with("retired", "1"), | |
| 462 | - | ) else { | |
| 469 | + | ) | |
| 470 | + | .outcome | |
| 471 | + | else { | |
| 463 | 472 | panic!("a screen"); | |
| 464 | 473 | }; | |
| 465 | 474 | let html = quasi_webview::Webview::new().screen(&screen); |
| @@ -9,6 +9,7 @@ | |||
| 9 | 9 | ||
| 10 | 10 | use goingson_core::{NewTask, Priority}; | |
| 11 | 11 | use quasi_http::Render as _; | |
| 12 | + | use quasi_router::Outcome; | |
| 12 | 13 | use quasi_router::{Method, Params, Response}; | |
| 13 | 14 | ||
| 14 | 15 | use super::super::router; | |
| @@ -63,9 +64,13 @@ | |||
| 63 | 64 | } | |
| 64 | 65 | ||
| 65 | 66 | fn html(response: Response) -> String { | |
| 66 | - | match response { | |
| 67 | - | Response::Screen(screen) => quasi_webview::Webview::new().screen(&screen), | |
| 68 | - | Response::Fragment { node, .. } => quasi_webview::Webview::new().fragment(&node), | |
| 67 | + | match response.outcome { | |
| 68 | + | Outcome::Screen(screen) => quasi_webview::Webview::new().screen(&screen), | |
| 69 | + | Outcome::Fragment { node, .. } => quasi_webview::Webview::new().fragment(&node), | |
| 70 | + | // Deliberately not a wildcard. See the same helper in the dashboard | |
| 71 | + | // tests: a redirect has no body, and a fallback returning empty markup | |
| 72 | + | // would read as a screen that rendered nothing. | |
| 73 | + | Outcome::Goto(action) => panic!("expected content, got a redirect to {action:?}"), | |
| 69 | 74 | } | |
| 70 | 75 | } | |
| 71 | 76 | ||
| @@ -278,7 +283,7 @@ | |||
| 278 | 283 | &format!("/tasks/{}/subtasks", task.id), | |
| 279 | 284 | Params::new().with("text", "Added here"), | |
| 280 | 285 | ); | |
| 281 | - | let Response::Screen(_) = &response else { | |
| 286 | + | let Outcome::Screen(_) = &response.outcome else { | |
| 282 | 287 | panic!("a write answers with the whole screen"); | |
| 283 | 288 | }; | |
| 284 | 289 | let page = html(response); | |
| @@ -357,20 +362,27 @@ | |||
| 357 | 362 | } | |
| 358 | 363 | ||
| 359 | 364 | #[tokio::test] | |
| 360 | - | async fn deleting_a_task_has_nowhere_to_send_the_user() { | |
| 361 | - | // The sixth finding, asserted. Every answer replaces a region or the screen | |
| 362 | - | // in place, and deleting the thing a screen is about is the first case | |
| 363 | - | // where the right answer is a different address. The JS closes the drawer. | |
| 365 | + | async fn deleting_a_task_sends_the_user_to_the_list_and_says_so() { | |
| 366 | + | // The sixth finding, closed 2026-08-09 (`80afd652`). This asserted the | |
| 367 | + | // workaround until then: a tombstone screen, addressed by the id of the | |
| 368 | + | // thing that no longer existed, because every answer was content. The | |
| 369 | + | // workaround is gone, so the assertion had to change rather than be relaxed. | |
| 364 | 370 | let state = state().await; | |
| 365 | 371 | let task = add(&state, "Going"); | |
| 366 | 372 | ||
| 367 | - | let page = html(post( | |
| 368 | - | &state, | |
| 369 | - | &format!("/tasks/{}/delete", task.id), | |
| 370 | - | Params::new(), | |
| 371 | - | )); | |
| 373 | + | let answer = post(&state, &format!("/tasks/{}/delete", task.id), Params::new()); | |
| 374 | + | ||
| 375 | + | let Outcome::Goto(action) = &answer.outcome else { | |
| 376 | + | panic!("a delete answers with somewhere to go, not with content"); | |
| 377 | + | }; | |
| 378 | + | assert_eq!(action.destination.route(), Some("/tasks")); | |
| 379 | + | ||
| 380 | + | // And it says what happened, which the redirect alone cannot: the list it | |
| 381 | + | // lands on looks the same whether a task was deleted or the user navigated. | |
| 382 | + | let notice = answer.notice.as_ref().expect("a delete says so"); | |
| 383 | + | assert_eq!(notice.text, "Task deleted"); | |
| 384 | + | assert_eq!(notice.kind, quasi_router::layout::Notice::Toast); | |
| 372 | 385 | ||
| 373 | - | assert!(page.contains("Task deleted")); | |
| 374 | 386 | // And it is really gone. | |
| 375 | 387 | let error = router() | |
| 376 | 388 | .handle( |
| @@ -6,6 +6,7 @@ | |||
| 6 | 6 | MilestoneStatus, NewMilestone, NewProject, NewTask, ProjectId, ProjectStatus, ProjectType, | |
| 7 | 7 | }; | |
| 8 | 8 | use quasi_http::Render as _; | |
| 9 | + | use quasi_router::Outcome; | |
| 9 | 10 | use quasi_router::{Method, Params, Response}; | |
| 10 | 11 | ||
| 11 | 12 | use crate::quasi::router; | |
| @@ -66,9 +67,14 @@ | |||
| 66 | 67 | } | |
| 67 | 68 | ||
| 68 | 69 | fn html(response: Response) -> String { | |
| 69 | - | match response { | |
| 70 | - | Response::Screen(screen) => quasi_webview::Webview::new().screen(&screen), | |
| 71 | - | Response::Fragment { node, .. } => quasi_webview::Webview::new().fragment(&node), | |
| 70 | + | match response.outcome { | |
| 71 | + | Outcome::Screen(screen) => quasi_webview::Webview::new().screen(&screen), | |
| 72 | + | Outcome::Fragment { node, .. } => quasi_webview::Webview::new().fragment(&node), | |
| 73 | + | // A redirect has no body to render. `Outcome` is deliberately not | |
| 74 | + | // `#[non_exhaustive]`, so this arm had to be written rather than falling | |
| 75 | + | // into a wildcard that returned empty markup and looked like a screen | |
| 76 | + | // that rendered nothing. | |
| 77 | + | Outcome::Goto(action) => panic!("expected content, got a redirect to {action:?}"), | |
| 72 | 78 | } | |
| 73 | 79 | } | |
| 74 | 80 | ||
| @@ -143,11 +149,13 @@ | |||
| 143 | 149 | let detail = get(&state, &format!("/projects/{project}"), Params::new()); | |
| 144 | 150 | assert_eq!(detail.target(), Some("projects-detail")); | |
| 145 | 151 | ||
| 146 | - | let Response::Screen(_) = get( | |
| 152 | + | let Outcome::Screen(_) = get( | |
| 147 | 153 | &state, | |
| 148 | 154 | &format!("/projects/{project}/dashboard"), | |
| 149 | 155 | Params::new(), | |
| 150 | - | ) else { | |
| 156 | + | ) | |
| 157 | + | .outcome | |
| 158 | + | else { | |
| 151 | 159 | panic!("the dashboard answers with a screen"); | |
| 152 | 160 | }; | |
| 153 | 161 | } | |
| @@ -194,7 +202,8 @@ | |||
| 194 | 202 | assert!(page.contains(&format!("hx-get=\"/tasks/{}\"", task.id))); | |
| 195 | 203 | ||
| 196 | 204 | // And it answers. | |
| 197 | - | let Response::Screen(_) = get(&state, &format!("/tasks/{}", task.id), Params::new()) else { | |
| 205 | + | let Outcome::Screen(_) = get(&state, &format!("/tasks/{}", task.id), Params::new()).outcome | |
| 206 | + | else { | |
| 198 | 207 | panic!("the overview answers"); | |
| 199 | 208 | }; | |
| 200 | 209 | } |