Skip to content

fix(ui): clear the weekday chips on the share form's recycle path - #280

Merged
argszero merged 1 commit into
mainfrom
fix/form-control-disabled-recycle-path
Sep 21, 2026
Merged

argszero merged 1 commit into
mainfrom
fix/form-control-disabled-recycle-path

Conversation

@argszero

Copy link
Copy Markdown
Owner

Summary

On the sharing form the "every day" shortcut disables the seven weekday chips by
property:

if (cb !== allCb) { cb.checked = allCb.checked; cb.disabled = allCb.checked; }

A successful listing recycles the form with e.target.reset(). HTMLFormElement.reset()
restores values and checkedness to their defaults, but it does not clear the
disabled property — and the property here was never set as an attribute, so there is
nothing for reset() to reflect.

Result: after one successful listing with "every day" checked, the seven chips are left
unchecked and disabled. The form card is static HTML — showShareForm,
renderSharing and fillPlans never touch it — so nothing in the session repairs it:
clicking any weekday is silently swallowed until the user toggles "every day" on and off
again.

The fix collapses the clear into a single named helper and calls it on both recycle roots:
the reset path (afterOk, immediately after e.target.reset()) and the reopen path
(showShareForm).

Related Issue

None — the repository has no open issue for this; it was found during a UI state audit.
No Closes #N.

Changes

  • ui/js/app.js — new resetShareAvail() helper (the single clear point for the
    seven chips) called from showShareForm() and from afterOk().
  • ui/index.html — cache-bust bump for app.js.
  • src/state_gate.rs — new gate
    a_form_control_disabled_by_the_property_is_cleared_on_the_recycle_path plus its
    self-check the_form_recycle_path_scanners_have_teeth (test-only module; nothing
    compiles into release artifacts).
  • ui/README.md — documents the convention ("a form control disabled by property
    must have a same-channel .disabled = false on the recycle path").
  • No configuration / data-structure change, so no sample file is involved.

Tests

  • cargo test — 322 passed / 0 failed (baseline main 320; +2 = the two new tests).
  • cargo fmt --check — clean.
  • cargo clippy --all-targets -- -D warnings — clean (CI's exact command).
  • New unit tests added.

What the gate pins (shape)

Three rules, each with its own tooth:

  1. Positive control — the set of "form control disabled by property" channels is
    non-empty, and each channel derives its <form> id and card id from index.html
    (today: #sf-days .chip input → share-form → share-form-card, no roster).
  2. Invariant — every such channel has a same-channel .disabled = false inside the
    recycle closure = the call closure of the function that calls form.reset() ∪ the
    call closures of the functions that open the form card (both roots derived from
    index.html). This is RED on the pre-fix tree (witness = None) and GREEN after.
  3. Reverse — the recycle host must still be called, so "delete the recycle path"
    cannot satisfy rule 2.

Deliberately accepted as legitimate (both roots are valid): clearing only after the reset,
or only when the form opens. Deliberately rejected: clearing inside the "every day" change
handler (not on a recycle root), and dropping the mutual exclusion (that empties the
writer set, i.e. the shortcut starts lying).

What proves the browser fact (runtime)

The gate is lexical; the browser fact (reset() does not clear the property) and the
user-visible symptom are covered by a jsdom probe that boots the real index.html + the
four real scripts, stubs and logs fetch, and drives the real controls (nav → add →
provider/plan/model → the "every day" chip → a real submit → the real POST → afterOk
→ reopen). Nine legs, each declared per tree:

tree reading
landed (this branch) 9/9 as declared
fix stripped in memory axis legs {B1,B2} FAIL, chips left -D -D -D -D -D -D -D
competing fix (drop the exclusion) {C2,C3,A3} FAIL — the shortcut lies and the POST no longer carries 7 days

Shape belongs to the gate, facts belong to the probe.

Checklist

  • Branch name follows the convention (fix/…).
  • Commit message uses Conventional Commits.
  • Single responsibility, minimal change — the diff is a pure insertion apart from the
    one cache-bust line.

The "every day" shortcut disables the seven weekday chips by **property**
(`cb.disabled = allCb.checked`), while a successful listing recycles the form with
`e.target.reset()`. `HTMLFormElement.reset()` restores values/checkedness to their
defaults but does **not** clear the `disabled` property — so after one successful
listing with "every day" checked the seven chips are left unchecked *and* disabled,
and nothing in the session ever clears it again (the form card is static HTML, so
`showShareForm` / `renderSharing` / `fillPlans` never touch it). Picking any single
weekday is a no-op until the shortcut is toggled twice.

Collapse the clear into one named helper, `resetShareAvail()`, and call it on both
recycle roots: the reset path (`afterOk`, right after `e.target.reset()`) and the
reopen path (`showShareForm`).

The new gate `state_gate::a_form_control_disabled_by_the_property_is_cleared_on_
the_recycle_path` pins the shape: every channel in which a form control is disabled
by property must have a same-channel `.disabled = false` inside the recycle closure
(the call closure of the function that calls `form.reset()`, plus that of the
functions that open the form card — both roots derived from `index.html`, no
roster). A second rule refuses the over-correction of deleting the recycle path
itself, and `the_form_recycle_path_scanners_have_teeth` proves each rule with
synthetic inputs. The browser fact (that `reset()` does not touch the property) and
the user-visible symptom are covered by a jsdom probe; shape belongs to the gate,
facts belong to the probe.
@argszero

Copy link
Copy Markdown
Owner Author

Self-review note (own PR — a formal approving review is not possible here, so this is a
plain comment).

Verified on this head (f90eae0)

  • cargo test → 322 passed / 0 failed (baseline main = 320; the +2 are the two new
    tests in src/state_gate.rs).
  • cargo fmt --check → clean; cargo clippy --all-targets -- -D warnings → clean (CI's
    exact clippy invocation, so the two lints this fragment originally tripped are gone).
  • The diff is a pure insertion except the single cache-bust line in ui/index.html.
  • jsdom probe, three trees: landed 9/9 as declared; fix-stripped-in-memory ⇒ axis legs
    {B1,B2} fail with the chips left -D -D -D -D -D -D -D; competing fix (drop the mutual
    exclusion) ⇒ {C2,C3,A3} fail.

Two things worth a second pair of eyes

  1. resetShareAvail() is called on both recycle roots (afterOk after the reset, and
    showShareForm on reopen). Either one alone is a legitimate fix; the gate accepts both
    and only rejects a clear that lives upstream in the "every day" change handler.
  2. The gate's channel is derived by matching the CSS tag input (not the substring) —
    #chat-input must not qualify. If a future control is disabled by property through a
    single-quoted selector the scanner deliberately does not see it (documented range, in
    ui/README.md alongside the other gates).

@argszero
argszero merged commit 858c0b0 into main Sep 21, 2026
1 check passed
@argszero
argszero deleted the fix/form-control-disabled-recycle-path branch September 21, 2026 22:02
@argszero argszero mentioned this pull request Sep 24, 2026
10 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant