Hacktoberfest 2026: los issues que los mantenedores marcaron para octubre, abiertos y aptos para principiantes. Explorar issues de Hacktoberfest

L2: mount triggered by latest() shows content beside the committed value

Cerrado
#3,851 0 comentarios 0 reacciones 0 asignados Ver en GitHub

Los mantenedores suelen responder en 1 día

Nadie ha tomado este issue todavía.

Evaluación

Dificultad
4/5
Tiempo estimado
3-5 días
Aptitud para principiantes
55/100
Tipo de issue
Error
Claridad
Bien especificado
Estado de actividad
Activo
Stack tecnológico
typescript
Área
frontend

Línea de trabajo

Start with the provided failing vitest repro under packages/signals/tests, run it to confirm slot reads "content 1" while screenX stays 0. Then read the first-pass lane rule in recompute (packages/signals/src/core/core.ts) alongside the SPEC's lane rule and A29 boundary-scope note to see why a pass created inside a verdict lane inherits that lane instead of staying mainline. Done means the repro passes (slot equals "fallback" during the hold) without breaking the existing lane, loading-boundary and optimistic-lane tests — expect to consult the SPEC draft for proto/3540-boundary-scope before touching semantics.

Escrito por el modelo de indexación a partir del texto del issue.

Descripción

Summary

Inside an action, a <Show when={latest(x) > 0}> opens at once, which is expected, because latest() is display-ahead. But the new <Loading> it mounts shows content 1 while everything else on screen still shows the committed x = 0. Under the lane rule, a verdict lane's mounts stay mainline, so the mounted content belongs to the screen's world, not the proposal's. This is a torn frame.

Repro (packages/signals/tests, vitest)

import { expect, it } from "vitest";
import {
  action,
  createLoadingBoundary,
  createMemo,
  createRenderEffect,
  createRoot,
  createSignal,
  flush,
  latest,
  untrack
} from "../src/index.js";

const settle = async () => {
  for (let i = 0; i < 3; i++) {
    await new Promise(r => setTimeout(r, 0));
    flush();
  }
};

it("a mount triggered by latest() shows its content beside the committed value", async () => {
  const [x, setX] = createSignal(0);
  let screenX: unknown;
  let slot: unknown;
  createRoot(() => {
    // <p>{x()}</p>
    createRenderEffect(x, v => {
      screenX = v;
    });
    // <Show when={latest(x) > 0}>
    //   <Loading fallback="fallback">content {x()}</Loading>
    // </Show>
    const when = createMemo(() => latest(x) > 0);
    const children = createMemo(() =>
      when()
        ? untrack(() =>
            createLoadingBoundary(
              () => `content ${x()}`,
              () => "fallback"
            )
          )
        : "closed"
    );
    createRenderEffect(
      () => {
        const c = children();
        return typeof c === "function" ? c() : c;
      },
      v => {
        slot = v;
      }
    );
  });
  flush();
  expect([screenX, slot]).toEqual([0, "closed"]);

  let release!: () => void;
  const done = action(function* () {
    setX(1);
    yield new Promise<void>(r => (release = r));
  })();
  await settle();
  // The action holds x = 1; the screen still shows x = 0.
  expect(screenX).toBe(0);
  // Actual: "content 1" beside the committed 0.
  expect(slot).toBe("fallback");

  release();
  await done;
  await settle();
  expect([screenX, slot]).toEqual([1, "content 1"]);
});

The repro fails at expect(slot).toBe("fallback") with Received: "content 1". The same thing happens when the content is a memo, createMemo(() => \content ${x()}`)`.

Expected vs actual

Checkpoint Expected Actual
Before the action x = 0, slot closed x = 0, slot closed
Action holds x = 1 x = 0, slot fallback x = 0, slot content 1
Action ends x = 1, slot content 1 x = 1, slot content 1

Rules involved

  • The lane rule: an optimistic lane sees the screen plus its own guesses, and its mounts' children are the lane's. A verdict lane computes the real outcome, and its mounts stay mainline. Here the Show condition is verdict-lane work, and its children are seated in that lane. The new Loading's first pass inherits the lane from the pass that created it, so it reads the proposal x = 1.
  • A29's boundary scope (2026-10-06): a loading boundary that has not shown content owns its subtree. Content that reads a hold waits behind the boundary's fallback, and it appears at the hold's commit.
  • No tearing: content on screen agrees with the screen's value of what it reads.

The suspected site is the first-pass lane rule in recompute (core/core.ts). A first pass takes its creator's lane, which is right for an optimistic lane (ruling A). For a verdict lane, it should stay mainline.

Where it reproduces

  • next (ba67ddbaa)
  • #3835's branch (ae59c7682)
  • #3824's head (3ab71b0dc)
  • proto/3540-boundary-scope (60d2f1ded). Its SPEC draft already lists this shape as open ("a boundary a verdict reader mounts … shows the held derivation now rather than its fallback").

Provenance

Found by the semantic fuzzer's mount-under-hold cohort, verdict family (fuzz/semantic-fuzzer-l2, b2bdf125a, rules MH1 and MH5). It probably shares a root with the read-order mount family (fuzzer cases 827, 416 and 936). There, a latest() read routes a freshly mounted child into the verdict lane, its mount control publishes alone, and the slot stays empty. In both cases a pass picks up verdict-lane membership that the lane rule says stays mainline: through a read in the read-order family, and through its creator here.

Lenguaje dominante
TypeScript
Estrellas
36.1k
Forks
1.1k
Merge medio
11 h 42 min
PR fusionados (30 d)
326

Preparar el entorno

Primeros pasos

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Más de solidjs/solid

Todos los issues de solidjs/solid

Issues similares

Más issues de TypeScript

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.