mirror of
https://github.com/morluto/rea.git
synced 2026-10-26 17:35:11 +00:00
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).
This commit is contained in:
1 parent
a65755728f
commit
a6ba772c08
19 files changed
+1099
-143
No files matched your search
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -45,3 +45,6 @@ Thumbs.db
|
||||
|
||||
# Local reference sources and analysis artifacts
|
||||
/_reference/
|
||||
|
||||
# Generated website download
|
||||
/website/public/examples/notes-example.zip
|
||||
@@ -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")
|
||||
@@ -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
|
||||
|
||||
|
||||
+28
-6
@@ -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 <http://127.0.0.1:4173/>. 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
|
||||
```
|
||||
|
||||
|
||||
@@ -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/`.
|
||||
|
||||
@@ -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 `<copy-id>` are generic illustrations.
|
||||
`<copy-id>` 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.
|
||||
@@ -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();
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
><span class="brand-caption">Reverse Engineer Anything</span></a
|
||||
>
|
||||
<nav class="navigation" aria-label="Main navigation">
|
||||
<a href="../showcase/dx-ball/">DX-Ball</a
|
||||
<a href="../showcase/">Showcases</a
|
||||
><a class="nav-start" href="./" aria-current="page">Get started</a
|
||||
><a href="../guides/">Guides</a
|
||||
><a href="https://github.com/morluto/rea">GitHub</a>
|
||||
@@ -59,20 +59,48 @@
|
||||
include Claude Code, Codex, Cursor, Gemini CLI, Windsurf and other
|
||||
supported MCP clients.
|
||||
</p>
|
||||
<div class="code-block">
|
||||
<div class="code-header">
|
||||
<span>Terminal</span
|
||||
><button
|
||||
<div class="agent-terminal">
|
||||
<div class="terminal-header">
|
||||
<span class="terminal-title"
|
||||
><span class="terminal-dots" aria-hidden="true"
|
||||
><i></i><i></i><i></i></span
|
||||
>Your coding agent</span
|
||||
>
|
||||
<button
|
||||
class="copy-button"
|
||||
type="button"
|
||||
data-copy="agent-setup"
|
||||
aria-label="Copy agent setup command"
|
||||
data-copy="agent-install-prompt"
|
||||
aria-label="Copy REA installation prompt"
|
||||
>
|
||||
Copy
|
||||
</button>
|
||||
</div>
|
||||
<pre><code id="agent-setup">npx rea-agents@latest setup</code></pre>
|
||||
<p class="terminal-prompt">
|
||||
<span class="prompt-symbol" aria-hidden="true">›</span
|
||||
><span id="agent-install-prompt"
|
||||
>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.</span
|
||||
><span class="terminal-cursor" aria-hidden="true"></span>
|
||||
</p>
|
||||
</div>
|
||||
<details class="evidence-details">
|
||||
<summary>Or run setup yourself</summary>
|
||||
<div class="code-block">
|
||||
<div class="code-header">
|
||||
<span>Terminal</span
|
||||
><button
|
||||
class="copy-button"
|
||||
type="button"
|
||||
data-copy="agent-setup"
|
||||
aria-label="Copy agent setup command"
|
||||
>
|
||||
Copy
|
||||
</button>
|
||||
</div>
|
||||
<pre><code id="agent-setup">npx rea-agents@latest setup</code></pre>
|
||||
</div>
|
||||
</details>
|
||||
<p>
|
||||
Choose your client and review the setup plan. REA adds its MCP
|
||||
connection and the matching investigation skill, keeping a backup
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
><span class="brand-caption">Reverse Engineer Anything</span></a
|
||||
>
|
||||
<nav class="navigation" aria-label="Main navigation">
|
||||
<a href="../../showcase/dx-ball/">DX-Ball</a
|
||||
<a href="../../showcase/">Showcases</a
|
||||
><a class="nav-start" href="../../get-started/">Get started</a
|
||||
><a href="../">Guides</a
|
||||
><a href="https://github.com/morluto/rea">GitHub</a>
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
><span class="brand-caption">Reverse Engineer Anything</span></a
|
||||
>
|
||||
<nav class="navigation" aria-label="Main navigation">
|
||||
<a href="../showcase/dx-ball/">DX-Ball</a
|
||||
<a href="../showcase/">Showcases</a
|
||||
><a class="nav-start" href="../get-started/">Get started</a
|
||||
><a href="./" aria-current="page">Guides</a
|
||||
><a href="https://github.com/morluto/rea">GitHub</a>
|
||||
@@ -71,6 +71,10 @@
|
||||
<span>IPC</span> <b aria-hidden="true">→</b> <span>CSV file</span>
|
||||
</div>
|
||||
<a class="text-link" href="javascript/">Application workflows</a>
|
||||
<p>
|
||||
For a larger packaged app, follow
|
||||
<a href="../showcase/notion/">Notion's clipboard bridge</a>.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
<section class="guide-entry">
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
><span class="brand-caption">Reverse Engineer Anything</span></a
|
||||
>
|
||||
<nav class="navigation" aria-label="Main navigation">
|
||||
<a href="../../showcase/dx-ball/">DX-Ball</a
|
||||
<a href="../../showcase/">Showcases</a
|
||||
><a class="nav-start" href="../../get-started/">Get started</a
|
||||
><a href="../">Guides</a
|
||||
><a href="https://github.com/morluto/rea">GitHub</a>
|
||||
@@ -203,42 +203,53 @@
|
||||
<section class="article-section" id="try">
|
||||
<h2>Analyze the example yourself</h2>
|
||||
<p>
|
||||
Save these six files together in a folder named
|
||||
<code>notes-example</code>. 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 <code>notes-example</code> folder. Analyze it with
|
||||
Node.js and npm; Electron is not needed for this example.
|
||||
</p>
|
||||
<ul class="example-files">
|
||||
<li>
|
||||
<a href="../../examples/notes-electron/package.json" download
|
||||
>package.json</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="../../examples/notes-electron/main.js" download
|
||||
>main.js</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="../../examples/notes-electron/preload.js" download
|
||||
>preload.js</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="../../examples/notes-electron/renderer.js" download
|
||||
>renderer.js</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="../../examples/notes-electron/csv.js" download
|
||||
>csv.js</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="../../examples/notes-electron/index.html" download
|
||||
>index.html</a
|
||||
>
|
||||
</li>
|
||||
</ul>
|
||||
<p>
|
||||
<a
|
||||
class="button"
|
||||
href="../../examples/notes-example.zip"
|
||||
download="notes-example.zip"
|
||||
>Download example ZIP</a
|
||||
>
|
||||
</p>
|
||||
<details class="evidence-details">
|
||||
<summary>Browse the six source files</summary>
|
||||
<ul class="example-files">
|
||||
<li>
|
||||
<a href="../../examples/notes-electron/package.json" download
|
||||
>package.json</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="../../examples/notes-electron/main.js" download
|
||||
>main.js</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="../../examples/notes-electron/preload.js" download
|
||||
>preload.js</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="../../examples/notes-electron/renderer.js" download
|
||||
>renderer.js</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="../../examples/notes-electron/csv.js" download
|
||||
>csv.js</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="../../examples/notes-electron/index.html" download
|
||||
>index.html</a
|
||||
>
|
||||
</li>
|
||||
</ul>
|
||||
</details>
|
||||
<div class="code-block">
|
||||
<div class="code-header">
|
||||
<span>Static application analysis</span
|
||||
@@ -255,7 +266,7 @@
|
||||
/absolute/path/to/notes-example --json</code></pre>
|
||||
</div>
|
||||
<p>
|
||||
Replace the path with the folder you saved. Look for
|
||||
Replace the path with the folder you unzipped. Look for
|
||||
<code>normalized_result.summary.ipc</code> and the source
|
||||
locations in <code>normalized_result.graph</code>. The example has
|
||||
one channel and one paired invocation.
|
||||
@@ -281,6 +292,12 @@
|
||||
runtime observations give you another way to investigate them.
|
||||
</p>
|
||||
<ul class="reading-list">
|
||||
<li>
|
||||
<a href="../../showcase/notion/"
|
||||
>Follow Notion's clipboard bridge</a
|
||||
>
|
||||
<span>Apply the same approach to a packaged Electron app.</span>
|
||||
</li>
|
||||
<li>
|
||||
<a href="../browser/">Observe a browser export</a
|
||||
><span
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
><span class="brand-caption">Reverse Engineer Anything</span></a
|
||||
>
|
||||
<nav class="navigation" aria-label="Main navigation">
|
||||
<a href="../../showcase/dx-ball/">DX-Ball</a
|
||||
<a href="../../showcase/">Showcases</a
|
||||
><a class="nav-start" href="../../get-started/">Get started</a
|
||||
><a href="../">Guides</a
|
||||
><a href="https://github.com/morluto/rea">GitHub</a>
|
||||
|
||||
+99
-61
@@ -22,7 +22,7 @@
|
||||
><span class="brand-caption">Reverse Engineer Anything</span></a
|
||||
>
|
||||
<nav class="navigation" aria-label="Main navigation">
|
||||
<a href="showcase/dx-ball/">DX-Ball</a>
|
||||
<a href="showcase/">Showcases</a>
|
||||
<a class="nav-start" href="get-started/">Get started</a>
|
||||
<a href="guides/">Guides</a>
|
||||
<a href="https://github.com/morluto/rea">GitHub</a>
|
||||
@@ -34,17 +34,40 @@
|
||||
<p class="eyebrow">Software analysis · CLI & MCP</p>
|
||||
<h1>Find out how<br />software works.</h1>
|
||||
<p class="lede">
|
||||
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.
|
||||
</p>
|
||||
<div class="actions">
|
||||
<a class="button" href="get-started/">Get started</a>
|
||||
<a class="text-link" href="showcase/dx-ball/"
|
||||
>Read the DX-Ball case study</a
|
||||
>
|
||||
<a class="text-link" href="showcase/">Explore the case studies</a>
|
||||
</div>
|
||||
<div class="hero-command">
|
||||
<div class="agent-terminal">
|
||||
<div class="terminal-header">
|
||||
<span class="terminal-title"
|
||||
><span class="terminal-dots" aria-hidden="true"
|
||||
><i></i><i></i><i></i></span
|
||||
>Your coding agent</span
|
||||
>
|
||||
<button
|
||||
class="copy-button"
|
||||
type="button"
|
||||
data-copy="home-install-prompt"
|
||||
aria-label="Copy REA installation prompt"
|
||||
>
|
||||
Copy
|
||||
</button>
|
||||
</div>
|
||||
<p class="terminal-prompt">
|
||||
<span class="prompt-symbol" aria-hidden="true">›</span
|
||||
><span id="home-install-prompt"
|
||||
>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.</span
|
||||
><span class="terminal-cursor" aria-hidden="true"></span>
|
||||
</p>
|
||||
</div>
|
||||
<p class="caption">Or run this command in your terminal:</p>
|
||||
<div class="code-block code-inline">
|
||||
<code id="setup-command">npx rea-agents@latest setup</code>
|
||||
<button
|
||||
@@ -56,9 +79,43 @@
|
||||
Copy
|
||||
</button>
|
||||
</div>
|
||||
<p class="caption">
|
||||
Connects REA to your agent and installs its investigation workflow.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<div class="section-heading">
|
||||
<h2>What REA can analyze</h2>
|
||||
<p>
|
||||
Choose a guide for your binary, JavaScript app or browser session.
|
||||
</p>
|
||||
</div>
|
||||
<div class="workflows">
|
||||
<div>
|
||||
<h3>Native binaries</h3>
|
||||
<p>
|
||||
Inspect functions, strings, references and call relationships in
|
||||
executables and libraries.
|
||||
</p>
|
||||
<a href="guides/native/">Native analysis guide</a>
|
||||
</div>
|
||||
<div>
|
||||
<h3>JavaScript & Electron</h3>
|
||||
<p>
|
||||
Map modules, routes, IPC and native dependencies from an
|
||||
application folder or ASAR archive.
|
||||
</p>
|
||||
<a href="guides/javascript/">Application workflows</a>
|
||||
</div>
|
||||
<div>
|
||||
<h3>Browser & runtime activity</h3>
|
||||
<p>
|
||||
Capture selected browser or process activity, then compare the
|
||||
results across runs.
|
||||
</p>
|
||||
<a href="guides/browser/">Browser observation guide</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -93,7 +150,7 @@
|
||||
<section class="section">
|
||||
<div class="container feature-case">
|
||||
<div>
|
||||
<p class="eyebrow">Case study</p>
|
||||
<p class="eyebrow">Case study · Native</p>
|
||||
<h2>Rebuilding DX-Ball</h2>
|
||||
<p>
|
||||
DX-Ball is a 1996 Windows game. The project is reconstructing it
|
||||
@@ -134,64 +191,44 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<div class="section-heading">
|
||||
<h2>Bring a target. Ask a question.</h2>
|
||||
<p>
|
||||
Start with a local app or file, then narrow the investigation to
|
||||
the feature or behavior you want to understand.
|
||||
</p>
|
||||
</div>
|
||||
<div class="workflows">
|
||||
<div>
|
||||
<h3>Native applications</h3>
|
||||
<p>
|
||||
Inspect functions, strings, references and call relationships in
|
||||
executables and libraries.
|
||||
</p>
|
||||
<a href="guides/native/">Native analysis guide</a>
|
||||
</div>
|
||||
<div>
|
||||
<h3>JavaScript & Electron</h3>
|
||||
<p>
|
||||
Map modules, routes, IPC and native dependencies from an
|
||||
application folder or ASAR archive.
|
||||
</p>
|
||||
<a href="guides/javascript/">Application workflows</a>
|
||||
</div>
|
||||
<div>
|
||||
<h3>Runtime behavior</h3>
|
||||
<p>
|
||||
Capture selected browser or process activity, then compare the
|
||||
results across runs.
|
||||
</p>
|
||||
<a href="guides/browser/">Browser observation guide</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section">
|
||||
<div class="container feature-case">
|
||||
<div>
|
||||
<h2>Keep the investigation in your workflow.</h2>
|
||||
<p class="eyebrow">Case study · Electron</p>
|
||||
<h2>Notion's Electron clipboard</h2>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<h3>Use your analysis tools</h3>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
<a class="text-link" href="get-started/#native"
|
||||
>Choose a native provider</a
|
||||
<a class="text-link" href="showcase/notion/"
|
||||
>Follow the clipboard bridge</a
|
||||
>
|
||||
</div>
|
||||
<figure class="workflow-figure case-preview">
|
||||
<p class="figure-title">Clipboard IPC channel</p>
|
||||
<div class="code-block">
|
||||
<div class="code-header"><span>Preload · send</span></div>
|
||||
<pre><code>invokerInMain(
|
||||
<span class="notion-channel">"notion:clipboard:write"</span>
|
||||
)</code></pre>
|
||||
</div>
|
||||
<div class="code-block">
|
||||
<div class="code-header"><span>Main process · receive</span></div>
|
||||
<pre><code>addListener(
|
||||
<span class="notion-channel">"notion:clipboard:write"</span>,
|
||||
handler
|
||||
)</code></pre>
|
||||
</div>
|
||||
<figcaption>
|
||||
Matching names give the investigation a path to follow.
|
||||
</figcaption>
|
||||
</figure>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -200,7 +237,8 @@
|
||||
<div>
|
||||
<h2>What would you like to understand?</h2>
|
||||
<p>
|
||||
Connect your agent, or get your first result from the terminal.
|
||||
Connect REA to your coding agent, or run your first analysis from
|
||||
the terminal.
|
||||
</p>
|
||||
</div>
|
||||
<a class="button" href="get-started/">Get started</a>
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
><span class="brand-caption">Reverse Engineer Anything</span></a
|
||||
>
|
||||
<nav class="navigation" aria-label="Main navigation">
|
||||
<a href="./" aria-current="page">DX-Ball</a>
|
||||
<a href="../">Showcases</a>
|
||||
<a class="nav-start" href="../../get-started/">Get started</a>
|
||||
<a href="../../guides/">Guides</a>
|
||||
<a href="https://github.com/morluto/rea">GitHub</a>
|
||||
@@ -31,7 +31,9 @@
|
||||
</header>
|
||||
<main id="main" class="container">
|
||||
<div class="page-heading">
|
||||
<p class="breadcrumb"><a href="../../">Home</a> / DX-Ball</p>
|
||||
<p class="breadcrumb">
|
||||
<a href="../../">Home</a> / <a href="../">Showcases</a> / DX-Ball
|
||||
</p>
|
||||
<p class="eyebrow">Case study · DX-Ball 1.07</p>
|
||||
<h1>Rebuilding DX-Ball<br />from its executable.</h1>
|
||||
<p class="lede">
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Case studies — REA</title>
|
||||
<meta
|
||||
name="description"
|
||||
content="Follow REA investigations through code and figures: recover a native calculation in DX-Ball and trace Notion's Electron clipboard bridge."
|
||||
/>
|
||||
<meta name="theme-color" content="#ffffff" />
|
||||
<link rel="icon" href="../assets/favicon.svg" type="image/svg+xml" />
|
||||
<link rel="stylesheet" href="../assets/styles.css" />
|
||||
<script src="../assets/main.js" defer></script>
|
||||
</head>
|
||||
<body>
|
||||
<a class="skip-link" href="#main">Skip to content</a>
|
||||
<header class="site-header">
|
||||
<div class="container header-inner">
|
||||
<a class="brand" href="../" aria-label="REA home"
|
||||
><span class="brand-name">REA</span
|
||||
><span class="brand-caption">Reverse Engineer Anything</span></a
|
||||
>
|
||||
<nav class="navigation" aria-label="Main navigation">
|
||||
<a href="./" aria-current="page">Showcases</a>
|
||||
<a class="nav-start" href="../get-started/">Get started</a>
|
||||
<a href="../guides/">Guides</a>
|
||||
<a href="https://github.com/morluto/rea">GitHub</a>
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
<main id="main" class="container">
|
||||
<div class="page-heading">
|
||||
<p class="breadcrumb"><a href="../">Home</a> / Showcases</p>
|
||||
<h1>Follow the<br />investigation.</h1>
|
||||
<p class="lede">
|
||||
Start with a question about a real application. See what REA finds,
|
||||
follow the code, and understand how the pieces fit together.
|
||||
</p>
|
||||
</div>
|
||||
<div class="guide-index">
|
||||
<section class="guide-entry">
|
||||
<p class="eyebrow">Native · Reconstruction</p>
|
||||
<div>
|
||||
<h2>Rebuilding DX-Ball</h2>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
<div class="guide-route">
|
||||
<span>Executable</span> <b aria-hidden="true">→</b>
|
||||
<span>REA findings</span> <b aria-hidden="true">→</b>
|
||||
<span>Recovered C</span> <b aria-hidden="true">→</b>
|
||||
<span>Validation</span>
|
||||
</div>
|
||||
<a class="text-link" href="dx-ball/">Read the DX-Ball case study</a>
|
||||
</div>
|
||||
</section>
|
||||
<section class="guide-entry">
|
||||
<p class="eyebrow">Electron · Feature tracing</p>
|
||||
<div>
|
||||
<h2>Following a copy through Notion</h2>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
<div class="guide-route">
|
||||
<span>Packaged app</span> <b aria-hidden="true">→</b>
|
||||
<span>Exposed API</span> <b aria-hidden="true">→</b>
|
||||
<span>IPC wrappers</span> <b aria-hidden="true">→</b>
|
||||
<span>Clipboard</span>
|
||||
</div>
|
||||
<a class="text-link" href="notion/">Read the Notion case study</a>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<section class="article-section">
|
||||
<h2>Try a smaller example first.</h2>
|
||||
<p>
|
||||
The guides walk through a native calculation, an Electron export and a
|
||||
browser request. Each starts with a prompt and a concrete target.
|
||||
</p>
|
||||
<a class="text-link" href="../guides/">Explore the worked guides</a>
|
||||
</section>
|
||||
</main>
|
||||
<footer class="site-footer">
|
||||
<div class="container footer-inner">
|
||||
<p>REA · Reverse Engineer Anything</p>
|
||||
<div class="footer-links">
|
||||
<a href="https://github.com/morluto/rea">Source</a
|
||||
><a href="https://github.com/morluto/rea/blob/main/LICENSE"
|
||||
>MIT license</a
|
||||
><a href="https://discord.gg/GkcryMnJDM">Community</a>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
<p class="sr-only" id="copy-status" role="status" aria-live="polite"></p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,477 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Following Notion's clipboard through Electron — REA</title>
|
||||
<meta
|
||||
name="description"
|
||||
content="Follow Notion's clipboard API through Electron's preload, IPC wrappers and main process, with short code excerpts and a look at its rich clipboard format."
|
||||
/>
|
||||
<meta name="theme-color" content="#ffffff" />
|
||||
<link rel="icon" href="../../assets/favicon.svg" type="image/svg+xml" />
|
||||
<link rel="stylesheet" href="../../assets/styles.css" />
|
||||
<script src="../../assets/main.js" defer></script>
|
||||
</head>
|
||||
<body>
|
||||
<a class="skip-link" href="#main">Skip to content</a>
|
||||
<header class="site-header">
|
||||
<div class="container header-inner">
|
||||
<a class="brand" href="../../" aria-label="REA home"
|
||||
><span class="brand-name">REA</span
|
||||
><span class="brand-caption">Reverse Engineer Anything</span></a
|
||||
>
|
||||
<nav class="navigation" aria-label="Main navigation">
|
||||
<a href="../">Showcases</a>
|
||||
<a class="nav-start" href="../../get-started/">Get started</a>
|
||||
<a href="../../guides/">Guides</a>
|
||||
<a href="https://github.com/morluto/rea">GitHub</a>
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
<main id="main" class="container">
|
||||
<div class="page-heading">
|
||||
<p class="breadcrumb">
|
||||
<a href="../../">Home</a> / <a href="../">Showcases</a> / Notion
|
||||
</p>
|
||||
<p class="eyebrow">Case study · Electron</p>
|
||||
<h1>Following a copy<br />through Notion.</h1>
|
||||
<p class="lede">
|
||||
A familiar action leads through several files. Follow Notion's
|
||||
clipboard API from the page, through Electron, to the system
|
||||
clipboard.
|
||||
</p>
|
||||
<p class="page-meta">Notion Desktop 7.6.1 · JavaScript · REA</p>
|
||||
</div>
|
||||
|
||||
<figure class="workflow-figure">
|
||||
<p class="figure-title">The bridge inside Notion's desktop package</p>
|
||||
<ol class="flow-steps notion-bridge-flow">
|
||||
<li>
|
||||
<span class="flow-label">01 · Page-facing API</span>
|
||||
<span class="flow-node"><code>clipboard.write(data)</code></span>
|
||||
<span class="flow-note"
|
||||
>The preload exposes a callable method.</span
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<span class="flow-label">02 · Preload wrapper</span>
|
||||
<span class="flow-node"><code>ipcRenderer.invoke</code></span>
|
||||
<span class="flow-note"
|
||||
>The wrapper sends the channel and arguments.</span
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<span class="flow-label">03 · Main-process handler</span>
|
||||
<span class="flow-node"><code>ipcMain.handle</code></span>
|
||||
<span class="flow-note"
|
||||
>The receiver checks which page sent it.</span
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<span class="flow-label">04 · System clipboard</span>
|
||||
<span class="flow-node"><code>clipboard.write(data)</code></span>
|
||||
<span class="flow-note"
|
||||
>Electron writes the supplied text and HTML.</span
|
||||
>
|
||||
</li>
|
||||
</ol>
|
||||
<p class="flow-result">
|
||||
One channel joins the two sides:
|
||||
<code class="notion-channel">notion:clipboard:write</code>
|
||||
</p>
|
||||
<figcaption>
|
||||
The packaged code uses the same channel in the preload and main
|
||||
process. Resolving the wrappers reveals the Electron calls behind
|
||||
those names.
|
||||
</figcaption>
|
||||
</figure>
|
||||
|
||||
<div class="article-layout">
|
||||
<nav class="contents" aria-label="On this page">
|
||||
<p>In this case study</p>
|
||||
<ul>
|
||||
<li><a href="#clipboard">Follow the clipboard bridge</a></li>
|
||||
<li><a href="#rich-clipboard">Keeping a block's structure</a></li>
|
||||
<li><a href="#markdown">Markdown becomes blocks</a></li>
|
||||
<li><a href="#continue">Try another question</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
<article class="article" aria-label="Notion Electron case study">
|
||||
<section class="article-section" id="clipboard">
|
||||
<h2>Where does the copy operation go?</h2>
|
||||
<p>
|
||||
The page sees a method called <code>clipboard.write</code>. Its
|
||||
implementation in the preload is a wrapper. The useful question is
|
||||
what that wrapper sends, and which code receives it.
|
||||
</p>
|
||||
<div class="agent-terminal">
|
||||
<div class="terminal-header">
|
||||
<span class="terminal-title"
|
||||
><span class="terminal-dots" aria-hidden="true"
|
||||
><i></i><i></i><i></i></span
|
||||
>Your coding agent</span
|
||||
><button
|
||||
class="copy-button"
|
||||
type="button"
|
||||
data-copy="notion-prompt"
|
||||
aria-label="Copy Notion analysis prompt"
|
||||
>
|
||||
Copy
|
||||
</button>
|
||||
</div>
|
||||
<p class="terminal-prompt">
|
||||
<span class="prompt-symbol" aria-hidden="true">›</span
|
||||
><span id="notion-prompt"
|
||||
>Use REA to trace how Notion copies text and HTML to the
|
||||
system clipboard. Show the code at each step.</span
|
||||
><span class="terminal-cursor" aria-hidden="true"></span>
|
||||
</p>
|
||||
</div>
|
||||
<p class="caption">
|
||||
An example prompt with Notion's local application package
|
||||
available to your coding agent.
|
||||
</p>
|
||||
|
||||
<h3 class="small-heading">REA finds where to start reading</h3>
|
||||
<p>
|
||||
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:
|
||||
</p>
|
||||
<figure class="workflow-figure">
|
||||
<p class="figure-title">REA findings → two places to open</p>
|
||||
<div class="rea-query-pair">
|
||||
<div class="code-block">
|
||||
<div class="code-header"><span>Exposed page API</span></div>
|
||||
<pre><code>api_key: "__electronApi"
|
||||
api_status: "dynamic"
|
||||
source: preload.js:1:22764–22828</code></pre>
|
||||
</div>
|
||||
<div class="code-block">
|
||||
<div class="code-header"><span>Renderer IPC call</span></div>
|
||||
<pre><code>operation: "invoke"
|
||||
channel_expression: "e"
|
||||
source: preload.js:1:6486–6522</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
<figcaption>
|
||||
Selected fields from a fresh
|
||||
<code>analyze_javascript_application</code> result on Notion's
|
||||
tab preload, recorded with REA 4.1.0. Positions use the original
|
||||
bundle's line and zero-based column.
|
||||
</figcaption>
|
||||
</figure>
|
||||
<p>
|
||||
The agent opens those locations, finds the clipboard member in the
|
||||
assembled API, and reads <code>invokerInMain</code>. Its call site
|
||||
supplies
|
||||
<code class="notion-channel">notion:clipboard:write</code>. That
|
||||
name gives the agent something precise to look for in the main
|
||||
bundle. The four excerpts below show the route it connects.
|
||||
</p>
|
||||
|
||||
<h3 class="small-heading">Four short pieces of code</h3>
|
||||
<p class="caption">
|
||||
Simplified from the packaged JavaScript, with readable variable
|
||||
names. Unrelated members are omitted.
|
||||
</p>
|
||||
<ol class="rea-queries">
|
||||
<li>
|
||||
<h4>Start at the API exposed to the page</h4>
|
||||
<div class="code-block">
|
||||
<div class="code-header">
|
||||
<span>tab_browser_view/preload.js · API member</span
|
||||
><button
|
||||
class="copy-button"
|
||||
type="button"
|
||||
data-copy="notion-preload-source"
|
||||
aria-label="Copy Notion preload excerpt"
|
||||
>
|
||||
Copy
|
||||
</button>
|
||||
</div>
|
||||
<pre><code id="notion-preload-source">const clipboardApi = {
|
||||
write: invokerInMain(<span class="notion-channel">"notion:clipboard:write"</span>)
|
||||
};
|
||||
|
||||
// Included in the API exposed through contextBridge:
|
||||
// window.__electronApi.clipboard</code></pre>
|
||||
</div>
|
||||
<p>
|
||||
This member supplies the channel name. The implementation of
|
||||
<code>invokerInMain</code> tells us how the request leaves the
|
||||
page's process.
|
||||
</p>
|
||||
</li>
|
||||
<li>
|
||||
<h4>Resolve the preload's wrapper</h4>
|
||||
<div class="code-block">
|
||||
<div class="code-header">
|
||||
<span>tab_browser_view/preload.js · IPC helper</span
|
||||
><button
|
||||
class="copy-button"
|
||||
type="button"
|
||||
data-copy="notion-invoke-source"
|
||||
aria-label="Copy Notion invoke wrapper excerpt"
|
||||
>
|
||||
Copy
|
||||
</button>
|
||||
</div>
|
||||
<pre><code id="notion-invoke-source">function invokerInMain(channel) {
|
||||
return (...args) => invokeInMainAndReturnResult(channel, ...args);
|
||||
}
|
||||
|
||||
function invokeInMainAndReturnResult(channel, ...args) {
|
||||
return ipcRenderer.invoke(channel, ...args);
|
||||
}</code></pre>
|
||||
</div>
|
||||
<p>
|
||||
The returned function forwards the arguments through
|
||||
Electron's request-and-response IPC. We can now look for
|
||||
<code class="notion-channel">notion:clipboard:write</code>
|
||||
on the main-process side.
|
||||
</p>
|
||||
</li>
|
||||
<li>
|
||||
<h4>Check how the receiver is registered</h4>
|
||||
<div class="code-block">
|
||||
<div class="code-header">
|
||||
<span>main/index.js · handler helper</span
|
||||
><button
|
||||
class="copy-button"
|
||||
type="button"
|
||||
data-copy="notion-handler-source"
|
||||
aria-label="Copy Notion handler wrapper excerpt"
|
||||
>
|
||||
Copy
|
||||
</button>
|
||||
</div>
|
||||
<pre><code id="notion-handler-source">const handleRequestFromRenderer = {
|
||||
addListener(channel, handler) {
|
||||
ipcMain.handle(channel, handler);
|
||||
}
|
||||
};</code></pre>
|
||||
</div>
|
||||
<p>
|
||||
The main process uses another wrapper. Its
|
||||
<code>addListener</code> method registers an
|
||||
<code>ipcMain.handle</code> receiver for that channel.
|
||||
</p>
|
||||
</li>
|
||||
<li>
|
||||
<h4>Find the write and its sender check</h4>
|
||||
<div class="code-block">
|
||||
<div class="code-header">
|
||||
<span>main/index.js · clipboard receiver</span
|
||||
><button
|
||||
class="copy-button"
|
||||
type="button"
|
||||
data-copy="notion-write-source"
|
||||
aria-label="Copy Notion clipboard receiver excerpt"
|
||||
>
|
||||
Copy
|
||||
</button>
|
||||
</div>
|
||||
<pre><code id="notion-write-source">handleRequestFromRenderer.addListener(
|
||||
<span class="notion-channel">"notion:clipboard:write"</span>,
|
||||
(event, data) =>
|
||||
isNotionWebContents(event.sender)
|
||||
? clipboard.write(data)
|
||||
: undefined
|
||||
);</code></pre>
|
||||
</div>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
</li>
|
||||
</ol>
|
||||
</section>
|
||||
|
||||
<section class="article-section" id="rich-clipboard">
|
||||
<h2>How does a copied block keep its structure?</h2>
|
||||
<p>
|
||||
The cached web editor adds a small detail to programmatic copies.
|
||||
It sends text and HTML through
|
||||
<code>electronApi.clipboard.write</code>, while keeping Notion's
|
||||
private block data in a local virtual clipboard. An ID in the HTML
|
||||
connects the two.
|
||||
</p>
|
||||
<figure class="workflow-figure">
|
||||
<p class="figure-title">One copy, two representations</p>
|
||||
<div class="notion-clipboard-grid">
|
||||
<div class="code-block">
|
||||
<div class="code-header">
|
||||
<span>Sent to the system clipboard</span>
|
||||
</div>
|
||||
<pre><code>text: "Project notes"
|
||||
html: "<p>Project notes</p>"
|
||||
+ "<!-- notionvc: <span class="notion-channel"><copy-id></span> -->"</code></pre>
|
||||
</div>
|
||||
<div class="code-block">
|
||||
<div class="code-header">
|
||||
<span>Kept in the local virtual clipboard</span>
|
||||
</div>
|
||||
<pre><code>id: <span class="notion-channel"><copy-id></span>
|
||||
mimeTypes:
|
||||
text/_notion-blocks-v3-production
|
||||
→ structured block data</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
<ol
|
||||
class="notion-mini-flow"
|
||||
aria-label="Restoring rich clipboard data on paste"
|
||||
>
|
||||
<li>Read the HTML ID</li>
|
||||
<li>Find the same local ID</li>
|
||||
<li>Restore private formats</li>
|
||||
</ol>
|
||||
<figcaption>
|
||||
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.
|
||||
</figcaption>
|
||||
</figure>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
<p class="caption">
|
||||
Web assets cached on July 13, 2026. The desktop-package bridge
|
||||
above and this web-side format are separate pieces of the
|
||||
investigation.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section class="article-section" id="markdown">
|
||||
<h2>From Markdown to structured blocks</h2>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
<figure class="workflow-figure">
|
||||
<p class="figure-title">
|
||||
A table through Notion's Markdown parser
|
||||
</p>
|
||||
<div class="notion-table-example">
|
||||
<div class="code-block">
|
||||
<div class="code-header"><span>Markdown input</span></div>
|
||||
<pre><code>| A | B |
|
||||
| - | - |
|
||||
| 1 | 2 |</code></pre>
|
||||
</div>
|
||||
<div class="code-block">
|
||||
<div class="code-header">
|
||||
<span>Structured parser output · excerpt</span>
|
||||
</div>
|
||||
<pre><code>table · headerRow: true
|
||||
table_row
|
||||
cells: ["A"], ["B"]
|
||||
table_row
|
||||
cells: ["1"], ["2"]</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
<figcaption>
|
||||
A saved extracted-module probe of web parser
|
||||
<code>422457</code>. Both page and chat filters preserve this
|
||||
table. The tree shows the recorded rows and cell text.
|
||||
</figcaption>
|
||||
</figure>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section class="article-section" id="continue">
|
||||
<h2>Try another question</h2>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
<details class="evidence-details">
|
||||
<summary>Another path: the offline SQLite connection</summary>
|
||||
<p>
|
||||
Notion's renderer requests <code>getSqliteMeta</code> 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.
|
||||
</p>
|
||||
<p>
|
||||
Following this handoff explains where the database work runs,
|
||||
and why tracing only the initial IPC request misses the later
|
||||
conversation.
|
||||
</p>
|
||||
</details>
|
||||
<details class="evidence-details">
|
||||
<summary>Sources and how to read the excerpts</summary>
|
||||
<p>
|
||||
The clipboard bridge comes from Notion Desktop 7.6.1's
|
||||
<code>app.asar</code>: the tab preload and
|
||||
<code>main/index.js</code>. The excerpts expand minified
|
||||
variable names and omit unrelated code; the sender-check helper
|
||||
is shown with its underlying
|
||||
<code>isNotionWebContents</code> call.
|
||||
</p>
|
||||
<p>
|
||||
The virtual clipboard and Markdown examples come from web assets
|
||||
cached on July 13, 2026, including modules <code>747001</code>,
|
||||
<code>58169</code>, <code>736810</code> and <code>422457</code>.
|
||||
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.
|
||||
</p>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
</details>
|
||||
<ul class="reading-list">
|
||||
<li>
|
||||
<a href="../../guides/javascript/"
|
||||
>JavaScript application guide</a
|
||||
><span
|
||||
>Follow a small Electron application's export feature.</span
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="../dx-ball/">DX-Ball reconstruction</a
|
||||
><span
|
||||
>Follow native instructions to a checked C function.</span
|
||||
>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="actions">
|
||||
<a class="button" href="../../get-started/"
|
||||
>Start with your own target</a
|
||||
>
|
||||
</div>
|
||||
</section>
|
||||
</article>
|
||||
</div>
|
||||
</main>
|
||||
<footer class="site-footer">
|
||||
<div class="container footer-inner">
|
||||
<p>REA · Reverse Engineer Anything</p>
|
||||
<div class="footer-links">
|
||||
<a href="https://github.com/morluto/rea">Source</a
|
||||
><a href="https://github.com/morluto/rea/blob/main/LICENSE"
|
||||
>MIT license</a
|
||||
><a href="https://discord.gg/GkcryMnJDM">Community</a>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
<p class="sr-only" id="copy-status" role="status" aria-live="polite"></p>
|
||||
</body>
|
||||
</html>
|
||||
Reference in new issue
Block a user