Found while adding render coverage for geography-facts-management.component.html (wave-5 coverage loop, angular branch test/component-coverage-wave5).
Symptom
Admin → Geography facts → pick a country → Edit on any fact whose source is empty throws during render and the inline edit form fails to appear:
TypeError: this.field(...) is not a function
at FormField.state (@angular/forms/fesm2022/signals.mjs:1250)
at FormField.ɵngControlUpdate (@angular/forms/fesm2022/signals.mjs:1113)
at _GeographyFactsManagementComponent_ng_container_0_Conditional_2_Conditional_42_For_2_Conditional_2_Template
Editing a fact that does have a source works normally.
Root cause
toggleEdit() (geography-facts-management.component.ts:215) seeds the edit draft with undefined when the fact has no source:
and the template binds [formField]="editDraftForm.source" (line 271). When the underlying value for that path is undefined, the FieldTree node is not a callable field, so ɵngControlUpdate → this.field(...) throws.
Confirmed with a three-way probe against the real component (map child stubbed so the template could mount at all):
draft source
result
"USGS" (string)
renders fine
null → ?? undefined
TypeError
key omitted entirely
TypeError
So it is the undefined value, not the null specifically — the same crash occurs for any Partial key that is missing.
GeographicFactDto.source is string | null, and the create form treats source as optional, so facts with no source are the normal case — this is not an edge case.
Impact
Admins cannot edit any fact that was submitted without a source URL. The edit panel simply fails to render (the click appears to do nothing, with an error in the console).
Fix sketch
Two options, both small:
Seed empty strings instead of undefined — source: fact.source ?? "". Matches what the input expects and keeps the FieldTree fully populated. Check saveEdit() so an empty string is normalised back to null/omitted on submit rather than persisting "".
Make the draft a full (non-Partial) model with an EMPTY_EDIT_FACT() factory, mirroring how the create form already does it with EMPTY_CREATE_FACT(). This is the more robust fix — a Partial model behind form() is a latent trap for every optional key, not just source.
Option 2 matches the existing pattern in this same component and the .cursor/rules/signal-forms.md guidance (model signal + schema validators).
Coverage note
geography-facts-management.render.spec.ts contains a characterization test naming this ticket which asserts the current throwing behavior, plus normal-path edit tests that pass a non-null source. Update the characterization test as part of the fix — it is written to fail once editing a source-less fact works.
Found while adding render coverage for `geography-facts-management.component.html` (wave-5 coverage loop, angular branch `test/component-coverage-wave5`).
## Symptom
**Admin → Geography facts → pick a country → Edit** on any fact whose **source is empty** throws during render and the inline edit form fails to appear:
```
TypeError: this.field(...) is not a function
at FormField.state (@angular/forms/fesm2022/signals.mjs:1250)
at FormField.ɵngControlUpdate (@angular/forms/fesm2022/signals.mjs:1113)
at _GeographyFactsManagementComponent_ng_container_0_Conditional_2_Conditional_42_For_2_Conditional_2_Template
```
Editing a fact that **does** have a source works normally.
## Root cause
`toggleEdit()` (`geography-facts-management.component.ts:215`) seeds the edit draft with `undefined` when the fact has no source:
```ts
this.editDraft.set({
title: fact.title,
content: fact.content,
source: fact.source ?? undefined, // <-- fact.source is `string | null`
});
```
The draft is a `Partial<SubmitFactRequest>` behind a Signal Forms FieldTree:
```ts
editDraft = signal<Partial<SubmitFactRequest>>({});
protected readonly editDraftForm = form(this.editDraft);
```
and the template binds `[formField]="editDraftForm.source"` (line 271). When the underlying value for that path is `undefined`, the FieldTree node is not a callable field, so `ɵngControlUpdate` → `this.field(...)` throws.
Confirmed with a three-way probe against the real component (map child stubbed so the template could mount at all):
| draft `source` | result |
| --- | --- |
| `"USGS"` (string) | renders fine |
| `null` → `?? undefined` | **TypeError** |
| key omitted entirely | **TypeError** |
So it is the `undefined` value, not the `null` specifically — the same crash occurs for any `Partial` key that is missing.
`GeographicFactDto.source` is `string | null`, and the create form treats source as optional, so **facts with no source are the normal case** — this is not an edge case.
## Impact
Admins cannot edit any fact that was submitted without a source URL. The edit panel simply fails to render (the click appears to do nothing, with an error in the console).
## Fix sketch
Two options, both small:
1. **Seed empty strings instead of `undefined`** — `source: fact.source ?? ""`. Matches what the input expects and keeps the FieldTree fully populated. Check `saveEdit()` so an empty string is normalised back to `null`/omitted on submit rather than persisting `""`.
2. **Make the draft a full (non-`Partial`) model with an `EMPTY_EDIT_FACT()` factory**, mirroring how the create form already does it with `EMPTY_CREATE_FACT()`. This is the more robust fix — a `Partial` model behind `form()` is a latent trap for every optional key, not just `source`.
Option 2 matches the existing pattern in this same component and the `.cursor/rules/signal-forms.md` guidance (model signal + schema validators).
## Coverage note
`geography-facts-management.render.spec.ts` contains a characterization test naming this ticket which asserts the current throwing behavior, plus normal-path edit tests that pass a non-null source. **Update the characterization test as part of the fix** — it is written to fail once editing a source-less fact works.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Found while adding render coverage for
geography-facts-management.component.html(wave-5 coverage loop, angular branchtest/component-coverage-wave5).Symptom
Admin → Geography facts → pick a country → Edit on any fact whose source is empty throws during render and the inline edit form fails to appear:
Editing a fact that does have a source works normally.
Root cause
toggleEdit()(geography-facts-management.component.ts:215) seeds the edit draft withundefinedwhen the fact has no source:The draft is a
Partial<SubmitFactRequest>behind a Signal Forms FieldTree:and the template binds
[formField]="editDraftForm.source"(line 271). When the underlying value for that path isundefined, the FieldTree node is not a callable field, soɵngControlUpdate→this.field(...)throws.Confirmed with a three-way probe against the real component (map child stubbed so the template could mount at all):
source"USGS"(string)null→?? undefinedSo it is the
undefinedvalue, not thenullspecifically — the same crash occurs for anyPartialkey that is missing.GeographicFactDto.sourceisstring | null, and the create form treats source as optional, so facts with no source are the normal case — this is not an edge case.Impact
Admins cannot edit any fact that was submitted without a source URL. The edit panel simply fails to render (the click appears to do nothing, with an error in the console).
Fix sketch
Two options, both small:
undefined—source: fact.source ?? "". Matches what the input expects and keeps the FieldTree fully populated. ChecksaveEdit()so an empty string is normalised back tonull/omitted on submit rather than persisting"".Partial) model with anEMPTY_EDIT_FACT()factory, mirroring how the create form already does it withEMPTY_CREATE_FACT(). This is the more robust fix — aPartialmodel behindform()is a latent trap for every optional key, not justsource.Option 2 matches the existing pattern in this same component and the
.cursor/rules/signal-forms.mdguidance (model signal + schema validators).Coverage note
geography-facts-management.render.spec.tscontains a characterization test naming this ticket which asserts the current throwing behavior, plus normal-path edit tests that pass a non-null source. Update the characterization test as part of the fix — it is written to fail once editing a source-less fact works.