Summary
applyTextInputEvent is the documented way to drive a markup text control from a TypeScript core: hold a TextEditState in the Model, feed it TextInputEvents, store the result. Doing exactly that fails native build with 19 integer-proof errors across 10 lines, all inside sdk/text.ts itself. The app's own code is never named.
native check passes. Only native build fails.
Reproduced against native 0.9.3 (b25cefe), macOS 26.5.2, Zig 0.16.0, from a clean native init.
Repro
// src/core.ts
import { Cmd, asciiBytes } from "@native-sdk/core";
import { type TextEditState, type TextInputEvent, applyTextInputEvent } from "@native-sdk/core/text";
export interface Model {
readonly name: TextEditState; // storing it in the Model is what triggers this
}
export type Msg =
| { readonly kind: "name_edit"; readonly edit: TextInputEvent }
| { readonly kind: "noop" };
export function initialModel(): Model {
return { name: { text: asciiBytes(""), selection: { anchor: 0, focus: 0 }, composition: null } };
}
export function nameText(model: Model): Uint8Array {
return model.name.text;
}
export function update(model: Model, msg: Msg): Model | [Model, Cmd<Msg>] {
switch (msg.kind) {
case "name_edit": {
const next = applyTextInputEvent(model.name, msg.edit, 120);
if (!next) return model;
return { ...model, name: next };
}
case "noop":
return model;
}
}
<!-- src/app.native -->
<column gap="12" padding="16">
<text-field text="{nameText}" placeholder="Type here" on-input="name_edit" />
</column>
Actual
sdk/text.ts:79 SC4023 'TextRange.start' / 'TextRange.end' - range failed:
proven range [-Infinity, 9007199254740991]
sdk/text.ts:243,244,325,355,375,394,414,439,459
SC4022 'TextRange.start' / 'TextRange.end' - wholeness failed:
the value may be NaN
Line 79 is rangeNormalized, where Math.min is float-classed:
const start = Math.min(r.start, textLen);
const end = Math.min(r.end, textLen);
The SC4022 sites are other TextRange constructions - snapTextOffset(...), previousTextCaretOffset(...), { start: result.insertedStart, end: result.insertedEnd }.
Scope
Two things I checked before filing:
- It does not reproduce unless
TextEditState is a Model field. Calling applyTextInputEvent and storing only next.text builds fine - the integer slots are only instantiated once the record becomes model state.
- Fixing
rangeNormalized alone is not sufficient. Replacing that Math.min pair with | 0-pinned ternaries clears all four SC4023 errors at line 79, but the nine SC4022 sites remain. This looks systemic across TextRange construction rather than a single bad line.
Workaround
| 0 restores integer classification (the remedy in #251), applied both where a derived index is produced and at the slot it lands in. I hand-rolled an equivalent caret/selection editor that compiles under those rules, so the constraint is satisfiable in app code - it just isn't satisfied in the shipped module.
Impact
Without this, a TypeScript core cannot use the SDK's own text engine, so text fields are limited to whatever the app hand-rolls. In my case that meant append/backspace-only editing until I worked out the | 0 pattern - a user could not delete a word from the start of a field.
Summary
applyTextInputEventis the documented way to drive a markup text control from a TypeScript core: hold aTextEditStatein the Model, feed itTextInputEvents, store the result. Doing exactly that failsnative buildwith 19 integer-proof errors across 10 lines, all insidesdk/text.tsitself. The app's own code is never named.native checkpasses. Onlynative buildfails.Reproduced against native 0.9.3 (
b25cefe), macOS 26.5.2, Zig 0.16.0, from a cleannative init.Repro
Actual
Line 79 is
rangeNormalized, whereMath.minis float-classed:The
SC4022sites are otherTextRangeconstructions -snapTextOffset(...),previousTextCaretOffset(...),{ start: result.insertedStart, end: result.insertedEnd }.Scope
Two things I checked before filing:
TextEditStateis a Model field. CallingapplyTextInputEventand storing onlynext.textbuilds fine - the integer slots are only instantiated once the record becomes model state.rangeNormalizedalone is not sufficient. Replacing thatMath.minpair with| 0-pinned ternaries clears all fourSC4023errors at line 79, but the nineSC4022sites remain. This looks systemic acrossTextRangeconstruction rather than a single bad line.Workaround
| 0restores integer classification (the remedy in #251), applied both where a derived index is produced and at the slot it lands in. I hand-rolled an equivalent caret/selection editor that compiles under those rules, so the constraint is satisfiable in app code - it just isn't satisfied in the shipped module.Impact
Without this, a TypeScript core cannot use the SDK's own text engine, so text fields are limited to whatever the app hand-rolls. In my case that meant append/backspace-only editing until I worked out the
| 0pattern - a user could not delete a word from the start of a field.