Skip to content

Capturing a tuple in a runtime expression throws at hydration #254

Description

@AmeinEskinder

expr.md lists "Tuples of vocabulary types" in the shared vocabulary, and expr_field.rs:21 compiles pair.0 into JavaScript pair[0], so a tuple is meant to be an array on the browser side. The Rust side serializes it as one. hydrateSurrogate has no array case, so every expression that captures a tuple throws in the browser.

Reproduction

$(pair.0) with let pair = (1.5f64, 2.5f64) renders:

<!-- ::topcoat::expr::start("(() => { const [__external0] = [cx.hydrate([1.5,2.5])]; return __external0[0]; })()") -->1.5<!-- ::topcoat::expr::end -->

The server text is correct. The JavaScript is not. cx.hydrate([1.5,2.5]) reaches hydrateSurrogate with an array, typeof value === "object" while value.t is undefined, so it falls through to the default arm:

Error: Unknown surrogate type: undefined
 ❯ hydrateSurrogate src/surrogate/index.ts:73:12

$(pair) on its own does the same, with return __external0 in place of the index.

Where it comes from

  • crates/topcoat-runtime/src/lib.rs:52 writes a captured value into the page as cx.hydrate(<json>).
  • A tuple surrogate is a plain Rust tuple of surrogates, so serde emits an array: (1.5f64, 2.5f64) gives [1.5,2.5], and (1.0f64, Some(2.0f64)) gives [1.0,{"t":"Option","v":2.0}].
  • browser/src/surrogate/index.ts has no array member in DehydratedSurrogate and no array case in hydrateSurrogate.

Reach

scan() walks the document in a single for loop and there is no try/catch in scan.ts, text.ts or binding.ts, so the throw leaves the loop and nothing after the offending expression is hydrated. I have verified the throw itself; I have not measured the page-level effect in a browser.

One thing a fix should not miss

Hydrating the array into a plain array of surrogates is what the generated pair[0] needs, but the text path would then disagree rather than throw. NodeViewParts for (T1, T2) concatenates its elements with no separator, so the server renders $(pair) as 1.52.5, while String(array) at text.ts:55 would give 1.5,2.5. Attribute values have the same shape: AttributeValueViewParts for (T1, T2) concatenates, and is present when any element is present.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions