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.
Verified 2026-08-07 against spikersoft-angular@8e5a404, and live against the deployed main-KZKZUOSX.js bundle on learn.spikersoft.com.
Code: Unchanged. projects/spikersoft/src/app/_components/admin/geography-facts-management/geography-facts-management.component.ts:104-105 — editDraft = signal<Partial<SubmitFactRequest>>({}) behind form(this.editDraft); toggleEdit() at :215-219 still seeds source: fact.source ?? undefined; the template still binds [formField]="editDraftForm.source" at geography-facts-management.component.html:271. The Partial model behind form() is intact, so every optional key carries the same trap.
Live:Confirmed deployed.learn.spikersoft.com → main-KZKZUOSX.js → chunk-rBzRkADA.js contains this.editDraft.set({title:t.title,content:t.content,source:t.source??void 0}) — the exact undefined seed that makes the FieldTree node non-callable. Admins editing a source-less fact hit this in production right now.
Status: not done — The fix (option 2 preferred: a full non-Partial model with an EMPTY_EDIT_FACT() factory, mirroring EMPTY_CREATE_FACT() in the same component and .cursor/rules/signal-forms.md), plus normalising empty strings back to null/omitted in saveEdit(), plus flipping the characterization test in geography-facts-management.render.spec.ts.
Closing here. Work now lives in the repo that holds the fix, so fixes #650 in a PR will auto-close it on merge. The umbrella tracker keeps cross-repo epics only.
— Opus 5 Agent
Migrated to **spikerj/spikersoft-angular#650** as part of the umbrella-tracker breakup.
Verified 2026-08-07 against `spikersoft-angular@8e5a404`, and live against the deployed `main-KZKZUOSX.js` bundle on learn.spikersoft.com.
- **Code:** Unchanged. `projects/spikersoft/src/app/_components/admin/geography-facts-management/geography-facts-management.component.ts:104-105` — `editDraft = signal<Partial<SubmitFactRequest>>({})` behind `form(this.editDraft)`; `toggleEdit()` at `:215-219` still seeds `source: fact.source ?? undefined`; the template still binds `[formField]="editDraftForm.source"` at `geography-facts-management.component.html:271`. The `Partial` model behind `form()` is intact, so every optional key carries the same trap.
- **Live:** **Confirmed deployed.** `learn.spikersoft.com` → `main-KZKZUOSX.js` → `chunk-rBzRkADA.js` contains `this.editDraft.set({title:t.title,content:t.content,source:t.source??void 0})` — the exact `undefined` seed that makes the FieldTree node non-callable. Admins editing a source-less fact hit this in production right now.
Status: **not done** — The fix (option 2 preferred: a full non-`Partial` model with an `EMPTY_EDIT_FACT()` factory, mirroring `EMPTY_CREATE_FACT()` in the same component and `.cursor/rules/signal-forms.md`), plus normalising empty strings back to `null`/omitted in `saveEdit()`, plus flipping the characterization test in `geography-facts-management.render.spec.ts`.
Closing here. Work now lives in the repo that holds the fix, so `fixes #650` in a PR will auto-close it on merge. The umbrella tracker keeps cross-repo epics only.
— Opus 5 Agent
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.Migrated to spikerj/spikersoft-angular#650 as part of the umbrella-tracker breakup.
Verified 2026-08-07 against
spikersoft-angular@8e5a404, and live against the deployedmain-KZKZUOSX.jsbundle on learn.spikersoft.com.projects/spikersoft/src/app/_components/admin/geography-facts-management/geography-facts-management.component.ts:104-105—editDraft = signal<Partial<SubmitFactRequest>>({})behindform(this.editDraft);toggleEdit()at:215-219still seedssource: fact.source ?? undefined; the template still binds[formField]="editDraftForm.source"atgeography-facts-management.component.html:271. ThePartialmodel behindform()is intact, so every optional key carries the same trap.learn.spikersoft.com→main-KZKZUOSX.js→chunk-rBzRkADA.jscontainsthis.editDraft.set({title:t.title,content:t.content,source:t.source??void 0})— the exactundefinedseed that makes the FieldTree node non-callable. Admins editing a source-less fact hit this in production right now.Status: not done — The fix (option 2 preferred: a full non-
Partialmodel with anEMPTY_EDIT_FACT()factory, mirroringEMPTY_CREATE_FACT()in the same component and.cursor/rules/signal-forms.md), plus normalising empty strings back tonull/omitted insaveEdit(), plus flipping the characterization test ingeography-facts-management.render.spec.ts.Closing here. Work now lives in the repo that holds the fix, so
fixes #650in a PR will auto-close it on merge. The umbrella tracker keeps cross-repo epics only.— Opus 5 Agent