Skip to content

Markerless placeholders/Cooperative scheduling (fibers)/Portals/Multiwindow#5554

Draft
ealmloff wants to merge 62 commits into
DioxusLabs:mainfrom
ealmloff:fix-textarea-hydration
Draft

Markerless placeholders/Cooperative scheduling (fibers)/Portals/Multiwindow#5554
ealmloff wants to merge 62 commits into
DioxusLabs:mainfrom
ealmloff:fix-textarea-hydration

Conversation

@ealmloff
Copy link
Copy Markdown
Member

@ealmloff ealmloff commented May 12, 2026

Currently we use comment nodes in several places at runtime and in hydration:

  1. To represent Placeholder nodes at runtime
  2. To represent empty lists
  3. To surround text nodes for hydration

This PR removes all of those in favor of keeping track of the position of nodes relative to an anchor. This lets us:

  1. Properly render element that interpret children as text like textarea (Fixes Textarea with placeholder child renders <!--placeholder8--> when first loaded in fullstack #5548)
  2. Drop the different between ssr prerendering and rendering since all of that data is now interpreted at runtime

This PR is working well enough to hydrate the docsite correctly, but needs some more refactoring and tests

TODO:

  • Round trip fuzzing with a mock renderer using mutatis
  • Make sure the docsite renders correctly
  • Playwright tests passing
  • Update most tests to use an oracle renderer to assert the output structure is correct instead of asserting the ops themselves

@ealmloff ealmloff requested a review from a team as a code owner May 12, 2026 19:48
@ealmloff ealmloff marked this pull request as draft May 12, 2026 19:48
@ealmloff ealmloff changed the title Markerless placeholders and hydration Markerless placeholders/Cooperative scheduling (fibers)/Portals/Multiwindow May 20, 2026
ealmloff added 21 commits May 21, 2026 19:24
`test -x` on the rustup path failed on the warp runner because
`llvm-tools-preview` does not always drop `llvm-symbolizer` at the
expected location. Try the rustup path first, fall back to whatever
the runner has on PATH, then to a versioned `/usr/bin/llvm-symbolizer-*`.
Without a symbolizer LSan reports unsymbolicated frames and the leak:
suppressions silently match nothing.
Reset to ealmloff/fuzzing as the new base, then reapplied:
- fiber/scheduler split (api/driver/fairness/message/queues/work)
- portals + render_targets multi-render-target architecture
- markerless hydration (web/src/hydration/{plan,suspense,hydrate}.rs,
  interpreter sledgehammer switch, ssr renderer/cache changes)
- desktop multi-window/portal plumbing
- concurrent-scheduler example + spec

Took from ealmloff/fuzzing:
- diff/attributes.rs k-way merge strategy (ported write_attribute to
  the portal-aware render_targets[].elements table)
- new oracle (ported away from create_placeholder/replace_placeholder
  to insert_children_at_path + pop_root; removed DynamicNode::Placeholder
  arm since this branch removed that variant)
- new fuzz harness (ported MutationTrace to drop CreatePlaceholder,
  rename ReplacePlaceholderWithNodes -> InsertChildrenAtPath, add
  PopRoot; DynamicSpec::Placeholder -> empty Fragment in vdom builder)
- improved core tests + suspense tests

Added back render_suspense_immediate() wrapper around
render_suspense_concurrent() so ported tests compile.

Preserved both SubDocumentAttr (with take_document, used by desktop
portal) and CustomWidgetAttr (used by native wgpu example) via
write_once_attr.rs.

Workspace + tests compile. Still need to actually run tests.
@ealmloff ealmloff force-pushed the fix-textarea-hydration branch from 54cf23a to c64b2a0 Compare May 22, 2026 18:27
ealmloff added 8 commits May 22, 2026 13:29
Pulled `sort_template_attributes` const fn + helpers into core/src/nodes.rs
and re-exported via dioxus_core::internal. Took ealmloff's rsx/src/element.rs
which separates static/dynamic and wraps the static prefix in
sort_template_attributes(...).

Also derived Clone, Copy on TemplateAttribute so the const sort can swap
entries.

This fixes the 4 dynamic_attr_override_restores_* tests in diff_element.rs:
they relied on attributes.rs's `static_template_attribute_value` finding
static attrs by binary-search-able sorted name.
Found a real architectural mismatch: ealmloff's oracle walked DOM children
by raw position, which broke for this branch's anchor diff because the
empty-slot case has no DOM child to walk to (the markerless renderer
uses logical `__dxSlotAnchors` instead).

Replaced packages/oracle/src/ contents with the slot-aware oracle from
fix-textarea-hydration-pre-ealmloff-merge, which tracks
`child_template_indices` per node so path walks resolve through Dynamic
slot positions even when no DOM child sits there.

Added back ealmloff-style API: `rebuild`/`render` now return EditSummary
directly, and `snapshot_eq(&[SnapshotNode]) -> bool` for the fuzz
harness's `check_matches_fresh`.

Updated test assertions for anchor mutation counts:
- attr_cleanup, cycle, diff_dynamic_node, diff_element, diff_keyed_list,
  diff_unkeyed_list, lifecycle: anchor diff substitutes
  `load_template + remove_node` for ealmloff's `replace_node_with` on
  placeholder swap. Total mutations are equal or fewer.

Ignored 3 suspense tests with `#[ignore = "needs suspense rerun start
port from ealmloff/fuzzing"]` — they assert behaviors (re-rendering a
suspending child during diff, promoting on task cancel) that require
porting ealmloff's suspense algorithm. TODO comments mark the work.

Core: 139 passed, 0 failed, 3 ignored.
Three suspense tests were failing because this branch's cooperative diff
queues descendant component scopes for later instead of running them
inline. The boundary's diff therefore committed before any
`suspend(task)?` from a re-rendering child reached
`suspense_context.suspended_futures()`, so:

- empty `(None, false)` never switched to the fallback when a child
  re-suspended during the boundary's diff
- `(Some, true)` never promoted to the resolved children when a child
  cancelled its suspended task during the boundary's diff

Added `Scheduler::pop_dirty_descendant_of(scope_id)` that drains the
smallest-height dirty fiber under a scope, and called it in a loop after
each `diff_node` inside `SuspenseBoundaryProps::diff`. That forces
descendant scopes to run synchronously while the boundary still owns the
diff, so any suspend/cancel registers against the boundary's
`SuspenseContext` before the post-diff branch is taken.

Also extended `(None, false)` to move the just-rendered children into
the background and show the fallback when descendants suspend during
diff (mirrors `(None, true)` semantics but applied retroactively).

Result: 142 core tests pass, 0 ignored.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

breaking This is a breaking change core relating to the core implementation of the virtualdom desktop Suggestions related to the desktop renderer

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Textarea with placeholder child renders <!--placeholder8--> when first loaded in fullstack

1 participant