Summary
When two Msg arms declare a payload member of the same name with different types,
the checker resolves the wrong arm's type and reports NS1061 against the innocent arm.
Reproduced against v0.7.1 (19519dd5), macOS, node 24.15.0.
Reproduction
export type PtyPhase = "output" | "exit";
export interface TerminalView { readonly scrollback: number }
export interface Model { readonly scrollback: number; readonly exited: boolean }
export function initialModel(): Model { return { scrollback: 0, exited: false } }
export type Msg =
| { readonly kind: "term_state"; readonly state: TerminalView }
| { readonly kind: "pty_event"; readonly state: PtyPhase; readonly code: number };
export const viewUnbound = ["term_state", "pty_event"] as const;
export function update(model: Model, msg: Msg): Model {
switch (msg.kind) {
case "term_state": return { ...model, scrollback: msg.state.scrollback | 0 };
case "pty_event": return msg.state === "exit" ? { ...model, exited: true } : model;
}
}
core.ts:12:30 error NS1061 value records stay scalar where the model keeps them
`===` compares `TerminalView` values by identity, which value storage does not carry ...
Line 12 is the pty_event arm, whose state is a string-literal union — comparing it
with === is ordinary and correct. The diagnostic names TerminalView, which is the
other arm's type. Renaming either member (e.g. term_state's to view) transpiles
clean, with no other change.
Why this shape is likely to be hit
state is what the docs' own terminal example names both of these members: the
<terminal> state arm and the pty-event arm. An app that mounts a terminal and owns the
pty behind it writes both arms, and the collision is the natural spelling.
The diagnostic text itself is fine — it is the arm resolution that is wrong, so the
teaching lands on code that has nothing to do with the problem.
Summary
When two
Msgarms declare a payload member of the same name with different types,the checker resolves the wrong arm's type and reports NS1061 against the innocent arm.
Reproduced against v0.7.1 (
19519dd5), macOS, node 24.15.0.Reproduction
Line 12 is the
pty_eventarm, whosestateis a string-literal union — comparing itwith
===is ordinary and correct. The diagnostic namesTerminalView, which is theother arm's type. Renaming either member (e.g.
term_state's toview) transpilesclean, with no other change.
Why this shape is likely to be hit
stateis what the docs' own terminal example names both of these members: the<terminal>state arm and the pty-event arm. An app that mounts a terminal and owns thepty behind it writes both arms, and the collision is the natural spelling.
The diagnostic text itself is fine — it is the arm resolution that is wrong, so the
teaching lands on code that has nothing to do with the problem.