Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/reactlynx-snapshot-debugging.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@lynx-js/skill-reactlynx-snapshot-debugging": minor
---

Add the `reactlynx-snapshot-debugging` skill: a troubleshooting guide for ReactLynx 3 Snapshot runtime errors (`snapshotPatchApply failed: ctx not found` and `Snapshot not found`). It covers the dual-thread hydrate/snapshotPatch model, a rule-out-upstream-crash diagnosis workflow, and snapshotPatch decoding. For capturing the dual-thread traffic it leads with the built-in ALog macros (`REACT_ALOG` / `REACT_ALOG_ELEMENT_API`, with version gating from `@lynx-js/react` 0.111.2) and driving the `lynx-devtool` skill to dump and analyze both threads directly, falling back to manual entry-file instrumentation only on older runtimes. Wired into the `reactlynx` plugin.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
"@lynx-js/skill-lynx-typescript": "workspace:*",
"@lynx-js/skill-lynx-ui": "3.133.4",
"@lynx-js/skill-reactlynx-best-practices": "workspace:*",
"@lynx-js/skill-reactlynx-snapshot-debugging": "workspace:*",
"@lynx-js/skill-rspeedy-bundle-size": "workspace:*",
"@lynx-js/skill-vanilla-lynx": "workspace:*"
},
Expand Down
3 changes: 2 additions & 1 deletion packages/plugins/reactlynx/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@
"build": "build-plugin"
},
"dependencies": {
"@lynx-js/skill-reactlynx-best-practices": "workspace:*"
"@lynx-js/skill-reactlynx-best-practices": "workspace:*",
"@lynx-js/skill-reactlynx-snapshot-debugging": "workspace:*"
},
"devDependencies": {
"build-plugin": "workspace:*"
Expand Down
82 changes: 82 additions & 0 deletions packages/skills/reactlynx-snapshot-debugging/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
---
name: reactlynx-snapshot-debugging
description: |
Use this Skill to diagnose and fix ReactLynx 3 (RL3) Snapshot runtime errors that surface during first-screen hydrate or subsequent state updates. RL3 compiles JSX into Snapshot definitions and drives the UI through dual-thread hydrate + snapshotPatch, so these errors are usually a downstream symptom of a different crash on the main thread or background thread.

Trigger Scenarios:
- The runtime logs "snapshotPatchApply failed: ctx not found, snapshot type: '__Card__:__snapshot_...'".
- The runtime logs "Error: Snapshot not found: __Card__:__snapshot_..." (or "Snapshot not found").
- A ReactLynx page renders blank / stops updating after hydrate, or a "not a function" / "cannot read property '0' of undefined" error appears in the hydrate call stack.
- The user needs to inspect dual-thread communication: the OnLifecycleEvent SnapshotInstance tree (main -> background) or the rLynxChange snapshotPatch (background -> main), or decode a raw snapshotPatch array.
- The user mentions RL3 snapshot, createSnapshot, snapshotPatchApply, SnapshotInstance, hydrate, rLynxChange, or a bug report about a ReactLynx snapshot error.
---

# ReactLynx 3 Snapshot Error Debugging

Diagnose and fix RL3 Snapshot runtime errors. This is a troubleshooting guide, not a code generator: read the evidence, find the *root* crash, then fix that.

## Background: how RL3 snapshots work

To reduce vnode count and improve performance, RL3 compiles JSX into **Snapshot** definitions. A snapshot definition is created by `createSnapshot(...)` at the very top level of the main-thread bundle (`main-thread.js` / `lepus.js`):

```js
const __snapshot_d9459_61989_1 = (__webpack_require__(".../react/runtime/lib/internal.js").createSnapshot)(
"__snapshot_d9459_61989_1",
function () { /* create elements */ },
[ /* update dynamic parts */ ],
...
);
```

The runtime then renders through a **dual-thread** flow:

- **Main thread** builds a `SnapshotInstance` tree from these definitions and owns the real UI (Element PAPI).
- **Background thread** builds a `BackgroundSnapshotInstance` tree from your React render.
- **`OnLifecycleEvent` (`rLynxFirstScreen`) + hydrate**: matches the main-thread tree against the background tree, diffs them, and sends a `snapshotPatch` to the main thread to run `snapshotPatchApply` and reconcile the UI.
- **`rLynxChange`**: every later update (hydrate result and `setState`) is delivered as a `snapshotPatch` over `rLynxChange`, which the main thread applies via `snapshotPatchApply`.

Because the whole UI hangs off this pipeline, a crash *anywhere upstream* (a main-thread definition that never got created, or a background hydrate that threw) shows up later as a confusing snapshot error.

## The first rule: rule out unrelated errors first

**Most snapshot errors are a secondary symptom.** Before analyzing the snapshot error itself, confirm there are **no other Errors** on either thread:

- **Main thread** (`main-thread.js` / `lepus.js`): if it throws before finishing, every `createSnapshot(...)` after the throw never runs, so those snapshot *definitions* are missing. Later patches that reference them fail.
- **Background thread**: if it throws so that `hydrate` doesn't complete, the main thread never gets the correct initial patch, and *every* subsequent `snapshotPatchApply` operates on a wrong/empty tree.

So the correct order is: **find and fix the first non-snapshot Error on each thread, then re-check whether the snapshot error is gone.** Only treat a snapshot error as a framework bug after both threads are otherwise clean.

## Diagnosis workflow

1. Collect the full error log from **both threads**, in order. Note which thread each line came from and the call stack (especially anything inside `hydrate` / `onHydrate` / `onLifecycleEventImpl`).
2. Look for an **earlier, non-snapshot error** (e.g. `not a function`, `cannot read property '0' of undefined`). If present, that is almost certainly the root cause — fix it first. See `references/diagnosis-workflow.md`.
3. To inspect the dual-thread traffic, prefer the **built-in ALog macros** (available since `@lynx-js/react` **0.111.2**): rebuild/run with `REACT_ALOG=true` (add `REACT_ALOG_ELEMENT_API=true`, `@lynx-js/react` >= 0.116.3, for Element PAPI tracing) so the runtime emits `[ReactLynxDebug]` diagnostics on both threads. **With the user's consent, you can dump and analyze these logs yourself** by driving the [`lynx-devtool`](../lynx-devtool/SKILL.md) skill (`get-console --thread main` / `--thread background`, reload to catch first-screen hydrate). The manual instrumentation snippet is only needed on `@lynx-js/react` < 0.111.2 (no built-in ALog). All of this is in `references/debug-instrumentation.md`.
4. Decode any raw `snapshotPatch` array using `references/snapshot-patch-format.md`.
5. Match the specific error to its playbook below.
6. If the case is complex and none of the above resolves it, escalate upstream with a minimal reproduction (see references).

You are not limited to handing the user a script: when a device/emulator is connected, offer to enable ALog, dump both threads via lynx-devtool, and analyze the root cause directly — only after the user agrees, since reloading restarts their session.

## Error catalog

| Error message | Where it throws | Read |
| --- | --- | --- |
| `snapshotPatchApply failed: ctx not found, snapshot type: '...'` | inside `snapshotPatchApply` (InsertBefore / RemoveChild / SetAttribute / SetAttributes) | `references/error-ctx-not-found.md` |
| `Error: Snapshot not found: __Card__:__snapshot_...` | `SnapshotInstance` constructor | `references/error-snapshot-not-found.md` |

Both playbooks share the same first move: **rule out an upstream crash** (the "first rule" above).

## Tooling reference

- `references/debug-instrumentation.md` — how to capture dual-thread logs: the built-in ALog macros (`REACT_ALOG` / `REACT_ALOG_ELEMENT_API`, with version requirements) first, dumping + analyzing them yourself via `lynx-devtool`, and the manual instrumentation snippet ([gist](https://gist.github.com/upupming/9f8c5d006dbfaccc225ca6b2ad32e8b5)) as a fallback only for `@lynx-js/react` < 0.111.2. Plus how to read the output.
- `references/snapshot-patch-format.md` — the `snapshotPatch` opcode table and how to pretty-print a raw patch.
- `references/diagnosis-workflow.md` — the end-to-end rule-out procedure, including the Lynx debugger's "Stop at Entry" (main-thread / MTS) setting to get accurate main-thread line numbers.

## Source of truth

Pinned RL3 runtime source (lynx-family/lynx-stack @ `e9e7c093`):

- `snapshotPatchApply` — [packages/react/runtime/src/lifecycle/patch/snapshotPatchApply.ts#L51](https://github.com/lynx-family/lynx-stack/blob/e9e7c093db36fbae51dc964afc1de4600a8b44a1/packages/react/runtime/src/lifecycle/patch/snapshotPatchApply.ts#L51)
- `SnapshotInstance` constructor — [packages/react/runtime/src/snapshot.ts#L293](https://github.com/lynx-family/lynx-stack/blob/e9e7c093db36fbae51dc964afc1de4600a8b44a1/packages/react/runtime/src/snapshot.ts#L293)
- `snapshotPatch` op definitions — [packages/react/runtime/src/lifecycle/patch/snapshotPatch.ts#L22](https://github.com/lynx-family/lynx-stack/blob/e9e7c093db36fbae51dc964afc1de4600a8b44a1/packages/react/runtime/src/lifecycle/patch/snapshotPatch.ts#L22)
- `prettyFormatSnapshotPatch` — [packages/react/runtime/src/debug/formatPatch.ts#L8](https://github.com/lynx-family/lynx-stack/blob/e9e7c093db36fbae51dc964afc1de4600a8b44a1/packages/react/runtime/src/debug/formatPatch.ts#L8)
48 changes: 48 additions & 0 deletions packages/skills/reactlynx-snapshot-debugging/evals/evals.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
{
"skill_name": "reactlynx-snapshot-debugging",
"description": "Task evals for diagnosing and fixing ReactLynx 3 Snapshot runtime errors (ctx not found / Snapshot not found) via dual-thread hydrate and snapshotPatch analysis.",
"evals": [
{
"id": 1,
"name": "ctx-not-found-root-cause",
"prompt": "我的 ReactLynx 3 页面白屏,日志里主线程先报 not a function,后台线程在 hydrate 时报 cannot read property '0' of undefined,最后主线程报 snapshotPatchApply failed: ctx not found, snapshot type: '__Card__:__snapshot_d9459_61989_1'。怎么排查和修复?",
"expected_output": "An explanation that ctx not found is a downstream symptom: the real root cause is the earlier main-thread 'not a function' crash which leaves the main-thread SnapshotInstance tree empty and breaks hydrate, so subsequent snapshotPatchApply cannot find the ctx. Guidance to rule out the upstream error first, use the Lynx debugger's 'Stop at Entry' (main-thread / MTS) to find the real main-thread throw location, and gate background-only code (e.g. wrap the offending call in `if (__BACKGROUND__) { ... }`) so unsupported native/JSB code is not bundled into the main thread.",
"expectations": [
"The answer says ctx not found is usually a secondary symptom and the first/earlier non-snapshot error (the main-thread 'not a function') must be found and fixed first.",
"The answer explains that a main-thread crash prevents createSnapshot definitions from running so the main-thread SnapshotInstance tree ends up empty (only root), and that a background hydrate throw breaks subsequent snapshotPatchApply.",
"The answer recommends enabling the Lynx debugger's 'Stop at Entry' (main-thread / MTS) and reloading to get accurate main-thread line/column numbers for the real throw.",
"The answer recommends gating background-only / unsupported-on-main-thread code (for example wrapping it in `if (__BACKGROUND__) { ... }`, or its deprecated alias `__JS__`) so it is not compiled into the main-thread bundle.",
"The answer identifies snapshotPatchApply as the throw site and does not claim the fix is inside snapshotPatchApply itself."
],
"files": []
},
{
"id": 2,
"name": "snapshot-not-found-investigation",
"prompt": "ReactLynx 3 线上报 Error: Snapshot not found: __Card__:__snapshot_d6204_98358_1,但本地开发没问题。这是什么原因,怎么查?",
"expected_output": "An explanation that 'Snapshot not found' throws in the SnapshotInstance constructor because the Snapshot definition for that type is not registered, and that a prod-only reproduction points at a compile/bundling problem. Guidance to first rule out an earlier main-thread crash, then grep the built lepus.js/main-thread.js for the exact snapshot type string; if absent it is a build/bundling issue, if present analyze OnLifecycleEvent/rLynxChange traffic; escalate upstream with a minimal reproduction if unresolved.",
"expectations": [
"The answer says the error throws in the SnapshotInstance constructor and means the Snapshot definition (the type) is missing/not registered, distinct from ctx not found.",
"The answer says to first rule out another main-thread error that terminates the bundle early and drops later createSnapshot definitions.",
"The answer tells the user to search the built main-thread output (lepus.js / main-thread.js) for the exact type '__snapshot_d6204_98358_1'.",
"The answer says a dev-passes / prod-fails pattern indicates a compile or bundling problem, and that if the type is present at build time the next step is analyzing OnLifecycleEvent and rLynxChange.",
"The answer suggests escalating upstream (e.g. a lynx-stack issue with a minimal reproduction) for complex/unresolved cases."
],
"files": []
},
{
"id": 3,
"name": "decode-snapshot-patch-and-instrument",
"prompt": "我想搞清楚 ReactLynx 3 后台线程发给主线程的 snapshotPatch 到底做了什么,比如 [0, \"__Card__:__snapshot_fffe1_test_3\", 2, 0, \"__Card__:__snapshot_fffe1_test_4\", 7, 1, 2, 7, null, 1, -1, 2, null]。怎么反解?还有怎么打印双线程通信数据?",
"expected_output": "A decoding of the flat snapshotPatch using the opcode table (0=CreateElement[type,id], 1=InsertBefore[parentId,childId,beforeId], 2=RemoveChild, 3=SetAttribute, 4=SetAttributes), yielding CreateElement id 2, CreateElement id 7, InsertBefore child 7 into parent 2, InsertBefore child 2 into parent -1 (root). Plus guidance to install the entry-file instrumentation that wraps OnLifecycleEvent (main->background SnapshotInstance tree) and rLynxChange (background->main snapshotPatch, pretty-formatted) and logs Element PAPI calls.",
"expectations": [
"The answer decodes the array as a sequence of ops by reading one opcode then consuming its parameters, producing at least: CreateElement id 2, CreateElement id 7, InsertBefore into parent 2, InsertBefore into parent -1.",
"The answer gives the opcode meanings, at minimum 0=CreateElement, 1=InsertBefore, 2=RemoveChild, 3=SetAttribute, 4=SetAttributes.",
"The answer notes parentId -1 is the root.",
"The answer explains you can instrument the entry (index.tsx) to wrap OnLifecycleEvent and rLynxChange to print the SnapshotInstance tree and the pretty-formatted snapshotPatch.",
"The answer connects a missing CreateElement for a referenced id to the ctx not found error."
],
"files": []
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
[
{
"query": "ReactLynx 报 snapshotPatchApply failed: ctx not found, snapshot type: '__Card__:__snapshot_d9459_61989_1',怎么修?",
"should_trigger": true
},
{
"query": "线上报 Error: Snapshot not found: __Card__:__snapshot_d6204_98358_1,本地没问题,帮我排查。",
"should_trigger": true
},
{
"query": "RL3 页面 hydrate 之后白屏,后台线程报 cannot read property '0' of undefined,主线程也有报错,怎么定位根因?",
"should_trigger": true
},
{
"query": "怎么打印 ReactLynx 双线程通信里的 OnLifecycleEvent SnapshotInstance 树和 rLynxChange 的 snapshotPatch?",
"should_trigger": true
},
{
"query": "帮我把这个 snapshotPatch 数组反解成可读的操作列表。",
"should_trigger": true
},
{
"query": "ReactLynx 里 useLayoutEffect 为什么不适合做布局测量?",
"should_trigger": false
},
{
"query": "帮我用 vanilla Lynx Element PAPI 写一个计数器卡片。",
"should_trigger": false
},
{
"query": "Lynx DevTool 怎么拉 console 日志?",
"should_trigger": false
},
{
"query": "帮我把 rspeedy 的包体积优化一下。",
"should_trigger": false
}
]
23 changes: 23 additions & 0 deletions packages/skills/reactlynx-snapshot-debugging/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"name": "@lynx-js/skill-reactlynx-snapshot-debugging",
"version": "0.0.0",
"description": "Diagnose and fix ReactLynx 3 (RL3) Snapshot runtime errors such as 'snapshotPatchApply failed: ctx not found' and 'Snapshot not found', using dual-thread hydrate/snapshotPatch analysis.",
"repository": {
"url": "https://github.com/lynx-community/skills"
},
"type": "module",
"files": [
"SKILL.md",
"scripts",
"references",
"examples",
"reference.md",
"examples.md",
"rules"
],
"scripts": {},
"devDependencies": {},
"engines": {
"node": ">=18"
}
}
Loading
Loading