Skip to content

Use Microtasks for activation#57

Merged
adebola-io merged 8 commits into
mainfrom
move-to-microtask
Jun 26, 2026
Merged

Use Microtasks for activation#57
adebola-io merged 8 commits into
mainfrom
move-to-microtask

Conversation

@adebola-io

Copy link
Copy Markdown
Collaborator

No description provided.

Run `pnpm install` and `pnpm run build` to update the package before
running tests.
Remove unused experiments dependencies and prune redundant entries.
After applying these changes, run `pnpm install` and `pnpm run test`
to ensure the project is correctly rebuilt and verified.
Rebuild packages with `pnpm build` before running `pnpm run test` to
verify the microtask timing.
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
retend f9f193e Commit Preview URL

Branch Preview URL
Jun 26 2026, 07:33 AM

@qodo-code-review

Copy link
Copy Markdown
Contributor

PR Summary by Qodo

Use microtasks for setup-effect activation and stabilize @adbl/cells
✨ Enhancement ⚙️ Configuration changes 🕐 20-40 Minutes

Grey Divider

Description

• Switch setup-effect activation waits from macrotasks to microtasks for tighter timing.
• Pin @adbl/cells to the stable 0.0.23 release.
• Prune/normalize pnpm lockfile entries after dependency cleanup.
Diagram

graph TD
  A["App bootstrap"] --> B["runPendingSetupEffects()"] --> C["observer.flush()"] --> D["EffectNode.activate()"] --> E["queueMicrotask yield"] --> F["run setup fns + dispatch activate"]
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Use `await Promise.resolve()` for microtask yielding
  • ➕ No callback wrapper; very concise
  • ➕ Works anywhere Promises are supported
  • ➖ Slightly less explicit than queueMicrotask about intent/scheduling
  • ➖ Harder to extend later (e.g., naming/diagnostics around the microtask)
2. Use `requestAnimationFrame` (or a macrotask) for DOM-paint ordering
  • ➕ Guarantees activation occurs after a frame, useful for layout/paint-dependent effects
  • ➖ Changes semantics more drastically (can add visible latency)
  • ➖ Not available in non-DOM runtimes without shims; harder to test consistently

Recommendation: Using queueMicrotask is a good fit for deterministic “yield but stay in the same tick” activation ordering. Consider adding a small fallback (queueMicrotask ?? (cb => Promise.resolve().then(cb))) if retend targets runtimes where queueMicrotask may be missing.

Files changed (3) +14 / -2514

Enhancement (1) +4 / -2
scope.jsSwitch effect activation defers to microtasks +4/-2

Switch effect activation defers to microtasks

• Replaces 'setTimeout(…, 0)' waits with 'queueMicrotask' in 'EffectNode.activate()' and after 'runPendingSetupEffects()' to yield execution without incurring an extra macrotask turn. This tightens activation sequencing before running setup functions and dispatching 'retend:activate'.

packages/retend/source/library/scope.js

Other (2) +10 / -2512
package.jsonPin @adbl/cells to stable semver release +1/-1

Pin @adbl/cells to stable semver release

• Replaces the PR tarball URL for @adbl/cells with the stable '^0.0.23' version range to consume published releases consistently.

packages/retend/package.json

pnpm-lock.yamlUpdate lockfile for stable cells + dependency pruning +9/-2511

Update lockfile for stable cells + dependency pruning

• Updates the lockfile to reflect @adbl/cells 0.0.23 and removes/prunes unused and redundant dependency entries (including removed experiment workspace packages). This normalizes resolutions and reduces lockfile noise going forward.

pnpm-lock.yaml

@qodo-code-review

Copy link
Copy Markdown
Contributor

Code Review by Qodo

🐞 Bugs (2) 📘 Rule violations (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Reentrant activation duplicates setup 🐞 Bug ≡ Correctness
Description
EffectNode.activate() now defers via queueMicrotask before running #runActivateFns(), but
#runActivateFns() awaits each SetupFn and only sets #active after the loop, so a second activate()
call can enter while the first is mid-await and run the same setup effects again. This is reachable
because multiple call sites invoke node.activate() without awaiting it (e.g., If/For updates), so
repeated activations in the same tick can overlap and duplicate side effects like event
listeners/timers.
Code

packages/retend/source/library/scope.js[R139-145]

  async activate() {
    if (!this.#enabled || this.#active) return;
-    await new Promise((resolve) => setTimeout(resolve));
+    await new Promise((resolve) => {
+      queueMicrotask(() => resolve(undefined));
+    });
    await this.#runActivateFns();
    this.renderer?.host.dispatchEvent(new Event('retend:activate'));
Evidence
Setup effects can be async (promise-returning), #runActivateFns() awaits them and sets #active
only at the end, and multiple places call activate() without awaiting it—so overlapping
activations can execute the same setup list multiple times before #active flips true.

packages/retend/source/library/scope.js[55-57]
packages/retend/source/library/scope.js[120-146]
packages/retend/source/library/if.js[149-158]
packages/retend/source/library/for.js[237-242]
packages/retend-web/source/dom-renderer.js[876-882]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`EffectNode.activate()` can be invoked multiple times before the node becomes active. After this PR, activation is microtask-based, which allows concurrent/overlapping activations while `#runActivateFns()` is mid-`await`, causing setup effects to execute more than once.

## Issue Context
- `#runActivateFns()` awaits each `SetupFn` and only sets `#active` after all setup functions complete.
- Several call sites call `node.activate()` without `await`, so repeated updates in the same tick can trigger multiple activations.

## Fix Focus Areas
- packages/retend/source/library/scope.js[120-146]
- packages/retend/source/library/if.js[149-158]
- packages/retend/source/library/for.js[237-242]

## Implementation direction
- Add a single-flight guard in `EffectNode` (e.g., `#activationPromise` or `#activating` flag).
- In `activate()`, if an activation is already in progress, return/await the same promise.
- Ensure `retend:activate` is dispatched once per activation and only after setup execution.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. queueMicrotask may be undefined 🐞 Bug ☼ Reliability
Description
The core lifecycle now calls queueMicrotask directly (in runPendingSetupEffects and
EffectNode.activate), which will throw a ReferenceError in runtimes that lack queueMicrotask and
prevent setup effects from running at all. There is no local fallback/polyfill and the package does
not declare an engines/runtime baseline.
Code

packages/retend/source/library/scope.js[R540-543]

  renderer?.observer?.flush();
  node.enable();
  await node.activate();
-  await new Promise((resolve) => setTimeout(resolve));
+  await new Promise((resolve) => queueMicrotask(() => resolve(undefined)));
Evidence
The modified code paths reference queueMicrotask as a free global in core activation functions, so
absence of that global will throw immediately when setup effects are run.

packages/retend/source/library/scope.js[139-146]
packages/retend/source/library/scope.js[533-544]
packages/retend/package.json[1-59]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Direct usage of the global `queueMicrotask` can crash at runtime in environments where it is not available.

## Issue Context
`runPendingSetupEffects()` and `EffectNode.activate()` are core entry points for setup effects; a missing `queueMicrotask` breaks all setup activation.

## Fix Focus Areas
- packages/retend/source/library/scope.js[139-146]
- packages/retend/source/library/scope.js[533-544]

## Implementation direction
- Introduce a small helper (e.g., `scheduleMicrotask(cb)`) that uses `globalThis.queueMicrotask` when present.
- Provide a safe fallback such as `Promise.resolve().then(cb)` (microtask) or `setTimeout(cb, 0)` (macrotask) depending on intended semantics.
- Use the helper in both `activate()` and `runPendingSetupEffects()`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

Comment on lines 139 to 145
async activate() {
if (!this.#enabled || this.#active) return;
await new Promise((resolve) => setTimeout(resolve));
await new Promise((resolve) => {
queueMicrotask(() => resolve(undefined));
});
await this.#runActivateFns();
this.renderer?.host.dispatchEvent(new Event('retend:activate'));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

1. Reentrant activation duplicates setup 🐞 Bug ≡ Correctness

EffectNode.activate() now defers via queueMicrotask before running #runActivateFns(), but
#runActivateFns() awaits each SetupFn and only sets #active after the loop, so a second activate()
call can enter while the first is mid-await and run the same setup effects again. This is reachable
because multiple call sites invoke node.activate() without awaiting it (e.g., If/For updates), so
repeated activations in the same tick can overlap and duplicate side effects like event
listeners/timers.
Agent Prompt
## Issue description
`EffectNode.activate()` can be invoked multiple times before the node becomes active. After this PR, activation is microtask-based, which allows concurrent/overlapping activations while `#runActivateFns()` is mid-`await`, causing setup effects to execute more than once.

## Issue Context
- `#runActivateFns()` awaits each `SetupFn` and only sets `#active` after all setup functions complete.
- Several call sites call `node.activate()` without `await`, so repeated updates in the same tick can trigger multiple activations.

## Fix Focus Areas
- packages/retend/source/library/scope.js[120-146]
- packages/retend/source/library/if.js[149-158]
- packages/retend/source/library/for.js[237-242]

## Implementation direction
- Add a single-flight guard in `EffectNode` (e.g., `#activationPromise` or `#activating` flag).
- In `activate()`, if an activation is already in progress, return/await the same promise.
- Ensure `retend:activate` is dispatched once per activation and only after setup execution.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Ensure packages are rebuilt with `pnpm build` before running `pnpm run
test`.
- Standardize `If()` usage by migrating away from object bags for
  true-only branches.
- Add `retend/no-if-three-args` to enforce cleaner `If()` syntax.
- Run `pnpm build` after making these changes to ensure the lint plugin
  is updated before testing.
@pkg-pr-new

pkg-pr-new Bot commented Jun 21, 2026

Copy link
Copy Markdown

Open in StackBlitz

retend

npm i https://pkg.pr.new/resuite/retend@57

retend-oxlint-plugin

npm i https://pkg.pr.new/resuite/retend/retend-oxlint-plugin@57

retend-server

npm i https://pkg.pr.new/resuite/retend/retend-server@57

retend-start

npm i https://pkg.pr.new/resuite/retend/retend-start@57

retend-utils

npm i https://pkg.pr.new/resuite/retend/retend-utils@57

retend-web

npm i https://pkg.pr.new/resuite/retend/retend-web@57

retend-web-devtools

npm i https://pkg.pr.new/resuite/retend/retend-web-devtools@57

commit: f9f193e

Ensure nodes are detached from their previous parents before being
moved. Also, update router replacement to correctly trigger navigation
logic.

Run `pnpm install` and `pnpm run test` after these changes.
Run `pnpm install` and `pnpm run build` to update the package
dependencies
before testing.
Rebuild packages after updating dependencies and run tests using `pnpm
run test`.
@adebola-io adebola-io merged commit 3f409c8 into main Jun 26, 2026
4 checks passed
@adebola-io adebola-io deleted the move-to-microtask branch June 26, 2026 07:34
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.

1 participant