| 378 |
378 |
|
/// The questions the create form asks.
|
| 379 |
379 |
|
///
|
| 380 |
380 |
|
/// The four `projects.js` asks, in its order. `errors` is what a rejected
|
| 381 |
|
- |
/// submission carries back, keyed by field name; on a first showing it is
|
| 382 |
|
- |
/// empty.
|
|
381 |
+ |
/// submission carries back, keyed by field name, and `submitted` is what that
|
|
382 |
+ |
/// submission held; on a first showing both are empty.
|
| 383 |
383 |
|
///
|
| 384 |
|
- |
/// # The finding this route ran into
|
|
384 |
+ |
/// # The finding this route ran into, and how it closed
|
| 385 |
385 |
|
///
|
| 386 |
|
- |
/// **A form that refuses cannot re-offer what was typed.** [`Field`] carries no
|
| 387 |
|
- |
/// value and says in its own docs that it is not going to, on the grounds that a
|
| 388 |
|
- |
/// value is renderer state. That is right for a form being shown, and it leaves
|
| 389 |
|
- |
/// the rejected case with nowhere to put the name the user typed: this answer
|
| 390 |
|
- |
/// names what is wrong and hands back an empty box to fix it in. `projects.js`
|
| 391 |
|
- |
/// validates in the browser and never loses a keystroke.
|
|
386 |
+ |
/// **A form that refuses cannot re-offer what was typed.** It named what was
|
|
387 |
+ |
/// wrong and handed back an empty box to fix it in, so a 101-character name was
|
|
388 |
+ |
/// reported as too long and then thrown away, and the user retyped it to
|
|
389 |
+ |
/// shorten it. `projects.js` validates in the browser and never loses a
|
|
390 |
+ |
/// keystroke, so the described screen was worse than the one it replaces on
|
|
391 |
+ |
/// exactly the path the description was meant to make safer.
|
| 392 |
392 |
|
///
|
| 393 |
|
- |
/// The workaround is asserted by a test rather than left to be noticed, and it
|
| 394 |
|
- |
/// is filed as makeover-layout `1c4a66a4`. Not patched here: the
|
| 395 |
|
- |
/// admission test says a description that needs a new fact asks the vocabulary
|
| 396 |
|
- |
/// for it.
|
| 397 |
|
- |
fn form_fields(errors: &[(&str, String)]) -> Vec<Field> {
|
|
393 |
+ |
/// Filed as `1c4a66a4` and closed 2026-08-09 on quasi-router rather than on
|
|
394 |
+ |
/// makeover-layout: an immediate-mode renderer and a terminal hold the value
|
|
395 |
+ |
/// across a refusal by construction, so the loss belongs to the request and
|
|
396 |
+ |
/// response cycle and is repaired there. [`Field::refilled`] is what this calls.
|
|
397 |
+ |
fn form_fields(errors: &[(&str, String)], submitted: Option<&quasi_router::Params>) -> Vec<Field> {
|
| 398 |
398 |
|
let error_for = |name: &str| {
|
| 399 |
399 |
|
errors
|
| 400 |
400 |
|
.iter()
|
| 416 |
416 |
|
);
|
| 417 |
417 |
|
description.placeholder = Some("What's this project about?".to_owned());
|
| 418 |
418 |
|
|
| 419 |
|
- |
vec![
|
|
419 |
+ |
let fields = vec![
|
| 420 |
420 |
|
apply(name, "name"),
|
| 421 |
421 |
|
apply(description, "description"),
|
| 422 |
422 |
|
apply(
|
| 441 |
441 |
|
),
|
| 442 |
442 |
|
"status",
|
| 443 |
443 |
|
),
|
| 444 |
|
- |
]
|
|
444 |
+ |
];
|
|
445 |
+ |
|
|
446 |
+ |
match submitted {
|
|
447 |
+ |
Some(params) => fields
|
|
448 |
+ |
.into_iter()
|
|
449 |
+ |
.map(|field| field.refilled(params))
|
|
450 |
+ |
.collect(),
|
|
451 |
+ |
None => fields,
|
|
452 |
+ |
}
|
| 445 |
453 |
|
}
|
| 446 |
454 |
|
|
| 447 |
455 |
|
/// The create form, in the pane the detail pane uses.
|
| 448 |
|
- |
fn form_pane(shared_only: bool, show_retired: bool, errors: &[(&str, String)]) -> Node {
|
|
456 |
+ |
fn form_pane(
|
|
457 |
+ |
shared_only: bool,
|
|
458 |
+ |
show_retired: bool,
|
|
459 |
+ |
errors: &[(&str, String)],
|
|
460 |
+ |
submitted: Option<&quasi_router::Params>,
|
|
461 |
+ |
) -> Node {
|
| 449 |
462 |
|
Node::Region(
|
| 450 |
463 |
|
Slot::new("projects-detail", RegionKind::Pane)
|
| 451 |
464 |
|
.with(Node::section("New project"))
|
| 452 |
465 |
|
.with(Node::Form {
|
| 453 |
466 |
|
action: filtered(Action::post("/projects"), shared_only, show_retired),
|
| 454 |
467 |
|
submit: "Create project".to_owned(),
|
| 455 |
|
- |
fields: form_fields(errors),
|
|
468 |
+ |
fields: form_fields(errors, submitted),
|
| 456 |
469 |
|
}),
|
| 457 |
470 |
|
)
|
| 458 |
471 |
|
}
|
| 461 |
474 |
|
fn new(_state: &AppState, params: quasi_router::Params) -> Result<Response, RouteError> {
|
| 462 |
475 |
|
Ok(Response::fragment(
|
| 463 |
476 |
|
"projects-detail",
|
| 464 |
|
- |
form_pane(flag(¶ms, "shared"), flag(¶ms, "retired"), &[]),
|
|
477 |
+ |
form_pane(flag(¶ms, "shared"), flag(¶ms, "retired"), &[], None),
|
| 465 |
478 |
|
))
|
| 466 |
479 |
|
}
|
| 467 |
480 |
|
|
| 529 |
542 |
|
let (Some(project_type), Some(status)) = (project_type, status) else {
|
| 530 |
543 |
|
return Ok(Response::fragment(
|
| 531 |
544 |
|
"projects-detail",
|
| 532 |
|
- |
form_pane(shared_only, show_retired, &errors),
|
|
545 |
+ |
form_pane(shared_only, show_retired, &errors, Some(¶ms)),
|
| 533 |
546 |
|
));
|
| 534 |
547 |
|
};
|
| 535 |
548 |
|
if !errors.is_empty() {
|
| 536 |
549 |
|
return Ok(Response::fragment(
|
| 537 |
550 |
|
"projects-detail",
|
| 538 |
|
- |
form_pane(shared_only, show_retired, &errors),
|
|
551 |
+ |
form_pane(shared_only, show_retired, &errors, Some(¶ms)),
|
| 539 |
552 |
|
));
|
| 540 |
553 |
|
}
|
| 541 |
554 |
|
|