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