Fix request-form builder and send-flow usability (PMS-747) #498

Merged
longjacksonle merged 4 commits from fix/PMS-747-request-form-ux into main 2026-08-09 18:50:40 +02:00

PMS-747. Five problems were reported against the request-form feature after building a "New user" form and sending it to a client. All five reproduce here; the sixth, found while reproducing the fifth, is server-side and goes out as a separate PR against mokosh-server.

Two of the reports are not quite what they say, and the ticket now records that: the send has never required a contact (a typed address is enough), and nothing about a form-raised ticket requires approval. Both are still real confusion, so both are fixed as confusion rather than as behaviour.

Add field scrolled out of reach

The only "Add field" control sat in the Fields section header, which is inside the one region the editor modal scrolls (the header and footer are pinned). Each field card is roughly 250px tall, so from the third field on the control was off-screen and adding a fourth meant scrolling back past everything just typed. There is now a second control under the last row.

The reported alternative, collapsing added fields into a table with a single edit dialog, is a rebuild of the editor rather than a fix to it. Not done here; the ticket says so.

Reference names derive from the label

A field's reference name was a bare required text input the operator had to invent, even though the form's own link name has followed the form name since PMS-731. It now derives the same way ("Phone number" gives phone_number) and stops following the label the moment the operator edits it. On an existing definition the derivation never runs at all: a reference name is the key answers are already stored under, so a label typo fixed months later must not silently rekey a live field.

A refused Create Form now says so

Pressing Create Form on an invalid form appeared to do nothing. The check ran and reported, but it reported into the ErrorBanner at the top of the modal body, which is the region that scrolls, so from the bottom of a three-field form the message rendered several hundred pixels above the viewport. Every problem is now written twice: into the row it belongs to, so the offending input is marked where the operator is looking, and into the banner, which is still the only surface that can speak for a row scrolled out of view. A count sits in the pinned footer next to the button that was pressed.

The reference-name shape the server enforces (validate_field_name) is now checked in the browser too, so a hand-typed "Last Name" is refused with the reason rather than round-tripping to a 400 that lands in the same invisible banner.

Sending to a client with no emailable contact

The Contact select rendered holding nothing but its placeholder, under help text that said only which contacts are listed. It never said there were none, so the screen read as "this needs a contact I have no way to create". The empty case now names the client, says it has no contact with an email address, and links to New Contact prefilled with that client. It is not an error banner: sending to a typed address is a normal way to use this modal, not a fallback.

The placeholder now says what choosing it does, "Someone else (type an address below)", instead of leaving the address field underneath it unexplained.

An empty Approvals card no longer reads as a gate

A ticket raised from a client's own submitted form looked like it was being held for approval. Nothing gates it: the server's forms module contains no reference to approvals and the ticket is created with the default status. What the reporter saw was the Approvals card, which is on every ticket, sitting empty and saying "No approvals requested on this ticket yet". The copy now says approval is optional and the ticket is not waiting on one. This touches every ticket, not only form-raised ones, which is the point.

Testing

cargo fmt --all --check, cargo clippy --all-targets -- -D warnings, both guard scripts, and cargo test --lib (273 passing, up from 269). New tests cover the reference-name derivation, that everything it derives is something the server accepts, the shape check applied to hand-typed names, and the Add-one link carrying its client through the query string that contacts.rs reads back.

Not covered by tests: the scroll and copy changes, which are why this needs eyeballing on staging. The browser-driven e2e specs are still quarantined.

PMS-747. Five problems were reported against the request-form feature after building a "New user" form and sending it to a client. All five reproduce here; the sixth, found while reproducing the fifth, is server-side and goes out as a separate PR against mokosh-server. Two of the reports are not quite what they say, and the ticket now records that: the send has never required a contact (a typed address is enough), and nothing about a form-raised ticket requires approval. Both are still real confusion, so both are fixed as confusion rather than as behaviour. ## Add field scrolled out of reach The only "Add field" control sat in the Fields section header, which is inside the one region the editor modal scrolls (the header and footer are pinned). Each field card is roughly 250px tall, so from the third field on the control was off-screen and adding a fourth meant scrolling back past everything just typed. There is now a second control under the last row. The reported alternative, collapsing added fields into a table with a single edit dialog, is a rebuild of the editor rather than a fix to it. Not done here; the ticket says so. ## Reference names derive from the label A field's reference name was a bare required text input the operator had to invent, even though the form's own link name has followed the form name since PMS-731. It now derives the same way ("Phone number" gives `phone_number`) and stops following the label the moment the operator edits it. On an existing definition the derivation never runs at all: a reference name is the key answers are already stored under, so a label typo fixed months later must not silently rekey a live field. ## A refused Create Form now says so Pressing Create Form on an invalid form appeared to do nothing. The check ran and reported, but it reported into the ErrorBanner at the top of the modal body, which is the region that scrolls, so from the bottom of a three-field form the message rendered several hundred pixels above the viewport. Every problem is now written twice: into the row it belongs to, so the offending input is marked where the operator is looking, and into the banner, which is still the only surface that can speak for a row scrolled out of view. A count sits in the pinned footer next to the button that was pressed. The reference-name shape the server enforces (`validate_field_name`) is now checked in the browser too, so a hand-typed "Last Name" is refused with the reason rather than round-tripping to a 400 that lands in the same invisible banner. ## Sending to a client with no emailable contact The Contact select rendered holding nothing but its placeholder, under help text that said only which contacts are listed. It never said there were none, so the screen read as "this needs a contact I have no way to create". The empty case now names the client, says it has no contact with an email address, and links to New Contact prefilled with that client. It is not an error banner: sending to a typed address is a normal way to use this modal, not a fallback. The placeholder now says what choosing it does, "Someone else (type an address below)", instead of leaving the address field underneath it unexplained. ## An empty Approvals card no longer reads as a gate A ticket raised from a client's own submitted form looked like it was being held for approval. Nothing gates it: the server's forms module contains no reference to approvals and the ticket is created with the default status. What the reporter saw was the Approvals card, which is on every ticket, sitting empty and saying "No approvals requested on this ticket yet". The copy now says approval is optional and the ticket is not waiting on one. This touches every ticket, not only form-raised ones, which is the point. ## Testing `cargo fmt --all --check`, `cargo clippy --all-targets -- -D warnings`, both guard scripts, and `cargo test --lib` (273 passing, up from 269). New tests cover the reference-name derivation, that everything it derives is something the server accepts, the shape check applied to hand-typed names, and the Add-one link carrying its client through the query string that `contacts.rs` reads back. Not covered by tests: the scroll and copy changes, which are why this needs eyeballing on staging. The browser-driven e2e specs are still quarantined.
PMS-747 R1: the only "Add field" button sits in the Fields section header, which is inside the one region the editor modal scrolls (header and footer are pinned). Each field card is roughly 250px tall, so from the third field on the control is off-screen and adding a fourth means scrolling back past everything just typed.

The reported alternative, collapsing added fields into a table with one edit dialog, is a rebuild of the editor rather than a fix to it. A second control under the last row puts it where the work is for the cost of one button.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E48b8YHWYwmaKjFBy5esXp
PMS-747 R2. Two defects in one flow, fixed together because the first is most of the cause of the second.

A field's reference name was a bare required text input the operator had to invent, even though the form's own link name has followed the form name since PMS-731. It now derives the same way: "Phone number" gives `phone_number`, and it stops following the label the moment the operator edits it. On an existing definition the derivation never runs, because a reference name is the key answers are already stored under and a label typo fixed months later must not silently rekey a live field.

Pressing Create Form on an invalid form appeared to do nothing. The check ran and reported, but it reported into the ErrorBanner at the top of the modal body, which is the region that scrolls, so from the bottom of a three-field form the message rendered several hundred pixels above the viewport. Every problem is now written twice: into the row it belongs to, so the offending input is marked where the operator is looking, and into the banner, which is still the only surface that can speak for a row scrolled out of view. A count also sits in the pinned footer, next to the button that was pressed.

The reference name shape the server enforces (`validate_field_name`: leading lowercase letter, `[a-z0-9_]`, no trailing or doubled underscore) is now checked in the browser, so a hand-typed "Last Name" is refused with the reason instead of round-tripping to a 400 that lands in the same invisible banner.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E48b8YHWYwmaKjFBy5esXp
PMS-747 R3 and R4. Sending a request form to a client with no contact carrying an email address rendered a Select holding nothing but its placeholder, under help text that said only which contacts are listed. It never said there were none, so the screen read as "this needs a contact I have no way to create" even though the send has always accepted a typed address on its own and the company page has carried an Add Contact action since MAPPS-207.

The empty case now names the client, says it has no emailable contact, and links to New Contact prefilled with that client. It is not an error banner: sending to a typed address is a normal way to use this modal, not a fallback.

The placeholder now says what choosing it does ("Someone else (type an address below)") instead of leaving the address field below it unexplained.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E48b8YHWYwmaKjFBy5esXp
fix(tickets): stop an empty Approvals card reading as a pending gate
All checks were successful
Check / fmt + clippy + tests (pull_request) Successful in 1m39s
Create release / Create release from merged PR (pull_request) Has been skipped
498aac6ce2
PMS-747 R5: a ticket raised from a client's own submitted request form looked like it was being held for approval. Nothing gates it. The form path neither creates nor requires an approval (there is no reference to approvals anywhere in the server's forms module, and the ticket is created with the default status); what the reporter saw was the Approvals card, which is on every ticket, sitting empty and saying "No approvals requested on this ticket yet".

The copy now says approval is optional and that the ticket is not waiting on one. No workflow change, because there is no workflow to change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E48b8YHWYwmaKjFBy5esXp
longjacksonle scheduled this pull request to auto merge when all checks succeed 2026-08-09 18:47:10 +02:00
longjacksonle deleted branch fix/PMS-747-request-form-ux 2026-08-09 18:50:40 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
psa-systems/mokosh-apps!498
No description provided.