From a6ba772c08e2648e6602787a0ea537d75d72802d Mon Sep 17 00:00:00 2001
From: ysopwnable <161784452+N0zoM1z0@users.noreply.github.com>
Date: Wed, 7 Oct 2026 20:59:56 +0800
Subject: [PATCH 1/2] docs(website): add Notion case study and improve
onboarding
The Electron guide currently uses a teaching fixture. Add a real Notion clipboard case with REA findings, short source excerpts and responsive figures, alongside the existing DX-Ball reconstruction.
- Trace the saved Notion 7.6.1 preload API through its wrappers to the clipboard receiver. Attribute the separate web-cache module probes to their original harness. Public content uses relative source locations and generic example data; packages and complete captures stay local.
- Add a Showcases index and links from the homepage and JavaScript guide. Simplify homepage copy while preserving the approved headline and closing question.
- Show copyable agent installation prompts and the direct setup command. Keep terminal cursors blinking consistently and respect reduced-motion settings.
- Provide one Notes example ZIP. Generate it from the six public source files before website checks and manual Pages deployment; verify its contents against the source. Pages remains manual-only.
Validation:
- `npm run check:fast`
- `python3 scripts/prepare-website.py` and `python3 scripts/verify-website.py`
- Browser checks at `/` and `/rea/`: downloads, prompt/command copying, responsive layouts, keyboard navigation, reduced motion and DX-Ball comparison controls.
- Published REA 4.1.0 preload analysis and feature traces; source/claim/privacy review.
- Real Hopper/Ghidra verification: not performed (website-only change).
---
.github/workflows/website-check.yml | 3 +
.github/workflows/website-pages.yml | 2 +
.gitignore | 3 +
scripts/prepare-website.py | 40 ++
scripts/verify-website.py | 27 +-
website/README.md | 34 +-
website/evidence/guide-examples.md | 6 +
website/evidence/notion-clipboard.md | 133 ++++++
website/public/assets/main.js | 26 +-
website/public/assets/styles.css | 78 +++-
website/public/get-started/index.html | 44 +-
website/public/guides/browser/index.html | 2 +-
website/public/guides/index.html | 6 +-
website/public/guides/javascript/index.html | 91 ++--
website/public/guides/native/index.html | 2 +-
website/public/index.html | 160 ++++---
website/public/showcase/dx-ball/index.html | 6 +-
website/public/showcase/index.html | 102 +++++
website/public/showcase/notion/index.html | 477 ++++++++++++++++++++
19 files changed, 1099 insertions(+), 143 deletions(-)
create mode 100644 scripts/prepare-website.py
create mode 100644 website/evidence/notion-clipboard.md
create mode 100644 website/public/showcase/index.html
create mode 100644 website/public/showcase/notion/index.html
diff --git a/.github/workflows/website-check.yml b/.github/workflows/website-check.yml
index 42257f867..4f410ef62 100644
--- a/.github/workflows/website-check.yml
+++ b/.github/workflows/website-check.yml
@@ -6,6 +6,7 @@ on:
paths:
- "website/**"
- "scripts/verify-website.py"
+ - "scripts/prepare-website.py"
- ".github/workflows/**"
workflow_dispatch:
@@ -24,5 +25,7 @@ jobs:
steps:
- name: Check out the repository
uses: actions/checkout@v4
+ - name: Prepare the example download
+ run: python3 scripts/prepare-website.py
- name: Check website links and publishing configuration
run: python3 scripts/verify-website.py
diff --git a/.github/workflows/website-pages.yml b/.github/workflows/website-pages.yml
index a6118bb6a..52908cd8b 100644
--- a/.github/workflows/website-pages.yml
+++ b/.github/workflows/website-pages.yml
@@ -21,6 +21,8 @@ jobs:
steps:
- name: Check out the repository
uses: actions/checkout@v4
+ - name: Prepare the example download
+ run: python3 scripts/prepare-website.py
- name: Check website links and publishing configuration
run: python3 scripts/verify-website.py
- name: Configure Pages
diff --git a/.gitignore b/.gitignore
index e699d15fd..330e03a16 100644
--- a/.gitignore
+++ b/.gitignore
@@ -45,3 +45,6 @@ Thumbs.db
# Local reference sources and analysis artifacts
/_reference/
+
+# Generated website download
+/website/public/examples/notes-example.zip
diff --git a/scripts/prepare-website.py b/scripts/prepare-website.py
new file mode 100644
index 000000000..1abc9c39d
--- /dev/null
+++ b/scripts/prepare-website.py
@@ -0,0 +1,40 @@
+"""Package the public Notes example for the static website."""
+
+from io import BytesIO
+from pathlib import Path
+from zipfile import ZIP_DEFLATED, ZipFile, ZipInfo
+
+
+EXAMPLE_FILES = (
+ "package.json",
+ "main.js",
+ "preload.js",
+ "renderer.js",
+ "csv.js",
+ "index.html",
+)
+
+
+def prepare_notes_example(site):
+ """Create a ZIP with public source only and reproducible metadata."""
+ source = site / "examples/notes-electron"
+ contents = BytesIO()
+ with ZipFile(contents, "w") as archive:
+ for name in EXAMPLE_FILES:
+ entry = ZipInfo(f"notes-example/{name}", (1980, 1, 1, 0, 0, 0))
+ entry.create_system = 3
+ entry.external_attr = 0o100644 << 16
+ archive.writestr(
+ entry,
+ (source / name).read_bytes(),
+ compress_type=ZIP_DEFLATED,
+ compresslevel=9,
+ )
+ destination = site / "examples/notes-example.zip"
+ destination.write_bytes(contents.getvalue())
+ print(f"Prepared examples/notes-example.zip ({len(EXAMPLE_FILES)} files).")
+
+
+if __name__ == "__main__":
+ root = Path(__file__).resolve().parent.parent
+ prepare_notes_example(root / "website/public")
diff --git a/scripts/verify-website.py b/scripts/verify-website.py
index 60e8e0b0f..d6f4a6be0 100644
--- a/scripts/verify-website.py
+++ b/scripts/verify-website.py
@@ -6,6 +6,7 @@ import re
import sys
from urllib.parse import unquote, urlsplit
import xml.etree.ElementTree as ET
+from zipfile import BadZipFile, ZipFile
class HtmlReferences(HTMLParser):
@@ -89,15 +90,39 @@ def check_publisher(root):
return []
+def check_example_archive(site):
+ """Verify the downloadable ZIP contains the current six source files."""
+ source = site / "examples/notes-electron"
+ expected = {
+ f"notes-example/{name}": source / name
+ for name in ("package.json", "main.js", "preload.js", "renderer.js", "csv.js", "index.html")
+ }
+ path = site / "examples/notes-example.zip"
+ if not path.is_file():
+ return ["Notes example ZIP is missing; run python3 scripts/prepare-website.py."]
+ try:
+ with ZipFile(path) as archive:
+ if sorted(archive.namelist()) != sorted(expected):
+ return ["Notes example ZIP must contain exactly the six files in notes-example/."]
+ return [
+ f"Notes example ZIP has stale source: {name}"
+ for name, original in expected.items()
+ if archive.read(name) != original.read_bytes()
+ ]
+ except (BadZipFile, RuntimeError) as error:
+ return [f"Notes example ZIP cannot be read: {error}"]
+
+
def main():
"""Run the same checks locally, on website PRs and before publishing."""
root = Path(__file__).resolve().parent.parent
pages, references, errors = check_site(root / "website/public")
+ errors.extend(check_example_archive(root / "website/public"))
errors.extend(check_publisher(root))
if errors:
print("Website checks failed:\n" + "\n".join(errors), file=sys.stderr)
return 1
- print(f"Verified {pages} HTML pages, {references} local references and SVG XML.")
+ print(f"Verified {pages} HTML pages, {references} local references, SVG XML and the example ZIP.")
print("website-pages.yml is the sole GitHub Pages publisher.")
return 0
diff --git a/website/README.md b/website/README.md
index 434cc2db2..300f000c1 100644
--- a/website/README.md
+++ b/website/README.md
@@ -1,15 +1,16 @@
# REA website
-An English static website with explanatory figures, worked guides and a DX-Ball investigation.
+An English static website with explanatory figures, worked guides and DX-Ball and Notion investigations.
The public files are in `website/public/`. The site uses HTML, CSS and a small
-script for copying code and following the assembly-to-C comparison; it has no
-build step or npm dependencies.
+script for copying code and following the assembly-to-C comparison. Python
+packages the downloadable example; there is no frontend bundler or npm dependency.
## Local preview
From the repository root:
```sh
+python3 scripts/prepare-website.py
python3 -m http.server 4173 --bind 127.0.0.1 --directory website/public
```
@@ -17,8 +18,10 @@ Open . Refresh the browser after editing a file.
## Pages
-- `public/index.html`: product introduction and DX-Ball overview.
+- `public/index.html`: product introduction and case-study overviews.
+- `public/showcase/index.html`: the case-study index.
- `public/showcase/dx-ball/index.html`: sound-pan investigation and project status.
+- `public/showcase/notion/index.html`: Notion's Electron clipboard bridge and rich clipboard format.
- `public/get-started/index.html`: agent setup, first CLI result and provider guides.
- `public/guides/`: a guide hub and native, JavaScript/Electron and browser examples.
- `public/examples/`: downloadable Electron source and an interactive Notes browser app.
@@ -40,9 +43,18 @@ Electron fixture is for static analysis; Electron is not a prerequisite for
following that example. The separate Notes browser app runs in the local preview
and constructs a CSV download after fetching its JSON data.
+The Electron guide offers one ZIP containing the six source files under
+`notes-example/`. `scripts/prepare-website.py` generates this download from
+an explicit file list, with fixed timestamps and permissions. The ZIP is ignored
+by Git; website checks and each manual publication regenerate it before checking
+and uploading the public directory. The verifier checks that its entries match
+the current source and contain no extra files.
+
Agent terminals show example prompts, not transcripts of previous
-investigations. A short cursor animation starts when the prompt enters view and
-respects reduced-motion preferences. All prompt text is present without JavaScript.
+investigations. All cursors blink continuously with the same CSS animation,
+respecting reduced-motion preferences. Both the prompt and animation work
+without JavaScript. The homepage and agent setup section share a copyable
+installation prompt; setup still presents its plan for approval.
DX-Ball figures and findings refer to the linked 7 October 2026 checkpoint,
commit `a55dca27ec0a07018c1b2c95ae2be027f7d8c3c4`. Update those links and figures
@@ -63,6 +75,15 @@ The worked guides use semantic HTML flows that stack vertically on smaller
screens. The Electron teaching example uses CommonJS, matching its preload
code; the scoped lint override admits `require` only in that example directory.
+The Notion case follows the same HTML figure style. Its short excerpts explain
+the packaged clipboard bridge; separate web-cache probes illustrate the rich
+clipboard and Markdown formats. Only selected source details and generic
+example data belong on the site. Machine paths, account identifiers, local
+configuration, complete vendor bundles and raw captured results stay outside
+the website.
+[evidence/notion-clipboard.md](evidence/notion-clipboard.md) records the REA
+package version, selected findings, source anchors and module-probe scope.
+
## GitHub Pages
`.github/workflows/website-pages.yml` prepares and deploys only `website/public`.
@@ -75,6 +96,7 @@ and the single Pages publisher without installing npm dependencies. The same
checks run before each manual deployment. You can also run them locally:
```sh
+python3 scripts/prepare-website.py
python3 scripts/verify-website.py
```
diff --git a/website/evidence/guide-examples.md b/website/evidence/guide-examples.md
index cae290a8c..70cdc859c 100644
--- a/website/evidence/guide-examples.md
+++ b/website/evidence/guide-examples.md
@@ -30,6 +30,12 @@ rea analyze-javascript-application \
The source was formatted before this result was recorded. Re-run analysis
when the example files change.
+The guide's `examples/notes-example.zip` is generated from exactly these six
+source files, under the `notes-example/` folder. Packaging changes no file
+contents. Website verification compares every ZIP entry to its source file and
+rejects extra entries. Existing source digests and static findings remain valid
+for the unzipped example.
+
## Notes browser example
The three-file browser app is in `website/public/examples/notes-web/`.
diff --git a/website/evidence/notion-clipboard.md b/website/evidence/notion-clipboard.md
new file mode 100644
index 000000000..c7296ca1d
--- /dev/null
+++ b/website/evidence/notion-clipboard.md
@@ -0,0 +1,133 @@
+# Notion clipboard case: sources and scope
+
+The case uses Notion Desktop 7.6.1's shipped JavaScript, a fresh REA inspection
+of its tab preload, and separately recorded probes of cached web-editor modules.
+The public page contains selected result fields, small code excerpts and generic
+example data. Original packages, complete analysis results and captured local
+state are not website assets.
+
+## Fresh REA result
+
+The published npm REA 4.1.0 package inspected the saved tab preload on
+7 October 2026, using Node.js 24.18.0. An independent source build of public
+checkpoint [`810fe53e`](https://github.com/morluto/rea/tree/810fe53e) returned the
+same API and invoke findings. The page uses the published-package result.
+
+The successful `analyze_javascript_application` request inspected the extracted
+directory `.webpack/renderer/tab_browser_view`. Its only file was `preload.js`:
+24,579 bytes, one parsed JavaScript file and no parse failures or truncated
+scopes. The result's relevant observations were:
+
+| Finding | Selected fields | Source range |
+| -------------- | ------------------------------------------------------------------------------------------ | ----------------------------------------- |
+| Context bridge | `api_key: "__electronApi"`, `api_status: "dynamic"`, `members: []`, `unknown_members: 1` | `preload.js`, line 1, columns 22764–22828 |
+| Renderer IPC | `operation: "invoke"`, `channel_expression: "e"`, `channel: null`, `resolution: "dynamic"` | `preload.js`, line 1, columns 6486–6522 |
+
+These are `ast-static-analysis` observations. Lines are one-based and columns
+zero-based, in the original decoded source. The page's two result panels are a
+readable projection of these fields, not a complete or replayable Evidence
+object.
+
+The focused result locates the exposed bridge and IPC wrapper. Reading the
+packaged API object supplies the `clipboard.write` member and its literal
+`notion:clipboard:write` argument. Reading the main-process wrapper and receiver
+connects that channel to Electron's clipboard call. The four-step figure
+summarizes this source reading; it is not an automatically paired IPC graph or
+a captured desktop execution.
+
+Additional feature traces reused the focused result:
+
+- API seed `__electronApi`, exact match: one seed, partial coverage, 17 nodes,
+ 16 edges and no terminal paths.
+- Channel seed `notion:clipboard:write`, exact match: no graph match. Its
+ propagation through the wrappers was unresolved in that graph.
+
+Neither trace establishes a complete clipboard feature graph. The page therefore
+uses the directly observed API/invoke source ranges and the separately inspected
+wrapper code, rather than presenting those trace counts as the clipboard route.
+
+## Packaged bridge excerpts
+
+Artifact: Notion Desktop 7.6.1 Windows `app.asar`, 8,903,750 bytes.
+SHA-256: `97c41bc14fc0ea8c91cd34f502660963dfdf294d5eb35165ccac9d30963069d4`.
+
+| Relative packaged file | Relevant source anchors |
+| ----------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `.webpack/renderer/tab_browser_view/preload.js` | Clipboard API member around character 8274; invoke helpers around 6105/6460; context bridge exposure around 22764 |
+| `.webpack/main/index.js` | Handler wrapper around character 313027; clipboard receiver around 726249; sender-check adapter around 750262; underlying `isNotionWebContents` around 1281553 |
+
+Character anchors refer to decoded JavaScript, counted from the start of each
+file. They are distinct from REA's line/column ranges above.
+
+Source digests:
+
+- Tab preload: `c209b1222cd15721bf1988f88c3e57e187d11306edb5eddb13eca441feb6e337`.
+- Main bundle: `7d06e97721281403e7cf221389de22c6e40d800030a87511231b5390df1452ea`.
+
+The HTML excerpts expand minified variable names and omit unrelated members.
+The API-member excerpt illustrates the relevant member; it does not reproduce
+the entire assembled API object. The sender-check expression expands the
+adapter to its underlying `isNotionWebContents` call. The meaningful operations
+are preserved: bind the literal channel, invoke it, register its receiver,
+check the sender and call `clipboard.write(data)`.
+
+## Separately cached web-editor code
+
+The web examples come from a 13 July 2026 cache, build
+`swv2-23.13.20260713.0259`. This is a separate artifact identity from the desktop
+ASAR. Corresponding API names help explain the two sides; no same-version live
+copy/paste session was captured for this case.
+
+The clipboard examples use:
+
+- Module `747001`: copy-payload construction, private MIME storage and virtual
+ clipboard handling.
+- Module `58169`: marker encoding and decoding.
+- Module `736810`: desktop clipboard routing, including the
+ `electronApi.clipboard.write` call.
+
+The routing helper's asset,
+`clipboard-817bda3acae9bf1e__be64437d1c0e.js`, has SHA-256
+`be64437d1c0eba9c74b48f4307c5936790c59e85aa2ce845e22016a101c2ff7f`.
+
+Saved Node/JSDOM probes used extracted modules, faithful lifted helpers and
+stubbed environment APIs. The marker helper is extracted code; the virtual
+clipboard reader includes a lifted implementation. A matching-ID specimen
+recovered private formats; a stale local entry retained ordinary text and HTML.
+This supports the recovered matching policy, rather than proof of OS clipboard
+hydration or fidelity of a complex block in the running app.
+
+The figure's “Project notes” and `` are generic illustrations.
+`` stands for the generated marker ID; it is not a literal runtime UUID.
+
+## Markdown specimen
+
+The table uses the saved extracted-module probe of structured Markdown parser
+`422457`. Input:
+
+```markdown
+| A | B |
+| --- | --- |
+| 1 | 2 |
+```
+
+In the structured sample record, `/samples/table/raw/blocks/0` is a table with
+`headerRow: true`, two `table_row` children, and cell text `A`/`B` followed by
+`1`/`2`. The corresponding page and chat filtered outputs preserve those rows
+and cells. The public tree is an outline of these fields, not an unmodified
+JSON object or a persisted Notion page.
+
+The broader historical harness included stubs and several parser variants.
+This specimen comes from the intact structured-parser result, rather than a
+legacy table-creation record affected by a synthetic ID collision. Historical
+module probes are attributed to that harness; REA's new role in this page is
+the separately verified preload analysis.
+
+## SQLite follow-up
+
+The short expandable follow-up is based on the same desktop package. Its
+`getSqliteMeta` path transfers `MessageChannelMain` ports between the renderer
+and a SQLite utility process. Later statement batches and results use that
+port. Relevant files are the tab preload, main bundle and `SqliteServer.js`.
+The page explains the source-level handoff without claiming a captured SQL
+session.
diff --git a/website/public/assets/main.js b/website/public/assets/main.js
index 00efa23a3..40a1d9808 100644
--- a/website/public/assets/main.js
+++ b/website/public/assets/main.js
@@ -5,8 +5,13 @@ function initializeCopyButtons() {
if (target === null) return;
const status = document.getElementById("copy-status");
+ const content = target.textContent.trim();
+ const text =
+ target.closest(".terminal-prompt") === null
+ ? content
+ : content.replace(/\s+/g, " ");
try {
- await navigator.clipboard.writeText(target.textContent.trim());
+ await navigator.clipboard.writeText(text);
button.textContent = "Copied";
if (status !== null) status.textContent = "Code copied to clipboard.";
} catch {
@@ -55,22 +60,3 @@ function initializeStepComparisons() {
}
initializeStepComparisons();
-
-function initializeAgentTerminals() {
- if (!("IntersectionObserver" in window)) return;
- const observer = new IntersectionObserver(
- (entries) => {
- entries.forEach((entry) => {
- if (!entry.isIntersecting) return;
- entry.target.classList.add("is-visible");
- observer.unobserve(entry.target);
- });
- },
- { threshold: 0.25 },
- );
- document.querySelectorAll(".agent-terminal").forEach((terminal) => {
- observer.observe(terminal);
- });
-}
-
-initializeAgentTerminals();
diff --git a/website/public/assets/styles.css b/website/public/assets/styles.css
index f27f68211..66bf75dbb 100644
--- a/website/public/assets/styles.css
+++ b/website/public/assets/styles.css
@@ -766,9 +766,7 @@ pre code {
margin-left: 0.4em;
vertical-align: -0.12em;
background: #9bbfe5;
-}
-.agent-terminal.is-visible .terminal-cursor {
- animation: terminal-blink 1s step-end 6;
+ animation: terminal-blink 1s step-end infinite;
}
@keyframes terminal-blink {
50% {
@@ -840,6 +838,61 @@ pre code {
color: var(--ink);
font-size: 0.9rem;
}
+.notion-bridge-flow {
+ grid-template-columns: repeat(4, minmax(0, 1fr));
+}
+.notion-channel {
+ color: var(--accent);
+ background: #edf2f8;
+ border-radius: 2px;
+ box-decoration-break: clone;
+ -webkit-box-decoration-break: clone;
+}
+.notion-clipboard-grid,
+.notion-table-example {
+ display: grid;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ gap: 1rem;
+}
+.notion-clipboard-grid .code-block,
+.notion-table-example .code-block {
+ min-width: 0;
+ margin: 0;
+}
+.notion-clipboard-grid pre,
+.notion-table-example pre {
+ padding: 1rem;
+ font-size: 0.75rem;
+ white-space: pre-wrap;
+ overflow-wrap: anywhere;
+}
+.notion-mini-flow {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.6rem 1.8rem;
+ margin: 1.3rem 0 0;
+ padding: 0;
+ list-style: none;
+ font-size: 0.8rem;
+}
+.notion-mini-flow li {
+ position: relative;
+ margin: 0;
+}
+.notion-mini-flow li:not(:last-child)::after {
+ content: "→";
+ position: absolute;
+ right: -1.3rem;
+ color: #8191a2;
+}
+.case-preview {
+ margin: 0;
+}
+.case-preview pre {
+ white-space: pre-wrap;
+ overflow-wrap: anywhere;
+ font-size: 0.8rem;
+}
.workflow-figure figcaption {
margin-top: 1rem;
color: var(--muted);
@@ -940,7 +993,8 @@ pre code {
.rea-query-pair {
grid-template-columns: 1fr;
}
- .flow-steps {
+ .flow-steps,
+ .notion-bridge-flow {
grid-template-columns: 1fr;
gap: 2rem;
}
@@ -1048,6 +1102,20 @@ pre code {
.workflow-figure {
padding: 1rem;
}
+ .notion-clipboard-grid,
+ .notion-table-example {
+ grid-template-columns: 1fr;
+ }
+ .notion-mini-flow {
+ display: grid;
+ gap: 1.5rem;
+ }
+ .notion-mini-flow li:not(:last-child)::after {
+ content: "↓";
+ right: auto;
+ left: 0.4rem;
+ bottom: -1.4rem;
+ }
.step-selector {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
@@ -1061,7 +1129,7 @@ pre code {
}
@media (prefers-reduced-motion: reduce) {
- .agent-terminal.is-visible .terminal-cursor {
+ .terminal-cursor {
animation: none;
}
}
diff --git a/website/public/get-started/index.html b/website/public/get-started/index.html
index 0541e21c4..15314573f 100644
--- a/website/public/get-started/index.html
+++ b/website/public/get-started/index.html
@@ -22,7 +22,7 @@
>Reverse Engineer Anything
-
-
- Terminal
+
+ Or run setup yourself
+
+
+ Terminal
+ Copy
+
+
+
npx rea-agents@latest setup
+
+
Choose your client and review the setup plan. REA adds its MCP
connection and the matching investigation skill, keeping a backup
diff --git a/website/public/guides/browser/index.html b/website/public/guides/browser/index.html
index 8a1a6cb7f..6c643bd0e 100644
--- a/website/public/guides/browser/index.html
+++ b/website/public/guides/browser/index.html
@@ -22,7 +22,7 @@
>Reverse Engineer Anything
- Save these six files together in a folder named
- notes-example. Static analysis needs Node.js and npm;
- you can inspect the code without installing or launching Electron.
+ Download the example and unzip it. The archive contains the
+ complete notes-example folder. Analyze it with
+ Node.js and npm; Electron is not needed for this example.
- Replace the path with the folder you saved. Look for
+ Replace the path with the folder you unzipped. Look for
normalized_result.summary.ipc and the source
locations in normalized_result.graph. The example has
one channel and one paired invocation.
@@ -281,6 +292,12 @@
runtime observations give you another way to investigate them.
- REA gives your coding agent tools to inspect apps, binaries and
- runtime behavior. Ask a question, follow the code, and use what you
- learn in your own project.
+ Use REA with your coding agent to inspect binaries, trace application
+ code and observe runtime behavior.
+ ›Install REA and connect it to this coding agent using npx
+ rea-agents@latest setup. Show me the setup plan for approval,
+ then verify the installation.
+
+
+
Or run this command in your terminal:
npx rea-agents@latest setup
-
- Connects REA to your agent and installs its investigation workflow.
-
+
+
+
+
+
+
+
What REA can analyze
+
+ Choose a guide for your binary, JavaScript app or browser session.
+
+
+
+
+
Native binaries
+
+ Inspect functions, strings, references and call relationships in
+ executables and libraries.
+
- Use REA from your terminal or through your agent's MCP connection.
- Analysis runs locally, and results include the source locations,
- evidence and open questions you need to continue.
+ A copy operation crosses from the page to Electron's main process.
+ Inspect Notion's packaged JavaScript with REA, then follow one
+ channel through the wrappers to the system clipboard.
-
-
-
Use your analysis tools
- Connect a local analysis provider for native binaries. Static
- JavaScript analysis runs with Node.js and npm.
+ Short code excerpts show each handoff. A second figure explains
+ how an HTML marker connects ordinary clipboard text to Notion's
+ structured block data.
+ Start with a question about a real application. See what REA finds,
+ follow the code, and understand how the pieces fit together.
+
+
+
+
+
Native · Reconstruction
+
+
Rebuilding DX-Ball
+
+ Recover a sound-pan calculation from a 1996 Windows game. REA's
+ instructions, caller references and numeric constants lead to a
+ small C function, checked against the original behavior and all 63
+ compiled bytes.
+
+ Trace a page-facing API through Notion's packaged JavaScript.
+ Follow its IPC wrappers to the system clipboard, then see how a
+ small HTML marker keeps rich block data connected to a copy.
+
+ A familiar action leads through several files. Follow Notion's
+ clipboard API from the page, through Electron, to the system
+ clipboard.
+
+
Notion Desktop 7.6.1 · JavaScript · REA
+
+
+
+
The bridge inside Notion's desktop package
+
+
+ 01 · Page-facing API
+ clipboard.write(data)
+ The preload exposes a callable method.
+
+
+ 02 · Preload wrapper
+ ipcRenderer.invoke
+ The wrapper sends the channel and arguments.
+
+
+ 03 · Main-process handler
+ ipcMain.handle
+ The receiver checks which page sent it.
+
+
+ 04 · System clipboard
+ clipboard.write(data)
+ Electron writes the supplied text and HTML.
+
+
+
+ One channel joins the two sides:
+ notion:clipboard:write
+
+
+ The packaged code uses the same channel in the preload and main
+ process. Resolving the wrappers reveals the Electron calls behind
+ those names.
+
+
+
+
+ The page sees a method called clipboard.write. Its
+ implementation in the preload is a wrapper. The useful question is
+ what that wrapper sends, and which code receives it.
+
+
+
+ Your coding agent
+ Copy
+
+
+
+ ›Use REA to trace how Notion copies text and HTML to the
+ system clipboard. Show the code at each step.
+
+
+
+ An example prompt with Notion's local application package
+ available to your coding agent.
+
+
+
REA finds where to start reading
+
+ Analyzing the tab preload with REA locates the API exposed to the
+ page and the call that sends an IPC request. Both findings come
+ with source ranges in the minified bundle:
+
+
+ Selected fields from a fresh
+ analyze_javascript_application result on Notion's
+ tab preload, recorded with REA 4.1.0. Positions use the original
+ bundle's line and zero-based column.
+
+
+
+ The agent opens those locations, finds the clipboard member in the
+ assembled API, and reads invokerInMain. Its call site
+ supplies
+ notion:clipboard:write. That
+ name gives the agent something precise to look for in the main
+ bundle. The four excerpts below show the route it connects.
+
+
+
Four short pieces of code
+
+ Simplified from the packaged JavaScript, with readable variable
+ names. Unrelated members are omitted.
+
+
+
+
Start at the API exposed to the page
+
+
+ tab_browser_view/preload.js · API member
+ Copy
+
+
+
const clipboardApi = {
+ write: invokerInMain("notion:clipboard:write")
+};
+
+// Included in the API exposed through contextBridge:
+// window.__electronApi.clipboard
+
+
+ This member supplies the channel name. The implementation of
+ invokerInMain tells us how the request leaves the
+ page's process.
+
+ The returned function forwards the arguments through
+ Electron's request-and-response IPC. We can now look for
+ notion:clipboard:write
+ on the main-process side.
+
+ This is the destination. After checking the sender, the
+ handler passes the supplied data to Electron's clipboard API.
+ The channel and the two resolved wrappers connect the exposed
+ method to this call.
+
+
+
+
+
+
+
How does a copied block keep its structure?
+
+ The cached web editor adds a small detail to programmatic copies.
+ It sends text and HTML through
+ electronApi.clipboard.write, while keeping Notion's
+ private block data in a local virtual clipboard. An ID in the HTML
+ connects the two.
+
id: <copy-id>
+mimeTypes:
+ text/_notion-blocks-v3-production
+ → structured block data
+
+
+
+
Read the HTML ID
+
Find the same local ID
+
Restore private formats
+
+
+ Illustrative text and ID. The saved module probe recovered the
+ private formats when IDs matched; a stale entry left only the
+ ordinary text and HTML.
+
+
+
+ On paste, the reader looks for the marker and checks it against
+ the saved copy. A matching ID reconnects the text to its richer
+ representation. This explains why the HTML carries more than
+ visible content.
+
+
+ Web assets cached on July 13, 2026. The desktop-package bridge
+ above and this web-side format are separate pieces of the
+ investigation.
+
+
+
+
+
From Markdown to structured blocks
+
+ The same investigation also recovered Notion's Markdown
+ processing. A small table makes the internal format easy to see:
+ its rows and cells become data that the block renderer can use.
+
+
+ A saved extracted-module probe of web parser
+ 422457. Both page and chat filters preserve this
+ table. The tree shows the recorded rows and cell text.
+
+
+
+ Finding that transformation required separating the ordinary
+ paste/import path from the structured Markdown and AI/chat
+ parsers. Each has its own entry point and intermediate format.
+
+
+
+
+
Try another question
+
+ Start with one feature and follow the code that crosses a process
+ boundary. A named API, a literal channel and its receiver are
+ useful places to begin.
+
+
+ Another path: the offline SQLite connection
+
+ Notion's renderer requests getSqliteMeta through
+ IPC. The main process creates a message channel and gives one
+ port to the renderer and the other to a SQLite utility process.
+ Subsequent statement batches and results travel over that port.
+
+
+ Following this handoff explains where the database work runs,
+ and why tracing only the initial IPC request misses the later
+ conversation.
+
+
+
+ Sources and how to read the excerpts
+
+ The clipboard bridge comes from Notion Desktop 7.6.1's
+ app.asar: the tab preload and
+ main/index.js. The excerpts expand minified
+ variable names and omit unrelated code; the sender-check helper
+ is shown with its underlying
+ isNotionWebContents call.
+
+
+ The virtual clipboard and Markdown examples come from web assets
+ cached on July 13, 2026, including modules 747001,
+ 58169, 736810 and 422457.
+ Those assets have their own version. Saved probes executed
+ extracted modules and small harnesses; their outputs describe
+ those probes rather than a captured desktop UI session.
+
+
+ The REA result shown here is a focused analysis of the saved tab
+ preload, recorded with the published REA 4.1.0 package. It
+ locates the API and invoke operation. The clipboard member and
+ matching receiver are connected by reading their wrapper
+ implementations in the package.
+