Skip to content

fix(announcements): submit button could never enable on the new-announcement form - #974

Merged
philmerrell merged 1 commit into
developfrom
fix/announcements-form-submit-gate
Sep 6, 2026
Merged

fix(announcements): submit button could never enable on the new-announcement form#974
philmerrell merged 1 commit into
developfrom
fix/announcements-form-submit-gate

Conversation

@philmerrell

Copy link
Copy Markdown
Contributor

Follow-up to #972. The admin form shipped unable to create anything — this fixes that.

The bug

Browser-verifying the page in dev: every field filled, the form reporting ng-valid on every control, and "Create draft" still disabled. No announcement could be authored from the UI at all, which was the entire point of PR-3.

canSubmit is a computed, and a computed tracks the signals read during its last execution — so an early return shortens its dependency set:

protected readonly canSubmit = computed(() => {
  if (this.isSubmitting()) return false;   // signal — tracked
  if (this.form.invalid) return false;     // ⚠️ plain getter, NOT a signal — returns here
  if (this.bodyOverLimit()) return false;  // never reached on first run
  
});

On the first evaluation the form was empty, so it returned at line 2 having tracked only isSubmitting. Nothing else was a dependency, so no later edit could schedule a recompute — and isSubmitting only changes inside onSubmit, which the disabled button prevented. A deadlock.

The fix

Two changes, both load-bearing:

  1. Form validity is mirrored into a signal fed by statusChanges, exactly like the valueChanges mirrors this component already keeps for the template.
  2. Every input is read unconditionally before being combined, so no branch can shrink the tracked dependency set again. The comment on canSubmit explains why, because the guard-clause version reads as the more natural style and someone will want to refactor back to it.

Why the existing tests missed it

They only ever read canSubmit() after filling the form. In that order the computed's first evaluation sees a valid form, reads past the form.invalid check, tracks every signal, and stays reactive — so the bug is invisible. In the real page the first read happens while the form is still empty.

The three new tests read canSubmit() while empty first, then fill. That ordering is the entire test.

I verified they actually catch it rather than assuming: reverting the fix makes the 3 new tests fail while all 24 original specs still pass.

Testing

  • npm test: 217 files, 2431 tests pass.
  • npx tsc --noEmit: clean.
  • Reverted-fix check: 3 failed / 24 passed, as described above.

Backend untouched.

Note for the epic

This is the second bug in this feature that unit tests passed and a browser caught (the first was PR-2's markdown rendering with inert prose classes). Both were in the SPA, and both were invisible to specs that exercised the logic without rendering it. Worth remembering when PR-4 (banner) and PR-5 (modal) land — those are the surfaces where a silent failure is most costly.

🤖 Generated with Claude Code

…ncement form

Found by browser-verifying the page in dev: every field filled, the form
reporting `ng-valid`, and "Create draft" still disabled. No announcement
could be authored from the UI at all.

`canSubmit` is a `computed`, and a computed tracks the signals read during
its *last* execution — so an early `return` shortens its dependency set. The
guard chain read `isSubmitting()` and then `if (this.form.invalid) return
false`, and `FormGroup.invalid` is a plain getter, not a signal. On the first
evaluation the form was empty, so it returned there having tracked only
`isSubmitting`. No later edit could schedule a recompute, and `isSubmitting`
changes only inside `onSubmit` — which the disabled button prevented.

Two changes, both load-bearing:
- form validity is mirrored into a signal fed by `statusChanges`, like the
  other `valueChanges` mirrors already in this component;
- every input is read unconditionally before being combined, so no branch can
  shrink the tracked dependency set again.

The three new tests read `canSubmit()` while the form is still **empty**,
then fill it. That ordering is the whole point: the existing 24 specs only
ever read it after filling, so the computed's first evaluation saw a valid
form, tracked everything, and stayed reactive — all 24 pass against the
broken code. Verified by reverting the fix: the 3 new tests fail, the other
24 do not.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@philmerrell
philmerrell merged commit 660b0a7 into develop Sep 6, 2026
4 checks passed
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