You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The factory type surface in packages/core/src/component.d.ts declares the same set of static members twice: once on the WebComponentConstructor interface, and again, identically, on the factory-call return type. The duplicated block is:
Because the list is hand-maintained in two places, adding or changing any static on WebComponent requires editing both copies or the constructor type and the factory-return type silently drift. This is a maintenance hazard, not a current bug (the two copies are in sync today).
Design / approach
Declare the static surface ONCE and reference it from both positions. Two viable shapes:
Extract a WebComponentStatics interface and intersect it into both the WebComponentConstructor interface and the factory-call return type.
Prefer whichever keeps the existing InferProps<S> instance typing and the call-signature overload intact. Option 1 is the smaller, lower-risk change; option 2 removes the most duplication but couples the constructor type to the base-class static declarations. Implementer's choice, validated by the type tests below.
Implementation notes (for the implementing agent)
Where to edit: packages/core/src/component.d.ts, the WebComponentConstructor interface (the static block on the construct signature) and the factory-call return type (the <S extends Record<string, any>>(shape: S): { ... } overload's static block). They are adjacent in the file.
Landmine: this is the type-only .d.ts overlay. There is no runtime change and must not be one. Do not touch component.js.
Landmine: the factory return must keep carrying the statics so MyComp.register('x-tag'), MyComp.shadow, etc. typecheck on a class X extends WebComponent({...}). Losing them is the regression to guard against.
Landmine: keep InferProps<S> on the instance type (new (): WebComponentBase & InferProps<S>) so factory-declared props stay typed on instances.
Invariant: packages/ is buildless plain JS + JSDoc (root AGENTS.md, framework-repo section). .d.ts overlays are fine and erased at runtime; do not introduce a .ts source file.
Invariant: index.d.ts must still declare every runtime named export (packages/core/AGENTS.md, dts-export-coverage). If you newly exportWebComponentBase, ensure the export-coverage test (test/types/dts-export-coverage.test.mjs) stays green.
Tests: the type-level tests live in test/types/component-types.test-d.ts (factory usage: WebComponent({...}), prop<T>(), .register(...), InferProps assertions). These are the regression net. Run webjs typecheck.
Acceptance criteria
The static member list (shadow, hydrate, properties, styles, lazy, register, observedAttributes) is declared in exactly one place and referenced from both the constructor type and the factory-call return type
class X extends WebComponent({...}) still typechecks X.register('x-tag'), X.shadow, and instance prop access (InferProps)
test/types/component-types.test-d.ts passes unchanged (or with only additive assertions)
Counterfactual: removing a static from the shared declaration makes the type tests fail (proves both positions actually consume the shared source)
webjs typecheck is green; no runtime change to component.js
No public-surface change, so no docs update expected
Problem
The factory type surface in
packages/core/src/component.d.tsdeclares the same set of static members twice: once on theWebComponentConstructorinterface, and again, identically, on the factory-call return type. The duplicated block is:Because the list is hand-maintained in two places, adding or changing any static on
WebComponentrequires editing both copies or the constructor type and the factory-return type silently drift. This is a maintenance hazard, not a current bug (the two copies are in sync today).Design / approach
Declare the static surface ONCE and reference it from both positions. Two viable shapes:
WebComponentStaticsinterface and intersect it into both theWebComponentConstructorinterface and the factory-call return type.extends Omit<typeof WebComponentBase, 'new'>(the approach an earlier uncommitted WIP draft from the feat: declare-free reactive-prop DX via dual-role WebComponent #597 / feat: enforce declare-free factory DX (drop static properties + declare) #599 declare-free-factory work used). This requiresWebComponentBaseto beexported and its statics to be declared on the class.Prefer whichever keeps the existing
InferProps<S>instance typing and the call-signature overload intact. Option 1 is the smaller, lower-risk change; option 2 removes the most duplication but couples the constructor type to the base-class static declarations. Implementer's choice, validated by the type tests below.Implementation notes (for the implementing agent)
packages/core/src/component.d.ts, theWebComponentConstructorinterface (the static block on the construct signature) and the factory-call return type (the<S extends Record<string, any>>(shape: S): { ... }overload's static block). They are adjacent in the file..d.tsoverlay. There is no runtime change and must not be one. Do not touchcomponent.js.MyComp.register('x-tag'),MyComp.shadow, etc. typecheck on aclass X extends WebComponent({...}). Losing them is the regression to guard against.InferProps<S>on the instance type (new (): WebComponentBase & InferProps<S>) so factory-declared props stay typed on instances.packages/is buildless plain JS + JSDoc (root AGENTS.md, framework-repo section)..d.tsoverlays are fine and erased at runtime; do not introduce a.tssource file.index.d.tsmust still declare every runtime named export (packages/core/AGENTS.md, dts-export-coverage). If you newlyexportWebComponentBase, ensure the export-coverage test (test/types/dts-export-coverage.test.mjs) stays green.test/types/component-types.test-d.ts(factory usage:WebComponent({...}),prop<T>(),.register(...),InferPropsassertions). These are the regression net. Runwebjs typecheck.Acceptance criteria
shadow,hydrate,properties,styles,lazy,register,observedAttributes) is declared in exactly one place and referenced from both the constructor type and the factory-call return typeclass X extends WebComponent({...})still typechecksX.register('x-tag'),X.shadow, and instance prop access (InferProps)test/types/component-types.test-d.tspasses unchanged (or with only additive assertions)webjs typecheckis green; no runtime change tocomponent.js