references/core-notes.md
references/core-notes.mdBrowse 2 files
9,372 bytes
Token encoding: o200k_base
Snapshot 638f809
Qwik Core Notes
Use this reference only after loading qwik-core-development and only for core runtime work that
needs more detail than the skill body. Keep it current when source changes make a note stale.
Maintainer Bias From History
Recent core work by Varixo and Wout tends to:
- isolate the broken invariant before changing code;
- keep producer and consumer protocol changes together;
- add focused regression tests beside the changed behavior;
- preserve compatibility paths deliberately and test them;
- extract small semantic helpers when they make ownership or ordering clearer;
- leave comments only for non-obvious runtime encodings, lifecycle ordering, or compatibility constraints.
Use that style for core work. Avoid broad rewrites, unexplained fallbacks, and temporary debug code.
Source Map
packages/qwik/src/core/
client/ client render, VNode materialization, cursor/diff integration
reactive-primitives/ signals, async signals, stores, subscriptions
shared/qrl/ QRL classes and helpers
shared/serdes/ serialization, inflation, root refs
shared/vnode/ VNode structures
shared/cursor/ cursor walking and DOM update primitives
ssr/ server-side JSX rendering
tests/ core feature specs
Closest tests are usually in the same subtree and named *.unit.ts(x) or *.spec.ts(x).
AsyncSignal Current Model
Current API and implementation facts:
- The async engine (AsyncJob, loading/error, cleanup) lives in
ComputedSignalImpl;AsyncSignalImplonly parses options and setsAsyncSignalFlags.ASYNC_MODE | CTX_ARG. - All compute fns receive the ComputeCtx argument (
track,previous,info,cleanup,abortSignal); sync computeds allocate an AsyncJob per compute and run the previous job's cleanups before recomputing.CTX_ARGsignals (createAsyncQrl/useResource$) track only via the explicitctx.track(); computeds auto-track synchronous reads via a dedicated invoke context, but that context is lost after the firstawait— later reads must usectx.track(). - A computed whose fn returns a promise lazily switches on
ASYNC_MODE(loading state staysdeclared until then) and then has the full AsyncSignal API. TheclientOnlyoption setsASYNC_MODEat construction, since SSR can never resolve such a signal synchronously. Sync compute throws stay in sync mode but still land in.error; reading.valuerethrows until a recompute or explicit value set clears it. Thrown promises must keep propagating for retry, never be captured as errors. - Serialization keys off
ASYNC_MODE, notinstanceof: async-mode computeds round-trip asTypeIds.AsyncSignaland resume asAsyncSignalImplinstances whose serialized flags (noCTX_ARG) preserve auto-track semantics. Runtime checks must use flags, not class identity. createAsyncSignal()passes the fullAsyncSignalOptionsobject to the constructor.- Signals have no built-in polling or expiration. Polling lives in
usePoll(signal, expires)from@qwik.dev/utils: an interval-based visible task (document-idle) that callsinvalidate()and resets on thependingflip. There is noexpires/poll/intervaloption or property. invalidate()keeps the previous value readable while recomputing;clear()drops the value first so readers suspend (the oldallowStale: falsebehavior, now explicit and one-shot).clientOnlyskips server computation and computes on first client read.eagerCleanupschedules cleanup after subscribers drop to zero.
When changing AsyncSignal behavior, inspect:
packages/qwik/src/core/reactive-primitives/impl/async-signal-impl.tspackages/qwik/src/core/reactive-primitives/types.tspackages/qwik/src/core/reactive-primitives/signal.public.tspackages/qwik/src/core/reactive-primitives/cleanup.tspackages/qwik/src/core/shared/serdes/serialize.tspackages/qwik/src/core/shared/serdes/inflate.ts- closest async signal unit/spec tests
AsyncSignal Invariants
- A first unresolved read may throw the compute promise; tests should use
retryOnPromise()when exercising first-read behavior. .value,.loading, and.errorhave separate subscriber sets. Subscriber-sensitive logic must account for all three.invalidate(info)records the latest info and increments the info version.- AbortError is cancellation, not a user-visible
.error. - Reading
.pendingor.errortriggers computation when needed; serialization must read the private$untrackedPending$/$untrackedError$fields to avoid starting computes. clientOnlyresume rides on the state script'sq-d:qidle_resQRL built from$eagerResume$; SSR must emit the state script whenever$eagerResume$is non-empty, even if no roots were discovered yet (the QRL captures become roots during attribute serialization).- Timeout IDs must be cleared in invalidation, destroy, and reschedule paths.
- Browser timers must not run during SSR. Current code uses
isServerplus the test platform check. - Node timers that can keep the process alive should use
.unref?.().
AsyncSignal Test Patterns
Use the current test helpers already present in nearby tests:
await withContainer(async () => {
const signal = createAsyncQrl($(async () => 42)) as AsyncSignalImpl<number>;
await retryOnPromise(() => {
effect$(() => signal.value);
});
await signal.promise();
expect(signal.value).toBe(42);
});
For mutable counters captured by $() closures, use an object ref:
const ref = { calls: 0 };
const signal = createAsyncQrl(
$(async () => {
ref.calls++;
return ref.calls;
})
);
Do not capture and mutate primitive let bindings from $() tests; optimizer serialization can
turn the binding into a const-like captured value.
Serialization And Inflation
When a core value gains serialized state:
- Update the serializer and inflater together.
- Keep array positions or marker encodings documented in the code that owns them.
- Add a round-trip test in
shared/serdesor the closest subsystem. - Check SSR and client resume behavior when the value affects resumed or streamed state.
For AsyncSignal fields, inspect the serdes tests that deserialize async signals and verify concurrency, timeout, stale value, and error/loading state behavior.
VNode, Cursor, And Streaming
Core rendering changes often cross multiple boundaries:
- SSR emits HTML, VNode data, event data, state, and sometimes streamed patches.
- Client startup materializes VNodes lazily from DOM plus
qwik/vnodedata. - Cursor work must preserve render promise resolution and not orphan paused cursors.
- Qwikloader changes need behavior tests because they run outside normal framework code.
When touching these areas:
- trace the owner of each marker or ID from emitter to consumer;
- keep numeric/string encodings deterministic;
- test root and nested/container cases when a feature can appear in both;
- include streaming or out-of-order cases when state can arrive after initial event listeners.
QRL And Optimizer-Facing Runtime
- Use
$-suffixed APIs and$()in tests when a QRL boundary is expected. - Avoid manual QRL construction unless nearby tests already use it for the same reason.
- If runtime behavior relies on optimizer output, inspect the optimizer transform and snapshot too.
- For event or JSX attribute changes, keep
event-names, JSX runtime, qwikloader, and optimizer behavior aligned.
Focused Verification
Use the closest command first:
pnpm vitest run packages/qwik/src/core/reactive-primitives/impl/async-signal.unit.tsx
pnpm vitest run packages/qwik/src/core/shared/serdes/serdes.unit.ts
pnpm vitest run packages/qwik/src/qwikloader.behavior.unit.ts
pnpm build.core.dev
pnpm api.update
Use e2e only when unit/spec tests cannot cover the behavior, such as real browser event timing,
streaming, navigation, or integration with fixture apps. For Qwik e2e, load
qwik-e2e-verification.
Never use pnpm test.unit for agent verification in this repo.
ErrorBoundary (experimental errorBoundary)
Keep error state non-enumerable and out of serialized state. Store the raw throw and project it only
at display sites; redaction is origin-based — the server display redacts in production unless the
app returns an Error from transformError; the client display always shows the error as thrown.
Reset must re-render the component that authored projected children. Identify and retain that component as soon as SSR error teardown determines it; store its VNode reference only when the projection cut prevents the client owner walk. Re-key the highest wrapper below that author so normal diffing recreates emptied projected content without projection-wide scheduling.
Keep This Reference Fresh
Before finishing a core task, ask:
- Did current source contradict anything in this reference?
- Did the task teach a durable pattern that future core work should reuse?
- Is the lesson specific enough to belong here rather than in
.ruler/AGENTS.md?
If yes, update this file in the same task when scope allows it. Prefer replacing stale text over appending another long lesson.
Referenced from SKILL.md
Source excerpt starting at line 21.SKILL.mdView in source ↗21Read `references/core-notes.md` only when the task involves:
Source excerpt starting at line 42.42- If the change affects public API, run `pnpm api.update` after the focused tests pass.43- If this skill or `references/core-notes.md` is stale after your source inspection, update it before44 finishing or record why guidance edits were out of scope.