WORK-600
ID:WORK-600Status:done

Drop redundant undefined guards in refs and properties literals

createComponentRenderable opens both slot loops with if (v === undefined) continue (component.ts:77 and :98), so inside refs and properties:

Priority:mediumComplexity:trivialMilestone:v0.38.0Source:SPEC-140

Criteria completion

Criteria completion: 4 of 4 (100%) checked; history from Sep 24 to Oct 60%25%50%75%100%Sep 24Oct 6
Branches 3
History 3
  1. 2973df4
    • ☑ No `...(x ? { k: x } : {})` remains inside a `refs` or `properties` object literal
    • ☑ The 20 equivalents on hand-built `Tag` attribute objects are **untouched** — there the guard is load-bearing
    • ☑ `refrakt contracts --check` and `npm run seo:baseline:check` report no drift
    • ☑ `npm test` passes unchanged
    by bjornolofandersson
  2. b94798a
    Created (ready)by bjornolofandersson
  3. 9762a80
    Content editedby Claude
    docs(plan): break down v0.38.0 — rune transform consolidation

Acceptance Criteria

  • No ...(x ? { k: x } : {}) remains inside a refs or properties object literal
  • The 20 equivalents on hand-built Tag attribute objects are untouched — there the guard is load-bearing
  • refrakt contracts --check and npm run seo:baseline:check report no drift
  • npm test passes unchanged

Approach

The sweep is bounded to the two slot literals. Guards on Tag attribute objects must stay: diff.ts:138, form.ts:165/203/204/215/229/230, sandbox.ts:253/254 and the rest write into an attributes record where an undefined value would be emitted as an attribute.

null and '' are also safe to pass through, not just undefined: Markdoc.Tag.isTag(null) is false, so a null slot value is a no-op in both loops. The guard is redundant for every falsy value a rune actually produces.

References

  • SPEC-140 — Tier 1
  • ADR-008 — the flat namespace the slot loops enforce, which must keep working

Resolution

Completed: 2026-10-06

Branch: claude/v0-37-post-release-plan-dc3va1

What was done

  • 52 ...(x ? { k: x } : {}) → k: x inside properties / refs literals across 23 files, via a brace-matching rewrite scoped to the two slot literals and to sites where condition and value are the same expression.
  • component.ts: slot types admit null, and the loops skip v == null. They already treated null as a no-op (Tag.isTag(null) is false); widening the type avoided retyping every Tag | null declaration (plan-history's idMeta was the first to fail the build).

Notes

  • Six guards stay — condition ≠ value, so they are load-bearing: drawer footer ? footer.tag('footer') (would throw), figure imgs.length > 0, budget hasPerDay, character/realm/faction hasSections.
  • Converting a guard makes the key present-with-undefined, which Object.keys in the ADR-008 collision check would see. A static scan of every converted call found no key in both maps.
  • Attribute-object guards (diff.ts, form.ts, sandbox.ts, …) untouched.