@symbiote-native/angular 3.1.1 → 3.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/build/angular/change-detection-flush.d.ts +2 -3
  2. package/build/angular/change-detection-flush.js +7 -11
  3. package/build/angular/change-detection-flush.js.map +1 -1
  4. package/build/angular/components/image-shared.js +6 -9
  5. package/build/angular/components/image-shared.js.map +1 -1
  6. package/build/angular/components/input-accessory-view-props.js +3 -4
  7. package/build/angular/components/input-accessory-view-props.js.map +1 -1
  8. package/build/angular/components/pressable-props.js +2 -4
  9. package/build/angular/components/pressable-props.js.map +1 -1
  10. package/build/angular/components/refresh-control-props.js +3 -5
  11. package/build/angular/components/refresh-control-props.js.map +1 -1
  12. package/build/angular/components/safe-area-view-props.js +3 -5
  13. package/build/angular/components/safe-area-view-props.js.map +1 -1
  14. package/build/angular/components/scroll-view-props.js +3 -6
  15. package/build/angular/components/scroll-view-props.js.map +1 -1
  16. package/build/angular/components/text-input-props.js +3 -4
  17. package/build/angular/components/text-input-props.js.map +1 -1
  18. package/build/angular/components/touchable-props.js +3 -5
  19. package/build/angular/components/touchable-props.js.map +1 -1
  20. package/build/angular/components/virtualized-list/index.js +9 -19
  21. package/build/angular/components/virtualized-list/index.js.map +1 -1
  22. package/build/angular/create-portal/index.js +3 -6
  23. package/build/angular/create-portal/index.js.map +1 -1
  24. package/build/angular/elements.js +4 -17
  25. package/build/angular/elements.js.map +1 -1
  26. package/build/angular/gate-demand.js +1 -1
  27. package/build/angular/gate-demand.js.map +1 -1
  28. package/build/angular/index.js +6 -28
  29. package/build/angular/index.js.map +1 -1
  30. package/build/angular/modules/animated/create-animated-component.js.map +1 -1
  31. package/build/angular/primitives/index.d.ts +0 -15
  32. package/build/angular/primitives/index.js +5 -19
  33. package/build/angular/primitives/index.js.map +1 -1
  34. package/build/angular/primitives/shared.js +3 -6
  35. package/build/angular/primitives/shared.js.map +1 -1
  36. package/build/angular/renderer/index.js +6 -24
  37. package/build/angular/renderer/index.js.map +1 -1
  38. package/metro-config.cjs +3 -24
  39. package/package.json +6 -6
  40. package/src/change-detection-flush.ts +7 -11
  41. package/src/components/image-shared.ts +6 -9
  42. package/src/components/input-accessory-view-props.ts +3 -4
  43. package/src/components/pressable-props.ts +2 -4
  44. package/src/components/refresh-control-props.ts +3 -5
  45. package/src/components/safe-area-view-props.ts +3 -5
  46. package/src/components/scroll-view-props.ts +3 -6
  47. package/src/components/text-input-props.ts +3 -4
  48. package/src/components/touchable-props.ts +3 -5
  49. package/src/components/virtualized-list/index.ts +6 -12
  50. package/src/create-portal/index.ts +3 -6
  51. package/src/elements.ts +10 -26
  52. package/src/gate-demand.ts +1 -1
  53. package/src/index.ts +6 -28
  54. package/src/modules/animated/create-animated-component.ts +3 -7
  55. package/src/primitives/index.ts +5 -19
  56. package/src/primitives/shared.ts +3 -6
  57. package/src/renderer/index.ts +6 -24
@@ -1,8 +1,6 @@
1
- // The input surface and prop fold `AnimatedImage` builds on. The `Image` COMPONENT that used to
2
- // share it is gone (2026-09-10) — the primitive is the `<image>` tag, matched by `ImageElement`,
3
- // and `registerImageBehavior` folds its source on that path. `Animated.Image` is not a primitive
4
- // and stays a component, so what it needs from here survived the deletion; the folder collapsed to
5
- // this one flat file with it (`symbiote-file-layout`).
1
+ // The input surface and prop fold `AnimatedImage` builds on. The primitive is the `<image>` tag
2
+ // (`ImageElement`), which folds its source via `registerImageBehavior`; `Animated.Image` stays a
3
+ // component and is the only consumer left, one flat file (`symbiote-file-layout`).
6
4
  import { EventEmitter, computed, signal } from '@angular/core';
7
5
  import {
8
6
  gateWanted,
@@ -562,10 +560,9 @@ export abstract class ImageBase {
562
560
  this.inputsRevision.update(revision => revision + 1);
563
561
  }
564
562
 
565
- // MEASURED 2026-08-16: as a getter this rebuilt the bag on every refresh of this view, so
566
- // `[symbioteHostProps]`'s reference check failed every time and every key was re-pushed through
567
- // Renderer2 -> routeProp - pure waste when nothing changed. computed() returns the SAME object
568
- // until a tracked dependency actually changes, and the input setter then skips the whole spread.
563
+ // A getter would rebuild the bag on every refresh, failing `[symbioteHostProps]`'s reference
564
+ // check and re-pushing every key through Renderer2 -> routeProp for nothing. computed() returns
565
+ // the SAME object until a tracked dependency changes, so the input setter skips the whole spread.
569
566
  //
570
567
  // A computed FIELD cannot be overridden by a subclass accessor the way a getter can, so the
571
568
  // platform components override buildImageProps() below and this stays the single memoization
@@ -1,7 +1,6 @@
1
- // The `<input-accessory-view>` tag's prop surface. The tag is matched by
2
- // `InputAccessoryViewElement` (`../elements`) and commits one `RCTInputAccessoryView`; its whole
3
- // wrapper was a prop FOLD with no aliasing at all (`registerInputAccessoryViewBehavior`), so it was
4
- // deleted 2026-09-10 and the behavior applies the same mapping on the tag's own path.
1
+ // The `<input-accessory-view>` tag's prop surface. The tag (`InputAccessoryViewElement`) commits
2
+ // one `RCTInputAccessoryView`; `registerInputAccessoryViewBehavior` folds its props on that path,
3
+ // no separate wrapper component needed.
5
4
  //
6
5
  // The type stays for a component forwarding a bag, and because `IAngularInputAccessoryViewProps` is
7
6
  // public API.
@@ -1,7 +1,5 @@
1
- // The `<pressable>` tag's prop surface. The tag is matched by `PressableElement`
2
- // (`../elements`) and carries the press machine as an engine behavior
3
- // (`registerPressableBehavior`), so the wrapper that used to run it in Angular was deleted
4
- // 2026-09-10.
1
+ // The `<pressable>` tag's prop surface. The tag (`PressableElement`) carries the press machine
2
+ // as an engine behavior (`registerPressableBehavior`), no separate wrapper component.
5
3
  //
6
4
  // The type stays for a component forwarding a bag, for the four `Touchable*` prop types that share
7
5
  // this base, and because `IAngularPressableProps` is public API. Mirrors React's IPressableProps
@@ -1,8 +1,6 @@
1
- // The `<refresh-control>` tag's prop surface. The tag is matched by `RefreshControlElement`
2
- // (`../elements`) and carries the controlled-spinner handshake as an engine behavior
3
- // (`registerRefreshControlBehavior` — mirror what native last reported, command it back when the
4
- // app's `refreshing` disagrees, RefreshControl.js:145-166), so the wrapper that ran that in Angular
5
- // was deleted 2026-09-11.
1
+ // The `<refresh-control>` tag's prop surface. The tag (`RefreshControlElement`) carries the
2
+ // controlled-spinner handshake as an engine behavior (`registerRefreshControlBehavior`: mirror
3
+ // what native last reported, command it back when `refreshing` disagrees), no wrapper component.
6
4
  //
7
5
  // WHERE it goes is the ScrollView's decision, not this tag's: write it as an ordinary FIRST CHILD
8
6
  // on both platforms and the scroll-view behavior re-parents it — iOS a sibling before the content
@@ -1,8 +1,6 @@
1
- // The `<safe-area-view>` tag's prop surface. The tag is matched by `SafeAreaViewElement`
2
- // (`../elements`) and commits a single `SafeAreaView` native view whose host insets its children to
3
- // the safe area; there is no JS-side translation to do, so the wrapper that used to fold
4
- // aria/role and forward a host bag was deleted 2026-09-10 — the engine's own aria fold and the
5
- // renderer's `id` -> `nativeID` alias cover both, on every path.
1
+ // The `<safe-area-view>` tag's prop surface. The tag (`SafeAreaViewElement`) commits a single
2
+ // `SafeAreaView` native view that insets its children; no wrapper component needed, since the
3
+ // engine's own aria fold and the renderer's `id` -> `nativeID` alias already cover both.
6
4
  //
7
5
  // The type stays for a component forwarding a bag, and because `IAngularSafeAreaViewProps` is
8
6
  // public API. Mirrors React's ISafeAreaViewProps minus children, which Angular takes via
@@ -1,12 +1,9 @@
1
1
  // The `<scroll-view>` / `<horizontal-scroll-view>` tag's prop surface — the axis is the TAG you
2
2
  // write, not a `horizontal` binding, because Android resolves a different native class per axis.
3
3
  //
4
- // The wrapper was deleted 2026-09-11 and nothing replaced it in the adapter: the ENGINE owns the
5
- // whole shape now (`core/components/src/behaviors/scroll-view/`). `buildStructure` builds the
6
- // content node the children go under, `slotProps` routes `contentContainerStyle` onto it as its
7
- // `style`, `payloadFold` composes the two base styles, and the sticky machinery pins by DOCUMENT
8
- // ORDER through the `<sticky-header>` tag — so the Angular projection bridge that used to wrap
9
- // projected children by index is gone with it, along with its renderer hooks.
4
+ // The ENGINE owns the whole shape (`core/components/src/behaviors/scroll-view/`), no adapter
5
+ // wrapper: `buildStructure` builds the content node, `slotProps` routes `contentContainerStyle`,
6
+ // `payloadFold` composes the base styles, sticky pins by DOCUMENT ORDER via `<sticky-header>`.
10
7
  //
11
8
  // A `<refresh-control>` is an ordinary FIRST CHILD on both platforms; the behavior re-parents it
12
9
  // per platform (iOS a sibling before the content, Android the swipe layout WRAPPING the scroll
@@ -1,7 +1,6 @@
1
- // The `<text-input>` / `<text-input-multiline>` tag's prop surface. The tags are matched by
2
- // `TextInputElement` / `MultilineTextInputElement` (`../elements`) and carry the controlled-input
3
- // machine as an engine behavior (`registerTextInputBehavior`), so the wrapper that used to run the
4
- // handshake, the imperative handle and the autoFocus command in Angular was deleted 2026-09-11.
1
+ // The `<text-input>` / `<text-input-multiline>` tag's prop surface. The tags
2
+ // (`TextInputElement`/`MultilineTextInputElement`) carry the controlled-input handshake, the
3
+ // imperative handle and the autoFocus command as an engine behavior (`registerTextInputBehavior`).
5
4
  //
6
5
  // Which of the two intrinsics a `<text-input multiline>` commits is the ENGINE's decision
7
6
  // (`intrinsicWhen` on the spec entry), not a template branch — the wrapper's `@if`/`@else` over two
@@ -1,10 +1,8 @@
1
1
  // The prop surfaces of `<touchable-opacity>` and `<touchable-highlight>`, for Angular.
2
2
  //
3
- // No components left to type — the elements ARE the tags, matched by `TouchableOpacityElement` and
4
- // `TouchableHighlightElement` (`../elements`), and both machines run on the engine node
5
- // (`registerTouchableOpacityBehavior` / `registerTouchableHighlightBehavior`): the opacity fade is
6
- // an engine-side style layer over the responder, and the underlay swap is the same `activeStyle`
7
- // channel a `:active` rule uses. The wrappers that ran those in Angular were deleted 2026-09-11.
3
+ // No components left to type — the elements ARE the tags (`TouchableOpacityElement` /
4
+ // `TouchableHighlightElement`), both running on the engine node: opacity fade is an engine-side
5
+ // style layer over the responder, underlay swap rides the same `activeStyle` a `:active` rule uses.
8
6
  //
9
7
  // Declared per-adapter over the Angular Pressable INPUT surface, since children arrive through
10
8
  // content projection rather than as a field — the split
@@ -362,13 +362,9 @@ interface IWindowCell<ItemT> {
362
362
  @if (gapSpacerStyle !== null) {
363
363
  <view [style]="gapSpacerStyle"></view>
364
364
  }
365
- <!-- The separator sits INSIDE the measuring view, as RN's own cell renderer places it
366
- (VirtualizedListCellRenderer.js:218-221). As a sibling it is an extra flex child, so
367
- the chrome between two cells is gap + separator + gap while a spacer collapsing that
368
- region replaces it with a single gap — every cell below the leading spacer then lands
369
- short by (separator + gap) and the content jumps by that much whenever the window's
370
- first index moves. Measured at exactly 17px on device 2026-08-19; see
371
- .claude/rules/list-geometry-feedback-loop.md. -->
365
+ <!-- Separator sits INSIDE the measuring view, matching RN's cell renderer
366
+ (VirtualizedListCellRenderer.js:218-221) — as a sibling it'd add an extra flex gap.
367
+ See list-geometry-feedback-loop.md. -->
372
368
  @for (cell of windowCells; track cell.key) {
373
369
  @if (cell.isSticky) {
374
370
  <sticky-header
@@ -1205,11 +1201,9 @@ export class VirtualizedList<ItemT = unknown>
1205
1201
 
1206
1202
  const cells: IWindowCell<ItemT>[] = [];
1207
1203
  for (const planned of plan.cells) {
1208
- // RN gates the separator on the last index of the DATA, not of the WINDOW
1209
- // (VirtualizedList.js:793 `const end = getItemCount(data) - 1`), and now that the separator
1210
- // lives INSIDE the measuring wrapper that distinction is load-bearing: gating on the window
1211
- // would make a cell's own measured height change as the window slides past it. Device-measured
1212
- // 2026-08-19 as a run of cells all shifting by exactly the divider's 1px.
1204
+ // RN gates the separator on the last index of the DATA, not the WINDOW
1205
+ // (VirtualizedList.js:793 `getItemCount(data) - 1`). The separator lives INSIDE the
1206
+ // measuring wrapper, so gating on the window would shift a cell's height as it slides past.
1213
1207
  const includeSeparator = hasSeparators && planned.index < m.count - 1;
1214
1208
  cells.push(
1215
1209
  this.buildWindowCell(
@@ -27,12 +27,9 @@
27
27
  // resolving a template reference variable, so `strictTemplates` rejects anything else at compile
28
28
  // time and there is nothing left to guard at runtime.
29
29
  //
30
- // THE MARKER GOES ON AN `<ng-container>` INSIDE THE TARGET, never on the target element. A
31
- // ViewContainerRef anchors AT its host and `createEmbeddedView` inserts after that anchor, so
32
- // `<view portalOutlet>` delivers a SIBLING where React's `createPortal(node, host)` and Vue's
33
- // `<Teleport to>` deliver a child. Device-reported 2026-09-02: a card portaled into an absolutely
34
- // positioned overlay laid out in the scroll flow, since the overlay cannot centre a node that is
35
- // not in it.
30
+ // THE MARKER GOES ON AN `<ng-container>` INSIDE THE TARGET, never on the target element:
31
+ // ViewContainerRef anchors AT its host, so `<view portalOutlet>` delivers a SIBLING where React's
32
+ // `createPortal` and Vue's `<Teleport to>` deliver a child — breaks centering inside an overlay.
36
33
 
37
34
  import {
38
35
  Directive,
package/src/elements.ts CHANGED
@@ -105,11 +105,9 @@ export abstract class SymbioteElement implements OnChanges {
105
105
  // MATCHES on the callback attributes instead, registering it against the node; `markViewFor` is how
106
106
  // the wrapper reaches it. The inputs did not move, so nothing about this file's public surface did.
107
107
 
108
- // THE `on*` WRAPPER LEFT THIS CLASS on 2026-09-18, for `SymbioteRenderer.setProperty`. Most of
109
- // these directives are withheld from runtime matching now (`./runtime-matching`), so a binding
110
- // reaches the renderer through `ɵɵproperty` without passing through any directive — a wrapper that
111
- // lives here would simply stop running. The renderer is reached by every path, which is the home it
112
- // should have had.
108
+ // No `on*` wrapper here: `SymbioteRenderer.setProperty` handles it. Most directives are withheld
109
+ // from runtime matching (`./runtime-matching`), so a binding reaches the renderer through
110
+ // `ɵɵproperty` without passing through any directive — the renderer is reached by every path.
113
111
 
114
112
  @Input() testID?: IElementProps['testID'];
115
113
  @Input() nativeID?: IElementProps['nativeID'];
@@ -255,22 +253,9 @@ abstract class ReadBackElement extends SymbioteElement implements OnDestroy {
255
253
  }
256
254
  }
257
255
 
258
- // REPORTED, NOT RETHROWN, and it is Angular's own contract rather than a swallow. Every other
259
- // change detection in an app runs inside `ApplicationRef.tick()`, which catches and hands the
260
- // error to `ErrorHandler` — `render/index.ts` provides `SymbioteErrorHandler` for exactly that,
261
- // after an unprovided token once turned every async tick exception into a hard crash. This flush
262
- // is the ONE change detection that runs outside that boundary: the engine calls it from inside a
263
- // native event dispatch, where a throw has nowhere to go but `RCTFatal`.
264
- //
265
- // Device-diagnosed 2026-09-20 on ApiPlaygroundScreen. `PlaygroundLifecycleLogger` emits from
266
- // `ngDoCheck`/`ngAfterContentChecked`/`ngAfterViewChecked` and the screen's handler writes a
267
- // signal its own template reads, so the view re-dirties itself on every pass and
268
- // `detectChangesInViewWhileDirty` throws NG0103 after MAXIMUM_REFRESH_RERUNS. The scheduler's own
269
- // ticks were already hitting it and reporting it quietly; the first KEYSTROKE took the same throw
270
- // through here and killed the app — `Terminating app due to uncaught exception
271
- // 'RCTFatalException: Unhandled JS Exception: Error: NG0103'`, with no redbox because Release has
272
- // none. So the app bug is the app's, and a flush that turns a reported error into a fatal one is
273
- // ours: a keystroke must not be stricter than a tick.
256
+ // REPORTED, NOT RETHROWN — Angular's own contract. Every other change detection runs inside
257
+ // `ApplicationRef.tick()`, caught by `ErrorHandler` (`SymbioteErrorHandler`); this flush instead
258
+ // runs from a native event dispatch, where an uncaught throw has nowhere to go but `RCTFatal`.
274
259
  private flush(): void {
275
260
  try {
276
261
  this.detector.detectChanges();
@@ -739,7 +724,7 @@ abstract class SymbioteValueAccessor
739
724
  // mirrors it, which is the shape rather than a workaround. A later `writeValue` still wins,
740
725
  // so an app that transforms or refuses the value keeps doing so, one microtask on.
741
726
  //
742
- // Device-reported 2026-09-20: every character snapped the field back to its mounted text.
727
+ // Without this, every character snaps the field back to its mounted text.
743
728
  this.setProp('value', value);
744
729
  fn(value);
745
730
  },
@@ -945,10 +930,9 @@ type IMissingInputs<TProps, TDirective, TIgnored extends PropertyKey = never> =
945
930
  const DECLARES_EVERY_PROP: {
946
931
  view: IMissingInputs<IElementProps, ViewElement>;
947
932
  pressable: IMissingInputs<IAngularPressableProps, PressableElement>;
948
- // The two touchables, added when their wrappers were deleted (2026-09-11). They inherit
949
- // `PressableElement`, so the rows above would pass whatever these declared — what they pin is the
950
- // per-touchable surface (`activeOpacity`, `underlayColor`, the three timing knobs), which used to
951
- // be fenced by a source-text assertion over the wrapper's template in `angular-gaps.test.ts`.
933
+ // The two touchables inherit `PressableElement`, so the rows above would pass whatever these
934
+ // declared — what they pin is the per-touchable surface (`activeOpacity`, `underlayColor`, the
935
+ // three timing knobs).
952
936
  touchableOpacity: IMissingInputs<
953
937
  IAngularTouchableOpacityProps,
954
938
  TouchableOpacityElement
@@ -18,7 +18,7 @@
18
18
  // nothing in this repo could detect and nothing on screen would show. A `viewProviders` entry is
19
19
  // not part of the component's binding surface, so an app cannot reach it by accident.
20
20
  //
21
- // WHY `viewProviders` AND NOT `providers` — measured, both arms, 2026-09-02:
21
+ // WHY `viewProviders` AND NOT `providers`:
22
22
  //
23
23
  // viewProviders the wrapper's OWN template sees it, projected content does NOT
24
24
  // providers both see it
package/src/index.ts CHANGED
@@ -5,12 +5,9 @@
5
5
  // SEAM SCAFFOLD: mount/unmount + the renderer seam + host intrinsic selectors. Full RN-like
6
6
  // composed components still flow through the shared @symbiote-native/components bridge.
7
7
  //
8
- // `View` AND `Text` ARE NO LONGER WORTH IMPORTING, and on 2026-09-18 the examples stopped. They are
9
- // `@Component`s on the `view` and `text` SELECTORS — a second mechanism on the same tags
10
- // `SYMBIOTE_ELEMENTS` covers, kept for one stated reason their own header gives: "Declaring `style`
11
- // as a real Angular input prevents Angular's CSS style engine from decomposing RN `StyleProp`
12
- // arrays". A tag takes an array or press-state callback as `[styleProp]`, a plain property binding
13
- // with no instance behind it.
8
+ // `View` AND `Text` ARE NOT WORTH IMPORTING — a tag covers the same selectors via
9
+ // `SYMBIOTE_ELEMENTS`. They stay as `@Component`s only because a real Angular `style` input stops
10
+ // Angular's CSS engine decomposing RN `StyleProp` arrays; a tag takes it as `[styleProp]` instead.
14
11
  //
15
12
  // An app loses nothing it cannot spell better: `@ViewChild('ref')` on a template reference returns
16
13
  // Angular's own `ElementRef<IHostInstance>`, whose `nativeElement` IS the engine host node — the same
@@ -137,28 +134,9 @@ export type {
137
134
  ITextInputChangeEvent,
138
135
  } from './components';
139
136
  export { setImageSourceResolver } from './components';
140
- // The element directives that make a HAND-WRITTEN intrinsic tag (`<view>`, `<text-input>`, ...)
141
- // compile under ngtsc with no schema, and with a real type on every declared prop. See `elements.ts`
142
- // for why a directive rather than `CUSTOM_ELEMENTS_SCHEMA`/`NO_ERRORS_SCHEMA`.
143
- //
144
- // `imports: [SYMBIOTE_ELEMENTS]` IS THE ONLY SUPPORTED SPELLING — and the individual classes are
145
- // exported anyway, because ngtsc requires it and a build proves it.
146
- //
147
- // They were removed on 2026-09-18 and put back the same hour. The reason to remove them is real: a
148
- // withheld tag directive (`./runtime-matching`) is a compile-time declaration and nothing else, so
149
- // what makes `<view [onPress]="fn">` work at all is `SymbioteCallbackHost` claiming the name before
150
- // `setDomProperty` throws NG0306. It rides this array, so a narrow `imports: [ViewElement]`
151
- // type-checks and then fails on a device the first time the app binds an `on*` prop.
152
- //
153
- // WHAT PUTS THEM BACK is NG3004: `Unable to import directive ViewElement — the symbol is not
154
- // exported from index.d.ts`. ngtsc resolves every directive reachable through an imported array to an
155
- // IMPORTABLE NAME in the package's public types, so a class inside `SYMBIOTE_ELEMENTS` that the
156
- // barrel does not name breaks AOT for every screen that uses the array. The removal passed its own
157
- // vitest guard and every headless suite, and failed on the first real `ngc` run — the same
158
- // wrong-harness green this repo keeps finding.
159
- //
160
- // So the narrow spelling is DISCOURAGED and not prevented, and `elements-are-exported-for-aot.test.ts`
161
- // now asserts the opposite invariant: every member of the array is named here.
137
+ // Element directives for a hand-written intrinsic tag (`<view>`, `<text-input>`, ...); see
138
+ // `elements.ts` for why a directive rather than a schema. `imports: [SYMBIOTE_ELEMENTS]` is the
139
+ // only supported spelling — a narrower array type-checks but fails AOT's NG3004 on-device.
162
140
  export {
163
141
  SYMBIOTE_ELEMENTS,
164
142
  SymbioteElement,
@@ -409,13 +409,9 @@ export class AnimatedImage
409
409
  changeDetection: ChangeDetectionStrategy.OnPush,
410
410
  })
411
411
  export class AnimatedScrollView extends AnimatedComponentBase {
412
- // Nothing left to override: `registerScrollViewBehavior()` (`../../register.ts`) now owns the
413
- // content node, `scrollViewBaseStyle`/`nestedScrollEnabled` defaults, and the sticky seam for
414
- // this SAME `scroll-view` tag. This class used to build its own `<scroll-content>` child by
415
- // hand — deleted 2026-09-11, since that made TWO owners of the content node the moment the
416
- // behavior registered on this tag: the engine's own `buildStructure` and this template both
417
- // creating one. `<ng-content>` now lands directly under the owner and the behavior redirects it
418
- // into the content node it builds, same as every other `<scroll-view>` in the app.
412
+ // Nothing left to override: `registerScrollViewBehavior()` owns the content node, base style
413
+ // and sticky seam for this SAME `scroll-view` tag. Building `<scroll-content>` here too would
414
+ // give the node two owners; `<ng-content>` lands under the one the behavior already builds.
419
415
  }
420
416
 
421
417
  // List components are already explicit AOT-compiled Angular components with their own full input
@@ -21,21 +21,8 @@ export {
21
21
  })
22
22
  export class ViewHost extends SymbiotePrimitiveHost {}
23
23
 
24
- /**
25
- * Text is an ORDINARY primitive host as of 2026-09-18, and the two `@Input()`s it used to declare
26
- * are gone with the code that needed them.
27
- *
28
- * They existed for one stated reason: a default can only be applied by code that can SEE whether the
29
- * caller supplied a value, and a pass-through host binding is invisible to the component — so the
30
- * defaults had to be applied where the inputs were readable. That argument was sound and it is now
31
- * answered one layer down. The payload builder reads the AUTHORED bag, so it can tell an absent
32
- * `ellipsizeMode` from an explicit `clip` without anyone declaring anything; the rule is keyed on the
33
- * component (`foldTextDefaults`, `SymbioteFabricProps.cpp`) and reaches every `RCTText` however it
34
- * was spelled.
35
- *
36
- * The pass-through is therefore the CORRECT path for both props now, and `text-defaults.test.ts`'s
37
- * "never overwrites a value the caller supplied" is what proves the authored value still arrives.
38
- */
24
+ // Text is an ordinary primitive host — no `@Input()`s for defaults; `foldTextDefaults` (C++)
25
+ // reads the authored bag directly, so an absent value never needs declaring here.
39
26
  @Component({
40
27
  selector: 'text',
41
28
  standalone: true,
@@ -99,10 +86,9 @@ export class MultilineTextInputHost extends SymbiotePrimitiveHost {}
99
86
  })
100
87
  export class SwitchHost extends SymbiotePrimitiveHost {}
101
88
 
102
- // The centering RCTView RN wraps the spinner in (ActivityIndicator.js:112), not the spinner —
103
- // which is what this tag resolved to until 2026-09-09. The native view moved to
104
- // `activity-indicator-spinner`, built by the engine's ActivityIndicator behavior, so nothing writes
105
- // it in a template and it needs no host here.
89
+ // The centering RCTView RN wraps the spinner in (ActivityIndicator.js:112) is
90
+ // `activity-indicator-spinner`, built by the engine's ActivityIndicator behavior — nothing writes
91
+ // it in a template, so it needs no host here.
106
92
  @Component({
107
93
  selector: 'activity-indicator',
108
94
  standalone: true,
@@ -287,12 +287,9 @@ export class SymbioteHostPropsDirective {
287
287
  return this.elementRef.nativeElement;
288
288
  }
289
289
 
290
- // MEASURED 2026-08-20, headless 1000-row create: writing every key of every bag cost 104 000
291
- // engine setProp calls where Solid's identical screen cost 12 000 — and 90 000 of them carried
292
- // `undefined`. A composed component's bag is a FIXED-SHAPE literal (Pressable's is ~48 keys,
293
- // most of them unset on any given instance), so the first push of a fresh node spends most of
294
- // its work deleting keys the node never had. Diffing per key removes exactly that: an unset key
295
- // is skipped on mount and still written the moment it changes to or from a real value.
290
+ // A composed component's bag is a FIXED-SHAPE literal, most keys unset on any given instance, so
291
+ // writing every key on the first push spends most of its work on keys the node never had.
292
+ // Diffing per key skips an unset one on mount and writes it only when it gains a real value.
296
293
  set symbioteHostProps(props: Record<string, unknown>) {
297
294
  const pushed = this.pushed;
298
295
  // Only keys the node actually carries, so the vanished-key sweep below walks a handful rather
@@ -62,17 +62,9 @@ function isRecord(value: unknown): value is Record<string, unknown> {
62
62
  return typeof value === 'object' && value !== null;
63
63
  }
64
64
 
65
- // RN's TWO TEXT DEFAULTS LEFT THIS RENDERER ENTIRELY ON 2026-09-18, in two steps a fortnight apart.
66
- // The SEED went first, to the payload builder — writing them as props cost a crossing every time an
67
- // app authored the same value (6 000 per 1 000-row create, `writesOfUnchanged`). What stayed was a
68
- // resolver for the clear-back path: a write of `undefined` looked up the default instead of clearing.
69
- //
70
- // That is gone too, and for the reason the seed was: `applyTextDefaults` (and its C++ twin) runs on
71
- // EVERY commit of every `RCTText`, so a cleared key is absent for exactly as long as it takes the
72
- // payload builder to supply the default again. The resolver was answering a question nothing asks.
73
- //
74
- // `PROP_ALIASES` (`id` -> `nativeID`) left this renderer on 2026-09-18 — `routeProp` resolves it
75
- // for every adapter now, so every path that can set a prop still reaches it.
65
+ // RN's two TEXT DEFAULTS and `PROP_ALIASES` (`id` -> `nativeID`) are NOT resolved here: text
66
+ // defaults run once per commit in `applyTextDefaults` (C++ twin included), and `routeProp` resolves
67
+ // aliases for every adapter — every path that can set a prop still reaches both.
76
68
  // Angular's two-way sugar `[(value)]` compiles to a `(valueChange)` binding; the engine knows the
77
69
  // same fold as the function prop `onValueChange`. See `listen()`. The two names live in a leaf
78
70
  // module so `elements.ts`'s ControlValueAccessor can name them without importing this cyclic file.
@@ -636,19 +628,9 @@ export class SymbioteRenderer implements Renderer2 {
636
628
  this.surface.requestCommit();
637
629
  }
638
630
 
639
- // FlatList/VirtualizedList cells are content projected into a component host (our
640
- // ANCHOR_HOST_COMPONENTS, e.g. ScrollView) — Angular's own addLViewToLContainer
641
- // (.vendors/angular node_manipulation.ts/container.ts) treats a null parent here as "defer —
642
- // the child component's own <ng-content>/ɵɵprojection will place this once its structure
643
- // resolves" (e.g. ScrollView's `@if(isHorizontal)` branch). Renderer2's contract types this
644
- // return as nullable for exactly that reason; returning `this.surface` as a non-null fallback
645
- // defeated that defer check and caused premature top-level insertion (2026-07: FlatList cells
646
- // rendered outside their ScrollView).
647
- //
648
- // Safe only because appendChild/insertBefore above now also treat a null parent as "skip, wait
649
- // for projection" — a second Angular call site (`insertAnchorNode`, hit whenever a directive
650
- // does `inject(ViewContainerRef)`, e.g. VListOutletDirective) forwards this null straight into
651
- // insertBefore without checking it; without that guard it crashed on-device.
631
+ // FlatList/VirtualizedList cells project into a component host (ANCHOR_HOST_COMPONENTS, e.g.
632
+ // ScrollView); Angular defers insertion on a null parent until projection resolves, so a
633
+ // non-null fallback here breaks that. appendChild/insertBefore/insertAnchorNode skip null too.
652
634
  parentNode(node: IHostNode): IHostElement | null {
653
635
  return parentOf(node) ?? null;
654
636
  }