Skip to content

Fix dekey reading Elm's tuple field from keyed children - #106

Merged
robinheghan merged 1 commit into
gren-lang:mainfrom
hufwin:fix-keyed-dekey
Sep 9, 2026
Merged

robinheghan merged 1 commit into
gren-lang:mainfrom
hufwin:fix-keyed-dekey

Conversation

@hufwin

@hufwin hufwin commented Sep 5, 2026

Copy link
Copy Markdown

Fixes the crash discussed on Discord (keyed node in a child list whose
shape changes across an update).

_VirtualDom_dekey still read keyedKids[i].b — the field of Elm's
(key, node) tuples. Gren's keyed children are { key, node } records,
which every other kernel site reads through __$node (_VirtualDom_render,
the keyed constructor's descendants count). On the diff path where an old
plain node meets a new keyed node at the same index, dekey therefore
produced a node whose children were all undefined; the following redraw
crashed with Cannot read properties of undefined (reading '$'), and from
then on every render threw removeChild type errors as the real DOM and
the virtual one drifted apart.

Minimal reproduction (click once):

view : Bool -> Html {}
view toggled =
    Html.div []
        ((if toggled then
            [ Html.div [] [ Html.text "moved" ]
            , Html.div []
                [ Html.button [ Html.Events.onClick {} ] [ Html.text "toggle" ] ]
            ]

          else
            [ Html.button [ Html.Events.onClick {} ] [ Html.text "toggle" ]
            , Html.text "flat"
            , Html.div [] [ Html.text "filler" ]
            ]
         )
            ++ [ Html.Keyed.node "span"
                    []
                    [ { key = "only", node = Html.text "keyed" } ]
               ]
        )

Verified by patching a package-cache copy of the kernel: the untouched
6.0.2 crashes on the first update, this change alone stops it, and
reverting brings the crash back. gren make compiles all 24 modules with
the change.

_VirtualDom_dekey still read keyedKids[i].b, the field of Elm's
(key, node) tuples. Gren's keyed children are { key, node } records,
which every other kernel site reads through __$node. On the diff path
where an old plain node meets a new keyed node at the same index,
dekey therefore produced a node whose children were all undefined,
and the following redraw crashed with "Cannot read properties of
undefined (reading '$')", after which the real DOM and the virtual
one never agreed again.

Verified against a minimal reproduction — a keyed node last in a
child list whose shape changes across an update: the untouched
kernel crashes on the first update, this change alone stops it, and
reverting brings the crash back.
@robinheghan

Copy link
Copy Markdown
Member

The fix looks good, but I haven't had the time to run the minimal repro case to verify. Will try to make time in the coming days.

@gilramir

gilramir commented Sep 9, 2026

Copy link
Copy Markdown

For what it's worth, I ran into the same problem last night in a little web app I'm writing.

My HTML body has a Just/Nothing when expression:

Html.main_ [ Attr.id "doc-pane" ]
    [ when model.current is
        Just doc ->
            viewNode doc.root      -- root case renders Html.Keyed.node "article" ...

        Nothing ->
            Html.p [ Attr.class "empty" ] [ Html.text "Select a document." ]
    ]

The first render (the Nothing case) was fine. When I clicked the page and model.current became Just, the page
died with:

TypeError: Cannot read properties of undefined (reading '$')
    at _VirtualDom_render
    at _VirtualDom_render
    at _VirtualDom_applyPatchRedraw
    at _VirtualDom_applyPatch
    at _VirtualDom_applyPatchesHelp

Instrumenting the compiled output at the point of the
crash shows a dekeyed parent whose children are all undefined:

BAD PARENT: $=1 typeof=number c=article nodeKeys=["$","c","d","e","f","b"] kids=["UNDEF"]

$=1 with c=article is the dekeyed node.

I patched my compiled javascript with the keyedKids[i].b -> keyedKids[i].node fix and it works.

@robinheghan
robinheghan merged commit 40c66ae into gren-lang:main Sep 9, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants